Conversation
9947809 to
38e222b
Compare
38e222b to
79aa226
Compare
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
c7311e8 to
8ea2d91
Compare
8ea2d91 to
1fed8ae
Compare
There was a problem hiding this comment.
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()callimport_cdp()which importsselenium.webdriver.common.bidi.cdp, but this PR deletespy/selenium/webdriver/common/bidi/cdp.py. That will cause aModuleNotFoundErrorthe first time devtools/BiDi connection code runs. Either keep/replace thecdpmodule (and updateimport_cdp()accordingly) or remove these code paths.
769bc91 to
3ee0804
Compare
…revert unrelated requirements changes
… the window to the size we want
91c9091 to
5f07cf5
Compare
There was a problem hiding this comment.
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
_pendingEventsis declared asChannel<EventItem>, butEventItemis not defined anywhere in this file (andEnqueueEventwritesPendingEvent). This will not compile; either change the channel back toChannel<PendingEvent>or introduce/rename theEventItemtype 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 toevt, but the body still referencesresult.Method/result.EventArgs. This is a compile-time error; update the references to useevt(and ensureevthas 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);
| def transform_download_params( | ||
| allowed: bool | None, | ||
| destination_folder: str | None, | ||
| ) -> dict[str, Any] | None: |
There was a problem hiding this comment.
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.
| private async Task ProcessEventsAsync() | ||
| { |
There was a problem hiding this comment.
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).
| # 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()) |
There was a problem hiding this comment.
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.
| 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() | |
| ) |
| self.subscribe_to_event(event_config.bidi_event, contexts) | ||
| self.add_callback_to_tracking(event_config.bidi_event, callback_id) |
There was a problem hiding this comment.
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.
| 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 |
| return new Subscription(subscribeResult.Subscription, this, eventHandler); | ||
| } | ||
| catch | ||
| { | ||
| registration.RemoveHandler(eventHandler); | ||
| throw; | ||
| } |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| 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") |
There was a problem hiding this comment.
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.
| 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)" | |
| ) |
| FileUtils.rm_rf("#{lib_path}/common/devtools") | ||
| FileUtils.cp_r("#{bazel_bin}/.", lib_path, remove_destination: true) | ||
| else | ||
| bidi_src = "#{bazel_bin}/common/bidi" |
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.