You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Python counterpart of #17855, which did this for Ruby.
💥 What does this PR do?
Note that this is for the BiDi implementation generated from the schema not the code currently in use off the driver.
BiDi wire errors now raise a typed exception instead of a bare WebDriverException: codes the classic error handler already types keep that class, and BiDi-only codes get their own.
Backwards compatible with classic error handling — except NoSuchElementException catches a BiDi failure and a classic one alike.
🔧 Implementation Notes
The exception classes are generated from the schema's ErrorCode enum as real class statements, so they type-check, autocomplete and document like every other exception in the bindings.
Reconciling with the classic exceptions reads errorhandler's own ErrorCode/ExceptionMapping tables at generation time rather than keeping a second copy, so a class the handler retypes later follows on the next build — this is why the generator now depends on :remote.
Codes the handler resolves to bare WebDriverException (unknown error, unsupported operation) get a declared subclass instead: strictly more specific, still caught by anyone catching the base.
🤖 AI assistance
No substantial AI assistance used
AI assisted (complete below)
Tool(s): Claude Code (Opus 5)
What was generated: the generator change, the tests, and this description
I reviewed all AI output and can explain the change
💡 Additional Considerations
Alternatives considered:
Minting at import with type() behind a module __getattr__, which is what the first pass did — mypy resolves any name off such a module, so a misspelled exception name imports clean and every class narrows only to type[WebDriverException], and the classes never reach Sphinx autodoc or dir(). It also let the generated table and the runtime surface disagree: NoSuchAlertException and UnableToCaptureScreenException were listed but raised AttributeError, because those two codes resolve to NoAlertPresentException and ScreenshotException.
Hand-writing the classes in selenium/common/exceptions.py — they would be public and could carry real docstrings and a hierarchy, but the list drifts from the schema on every spec bump and needs a coverage test to stay honest.
Generating a .pyi stub next to the minted classes — cannot be made correct from the schema alone: a stub built from the name table would declare the two classes above, which do not exist, and would type the ten shared codes as distinct from their classic counterparts, breaking except matching.
Two-stage generation, keeping the schema generator selenium-free and adding a second selenium-aware tool — same output for twice the build wiring, and generation depends on errorhandler either way, so the separation is organizational rather than real.
🔄 Types of changes
New feature (non-breaking change which adds functionality and tests!)
The following are alternative approaches to this PR:
1. Create classes dynamically at import time
➕ Avoids generated class declarations
➕ Keeps runtime mapping compact
➖ Weakens static typing and autocomplete
➖ Hides classes from autodoc and dir()
➖ Can let declared names diverge from raised classes
2. Hand-write public exception classes
➕ Allows curated documentation and inheritance
➕ Keeps exception definitions straightforward
➖ Can drift whenever the BiDi schema changes
➖ Requires separate synchronization coverage
3. Generate type stubs for dynamic classes
➕ Could improve editor and type-checker visibility
➕ Retains dynamic runtime creation
➖ Cannot accurately model classic exception aliases from schema alone
➖ Risks stubs declaring classes absent at runtime
Recommendation: The PR's generation-time reconciliation is the best approach: it emits real, discoverable classes while deriving shared mappings from the classic handler's authoritative tables. This avoids schema drift and preserves cross-protocol exception compatibility without dynamic-runtime or stub inconsistencies.
Files changed (5) +139 / -11
Enhancement (1) +83 / -2
generate_bidi_protocol.pyGenerate typed BiDi exception classes and lookup table+83/-2
Generate typed BiDi exception classes and lookup table
• Generates concrete exception classes for BiDi-only or otherwise untyped schema codes while importing existing classic exception classes for shared codes. Adds a typed lookup with a WebDriverException fallback and includes errors.py in all generated outputs.
bidi_transport_tests.pyCover typed BiDi error handling and stack traces+37/-4
Cover typed BiDi error handling and stack traces
• Extends the test connection to emit stack traces and verifies classic mappings, BiDi-specific classes, unknown-code fallback, message fallback, and stack-trace propagation.
BUILD.bazelAdd classic error mappings to generator dependencies+4/-0
Add classic error mappings to generator dependencies
• Adds the remote WebDriver target to both BiDi protocol generator binaries. This allows generation to inspect the classic error handler's code-to-exception tables.
When a remote message is present, _error now removes the BiDi wire error code from the exception
message, changing the existing observable error text from <code>: <message> to only <message>.
Callers that inspect or match exception messages can break on upgrade despite typed exceptions being
otherwise compatible.
+ # The class carries the code, so the message need not repeat it — except where the+ # remote sent no message, which would otherwise leave nothing to read.+ message = reply.get("message") or code
Evidence
PR Compliance ID 1 requires existing public behavior to remain compatible. The changed _error
implementation constructs the exception from reply.get("message") or code, so a reply containing
both error and message no longer includes the wire code in the exception message; the PR's
modified unit expectation at bidi_transport_tests.py[121-125] confirms this intentional output
change.
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
## Issue description
The typed-exception implementation removes the BiDi wire error code from exception messages whenever the remote supplies a message, breaking the prior observable `<code>: <message>` format.
## Issue Context
Keep the new typed exception and stacktrace behavior, but preserve the existing message text so callers matching or recording errors remain compatible. Update the focused test to continue asserting the prior format.
## Fix Focus Areas
- py/selenium/webdriver/common/_bidi/transport.py[61-66]
- py/test/unit/selenium/webdriver/common/bidi_transport_tests.py[121-125]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Context sources
Review mode: ⚖️ Balanced: This changes generated exception mapping, build-time dependencies, and BiDi runtime error propagation across multiple files; it has meaningful compatibility and API behavior risk, but not enough independent defect density to justify extended review.
Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
B-buildIncludes scripting, bazel and CI integrationsC-pyPython Bindings
3 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🔗 Related Issues
Python counterpart of #17855, which did this for Ruby.
💥 What does this PR do?
Note that this is for the BiDi implementation generated from the schema not the code currently in use off the driver.
WebDriverException: codes the classic error handler already types keep that class, and BiDi-only codes get their own.except NoSuchElementExceptioncatches a BiDi failure and a classic one alike.🔧 Implementation Notes
ErrorCodeenum as real class statements, so they type-check, autocomplete and document like every other exception in the bindings.errorhandler's ownErrorCode/ExceptionMappingtables at generation time rather than keeping a second copy, so a class the handler retypes later follows on the next build — this is why the generator now depends on:remote.WebDriverException(unknown error,unsupported operation) get a declared subclass instead: strictly more specific, still caught by anyone catching the base.🤖 AI assistance
💡 Additional Considerations
type()behind a module__getattr__, which is what the first pass did — mypy resolves any name off such a module, so a misspelled exception name imports clean and every class narrows only totype[WebDriverException], and the classes never reach Sphinx autodoc ordir(). It also let the generated table and the runtime surface disagree:NoSuchAlertExceptionandUnableToCaptureScreenExceptionwere listed but raisedAttributeError, because those two codes resolve toNoAlertPresentExceptionandScreenshotException.selenium/common/exceptions.py— they would be public and could carry real docstrings and a hierarchy, but the list drifts from the schema on every spec bump and needs a coverage test to stay honest..pyistub next to the minted classes — cannot be made correct from the schema alone: a stub built from the name table would declare the two classes above, which do not exist, and would type the ten shared codes as distinct from their classic counterparts, breakingexceptmatching.errorhandlereither way, so the separation is organizational rather than real.🔄 Types of changes