Skip to content

CDDL 2 Python generator#16914

Open
AutomatedTester wants to merge 25 commits intotrunkfrom
cddl2py
Open

CDDL 2 Python generator#16914
AutomatedTester wants to merge 25 commits intotrunkfrom
cddl2py

Conversation

@AutomatedTester
Copy link
Copy Markdown
Member

@AutomatedTester AutomatedTester commented Jan 16, 2026

This generates bidi code based off of the CDDL that we can update from the specification. I expect over time the generation will need other features added.

@selenium-ci selenium-ci added the C-py Python Bindings label Jan 16, 2026
@AutomatedTester AutomatedTester marked this pull request as draft January 16, 2026 10:14
Copilot AI review requested due to automatic review settings February 16, 2026 11:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a CDDL (Concise Data Definition Language) to Python generator for WebDriver BiDi modules. It generates 9 BiDi protocol modules from the W3C specification, replacing hand-written implementations with auto-generated code.

Changes:

  • Adds py/generate_bidi.py - CDDL parser and Python code generator (623 lines)
  • Adds Bazel build integration for code generation
  • Generates 9 BiDi modules (browser, browsing_context, emulation, input, network, script, session, storage, webextension) with 146 type definitions and 52 commands
  • Adds validation tooling and documentation

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
py/generate_bidi.py Core CDDL parser and Python code generator
py/private/generate_bidi.bzl Bazel rule for BiDi code generation
py/BUILD.bazel Integration of generation target
py/requirements.txt Added pycddl dependency
py/selenium/webdriver/common/bidi/*.py Generated BiDi module replacements
py/validate_bidi_modules.py Validation tooling for comparing generated vs hand-written code
common/bidi/spec/local.cddl CDDL specification (1331 lines)
Various .md files Documentation and findings

Copilot AI review requested due to automatic review settings February 17, 2026 10:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 24 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

py/validate_bidi_modules.py:1

  • Corrected spelling of 'Analyze' to match class name convention.
#!/usr/bin/env python3

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated 10 comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 35 out of 36 changed files in this pull request and generated 10 comments.

Copilot AI review requested due to automatic review settings February 25, 2026 13:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 35 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

py/selenium/webdriver/remote/webdriver.py:1107

  • start_devtools()/bidi_connection() call import_cdp() which imports selenium.webdriver.common.bidi.cdp, but this PR deletes py/selenium/webdriver/common/bidi/cdp.py. That will cause a ModuleNotFoundError the first time devtools/BiDi connection code runs. Either keep/replace the cdp module (and update import_cdp() accordingly) or remove these code paths.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 8 comments.

Copilot AI review requested due to automatic review settings March 2, 2026 11:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings March 7, 2026 11:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 37 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (2)

dotnet/src/webdriver/BiDi/EventDispatcher.cs:43

  • _pendingEvents is declared as Channel<EventItem>, but EventItem is not defined anywhere in this file (and EnqueueEvent writes PendingEvent). This will not compile; either change the channel back to Channel<PendingEvent> or introduce/rename the EventItem type consistently.
    private readonly Channel<EventItem> _pendingEvents = Channel.CreateUnbounded<EventItem>(new()
    {
        SingleReader = true,
        SingleWriter = true
    });

dotnet/src/webdriver/BiDi/EventDispatcher.cs:97

  • Inside ProcessEventsAsync, the loop variable was renamed to evt, but the body still references result.Method / result.EventArgs. This is a compile-time error; update the references to use evt (and ensure evt has the expected members).
            while (reader.TryRead(out var evt))
            {
                if (_eventRegistrations.TryGetValue(result.Method, out var registration))
                {
                    foreach (var handler in registration.GetHandlers()) // copy-on-write array, safe to iterate
                    {
                        var runningHandlerTask = InvokeHandlerAsync(handler, result.EventArgs);

Comment on lines +15 to +18
def transform_download_params(
allowed: bool | None,
destination_folder: str | None,
) -> dict[str, Any] | None:
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destination_folder is typed as str | None, but the docstring says it accepts pathlib.Path/path-like values. If path-like inputs are intended, the type hint should include os.PathLike[str] (or str | os.PathLike[str] | None) to match actual supported usage.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to 86
private async Task ProcessEventsAsync()
{
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProcessEventsAwaiterAsync was renamed to ProcessEventsAsync, but the constructor still starts _eventEmitterTask with Task.Run(ProcessEventsAwaiterAsync). This mismatch will fail to compile; rename the task target to ProcessEventsAsync (or keep the old method name).

Copilot uses AI. Check for mistakes.
# because cdp has been updated but selenium python has not been released yet.
devtools_path = pathlib.Path(__file__).parents[1].joinpath("devtools")
versions = tuple(f.name for f in devtools_path.iterdir() if f.is_dir() and f.name != "latest")
versions = tuple(f.name for f in devtools_path.iterdir() if f.is_dir())
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

versions is built from all subdirectories, but the next line assumes every directory name is of the form v<digits> (int(x[1:])). If devtools/ contains any non-version directory (e.g., latest), this will raise ValueError during fallback loading. Filter the directory names to v\d+ (or explicitly exclude latest) before converting.

Suggested change
versions = tuple(f.name for f in devtools_path.iterdir() if f.is_dir())
versions = tuple(
f.name
for f in devtools_path.iterdir()
if f.is_dir() and f.name.startswith("v") and f.name[1:].isdigit()
)

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +157
self.subscribe_to_event(event_config.bidi_event, contexts)
self.add_callback_to_tracking(event_config.bidi_event, callback_id)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add_event_handler registers a callback with conn.add_callback(...) before subscribing via session.subscribe(...). If the subscribe call fails/raises, the callback remains registered and the internal tracking state may be inconsistent. Consider subscribing first (or wrapping subscribe in try and removing the callback on failure) to keep registration atomic.

Suggested change
self.subscribe_to_event(event_config.bidi_event, contexts)
self.add_callback_to_tracking(event_config.bidi_event, callback_id)
try:
self.subscribe_to_event(event_config.bidi_event, contexts)
self.add_callback_to_tracking(event_config.bidi_event, callback_id)
except Exception:
# Ensure we do not leave a registered callback if subscription or tracking fails
try:
self.conn.remove_callback(event_wrapper, callback_id)
except Exception:
# Ignore cleanup errors to avoid masking the original exception
pass
raise

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +68
return new Subscription(subscribeResult.Subscription, this, eventHandler);
}
catch
{
registration.RemoveHandler(eventHandler);
throw;
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added try/catch around the subscription is syntactically incorrect: there is no try { ... } block before catch, and the braces/indentation don’t match. This will not compile; wrap the SubscribeAsync + AddHandler + return in a try block and keep the catch cleanup in the corresponding catch block.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +63
devtools_path = pathlib.Path(__file__).parents[1].joinpath("devtools")
versions = tuple(f.name for f in devtools_path.iterdir() if f.is_dir())
latest = max(int(x[1:]) for x in versions)
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fallback logic assumes every directory name under devtools_path is v<digits> (int(x[1:])), but versions currently includes all directories. If a non-version directory exists (like latest), this will throw ValueError. Filter directory names to the expected pattern (or exclude known non-version folders) before parsing.

Copilot uses AI. Check for mistakes.
if allowed is True and not destination_folder:
raise ValueError("destination_folder is required when allowed=True")
if allowed is False and destination_folder:
raise ValueError("destination_folder should not be provided when allowed=False")
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_download_behavior doesn’t validate the case where allowed is None but destination_folder is provided. In that scenario the destination is silently ignored and downloadBehavior is sent as null. Consider raising a ValueError when destination_folder is set but allowed is not True, to avoid misconfiguration.

Suggested change
raise ValueError("destination_folder should not be provided when allowed=False")
raise ValueError("destination_folder should not be provided when allowed=False")
if allowed is None and destination_folder:
raise ValueError(
"destination_folder should not be provided when allowed is None (reset to browser default)"
)

Copilot uses AI. Check for mistakes.
FileUtils.rm_rf("#{lib_path}/common/devtools")
FileUtils.cp_r("#{bazel_bin}/.", lib_path, remove_destination: true)
else
bidi_src = "#{bazel_bin}/common/bidi"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-py Python Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants