Skip to content

[py][bidi] Route navigation commands through BiDi#17715

Open
shossain786 wants to merge 2 commits into
SeleniumHQ:trunkfrom
shossain786:py-bidi-navigation
Open

[py][bidi] Route navigation commands through BiDi#17715
shossain786 wants to merge 2 commits into
SeleniumHQ:trunkfrom
shossain786:py-bidi-navigation

Conversation

@shossain786

Copy link
Copy Markdown

🔗 Related Issues

Part of #13995

💥 What does this PR do?

Implements the Python binding for routing the Classic navigation commands through WebDriver BiDi when BiDi is enabled, following the pattern of the merged Ruby PR (#14094) and the in-review Java PR (#17232).

When the session has BiDi enabled (the remote end advertised a webSocketUrl), WebDriver now routes:

Classic BiDi
get(url) browsing_context.navigate(context, url, wait=<readiness>)
back() browsing_context.traverse_history(context, delta=-1)
forward() browsing_context.traverse_history(context, delta=1)
refresh() browsing_context.reload(context, wait=<readiness>)

When BiDi is not enabled, the existing Classic HTTP commands are used unchanged.

The pageLoadStrategy capability is mapped to a BiDi readiness state: normal → COMPLETE, eager → INTERACTIVE, none → NONE (defaulting to COMPLETE to preserve Classic get() blocking semantics). The current window handle is used as the browsing-context id.

🔧 Implementation Notes

The generated BrowsingContext module already exposes navigate / traverse_history / reload, so no generator changes were needed — the change is contained in webdriver.py plus a new test module.

Unlike Ruby (which introduced a dedicated BiDiBridge), the Python binding has a single WebDriver class, so this uses an inline if self._is_bidi_enabled() branch in the four navigation methods plus two small helpers (_is_bidi_enabled, _page_load_readiness). If maintainers prefer a separate bridge/mixin to keep the Classic and BiDi paths cleanly split, I'm happy to refactor — see Additional Considerations.

🤖 AI assistance

  • AI assisted (complete below)
    • Tool(s): Claude Code (Claude Opus)
    • What was generated: the navigation routing in webdriver.py, the two helper methods, and the new bidi_navigation_tests.py
    • I reviewed all AI output and can explain the change

💡 Additional Considerations

  • Design question: inline branching in WebDriver vs. a dedicated BiDi bridge/mixin. Open to either — guidance welcome.
  • The new tests run under the --bidi flag (auto-globbed into BIDI_TESTS via the *bidi*_tests.py pattern).

🔄 Types of changes

  • New feature (non-breaking change which adds functionality and tests!)

Route get/back/forward/refresh through the BiDi browsingContext module
when BiDi is enabled (webSocketUrl present in capabilities), falling back
to Classic HTTP commands otherwise. The pageLoadStrategy capability is
mapped to a BiDi readiness state (normal=COMPLETE, eager=INTERACTIVE,
none=NONE), and the current window handle is used as the context id.

Mirrors the merged Ruby implementation (SeleniumHQ#14094). Part of SeleniumHQ#13995.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jun 24, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Route Python navigation commands through WebDriver BiDi when enabled
✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

Description

• Route get/back/forward/refresh via BiDi browsingContext when webSocketUrl is present.
• Map pageLoadStrategy to BiDi readiness to preserve Classic navigation blocking semantics.
• Add BiDi-only navigation tests gated behind the --bidi test flag.
Diagram

graph TD
  A(("Client / Tests")) --> B(["Remote WebDriver"]) --> C{"BiDi enabled? (webSocketUrl)"}
  C -->|"Yes"| D["BiDi browsingContext"] --> E(["BiDi WebSocket"]) --> F(("Remote end"))
  C -->|"No"| G["Classic HTTP commands"] --> F

  subgraph Legend
    direction LR
    _actor(("Caller")) ~~~ _svc(["Component"]) ~~~ _dec{"Decision"}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Introduce a BiDi bridge/strategy object
  • ➕ Keeps WebDriver methods free of repetitive capability branching
  • ➕ Scales better as more Classic commands are routed through BiDi
  • ➕ Makes it easier to test routing/translation in isolation
  • ➖ Adds extra abstraction/indirection for a small number of methods today
  • ➖ Requires more wiring/ownership decisions (lifecycle, dependency injection)
2. Centralize routing with a single internal navigation helper
  • ➕ Reduces duplication without adding a new public-facing class
  • ➕ Keeps behavioral mapping (context id, readiness) in one place
  • ➖ Still keeps BiDi/Classic split inside WebDriver; may grow complex as coverage expands

Recommendation: The current inline branching is reasonable given only four methods are affected and matches the intended cross-language direction. If the roadmap includes routing additional Classic commands through BiDi soon, refactoring to a small internal bridge/strategy (or a shared navigation helper) would reduce duplication and keep BiDi translation logic more maintainable.

Files changed (2) +107 / -4

Enhancement (1) +39 / -4
webdriver.pyRoute navigation via BiDi browsingContext when available +39/-4

Route navigation via BiDi browsingContext when available

• Updates get/back/forward/refresh to call BiDi browsingContext commands when the session advertises webSocketUrl, falling back to Classic HTTP commands otherwise. Adds helpers to detect BiDi enablement and to map pageLoadStrategy to a BiDi ReadinessState (defaulting to COMPLETE to preserve Classic blocking behavior).

py/selenium/webdriver/remote/webdriver.py

Tests (1) +68 / -0
bidi_navigation_tests.pyAdd BiDi navigation routing coverage under --bidi +68/-0

Add BiDi navigation routing coverage under --bidi

• Introduces tests that validate classic navigation APIs behave correctly when routed through BiDi (get, back, forward, refresh). Also asserts the default readiness mapping is COMPLETE when pageLoadStrategy is not explicitly set.

py/test/selenium/webdriver/common/bidi_navigation_tests.py

@qodo-code-review

qodo-code-review Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (3) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 17 rules

Grey Divider


Action required

1. webSocketUrl type unchecked 🐞 Bug ≡ Correctness
Description
_is_bidi_enabled() treats any truthy caps['webSocketUrl'] as BiDi-enabled, so a boolean value
can incorrectly route navigation through BiDi and then fail when BiDi startup uses that value as a
websocket URL.
Code

py/selenium/webdriver/remote/webdriver.py[R736-742]

+    def _is_bidi_enabled(self) -> bool:
+        """Returns True if WebDriver BiDi is enabled for this session.
+
+        BiDi is enabled when the remote end advertised a ``webSocketUrl`` in
+        the session capabilities (i.e. the user requested it via options).
+        """
+        return bool(self.caps.get("webSocketUrl"))
Evidence
The Grid node code documents that some remotes return webSocketUrl as a boolean and therefore
checks for instanceof String before enabling BiDi; Python’s BiDi startup assumes the capability is
directly usable as a websocket URL, and the new _is_bidi_enabled() truthiness check can therefore
incorrectly enable BiDi routing.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1242-1250]
py/selenium/webdriver/remote/webdriver.py[1243-1256]
py/selenium/webdriver/remote/webdriver.py[736-754]
py/selenium/webdriver/common/options.py[46-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`WebDriver._is_bidi_enabled()` currently returns `True` for any truthy `webSocketUrl` capability value. Some remote ends can return `webSocketUrl` as a boolean (e.g., echoing the requested capability) rather than an actual websocket URL string. When this happens, navigation methods (`get/back/forward/refresh`) will take the BiDi path and eventually try to create a websocket connection using a non-string value.

### Issue Context
This PR newly gates core navigation behavior on `_is_bidi_enabled()`, so mis-detection now impacts common flows even if users don’t explicitly call BiDi APIs.

### Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[736-754]
- py/selenium/webdriver/remote/webdriver.py[1243-1256]

### What to change
- Make `_is_bidi_enabled()` return `True` only when `caps['webSocketUrl']` is a valid websocket URL **string** (and ideally validate it parses as `ws://` or `wss://`).
- Harden `_start_bidi()` to validate the type/value before constructing `WebSocketConnection`, and raise a clear `WebDriverException` if invalid.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. bidi_navigation_tests missing type hints 📘 Rule violation ✧ Quality
Description
New pytest test functions were added without parameter type annotations and without explicit `->
None` return annotations. This violates the requirement that new function signatures include full
type annotations.
Code

py/test/selenium/webdriver/common/bidi_navigation_tests.py[R29-68]

+def test_bidi_is_enabled(driver):
+    """The driver should report BiDi as enabled when web_socket_url is set."""
+    assert driver._is_bidi_enabled() is True
+
+
+def test_get_navigates_via_bidi(driver, pages):
+    """driver.get() should load the page through BiDi and block until complete."""
+    url = pages.url("simpleTest.html")
+    driver.get(url)
+    assert driver.current_url == url
+    assert "Hello WebDriver" in driver.title
+
+
+def test_back_and_forward_via_bidi(driver, pages):
+    """back()/forward() should traverse history through BiDi."""
+    first = pages.url("simpleTest.html")
+    second = pages.url("formPage.html")
+
+    driver.get(first)
+    driver.get(second)
+    assert driver.current_url == second
+
+    driver.back()
+    assert driver.current_url == first
+
+    driver.forward()
+    assert driver.current_url == second
+
+
+def test_refresh_via_bidi(driver, pages):
+    """refresh() should reload the current context through BiDi."""
+    url = pages.url("formPage.html")
+    driver.get(url)
+    driver.refresh()
+    assert driver.current_url == url
+
+
+def test_page_load_readiness_default_is_complete(driver):
+    """With no explicit pageLoadStrategy, readiness should default to COMPLETE."""
+    assert driver._page_load_readiness() == ReadinessState.COMPLETE
Evidence
PR Compliance ID 337802 requires explicit type annotations on all new function/method parameters and
return types. The added test functions define parameters like driver/pages without annotations
and omit -> None.

Rule 337802: Require type annotations on new function and method signatures
py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New test functions in `bidi_navigation_tests.py` are missing parameter and return type annotations (e.g., `driver`, `pages`, and `-> None`).

## Issue Context
Compliance requires explicit type annotations on newly added function/method signatures.

## Fix Focus Areas
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Back/forward blocking changed 🐞 Bug ☼ Reliability
Description
back()/forward() now call BiDi browsing_context.traverse_history() without any readiness/wait
handling, which can change Classic blocking semantics and introduce races where these methods return
before navigation completes and tests (or callers) observe stale state. This is visible in the new
BiDi navigation test that asserts current_url immediately after back()/forward(), making
outcomes timing-dependent if traversal is asynchronous.
Code

py/selenium/webdriver/remote/webdriver.py[R717-726]

+        if self._is_bidi_enabled():
+            self.browsing_context.traverse_history(context=self.current_window_handle, delta=-1)
+        else:
+            self.execute(Command.GO_BACK)

    def forward(self) -> None:
        """Goes one step forward in the browser history."""
-        self.execute(Command.GO_FORWARD)
+        if self._is_bidi_enabled():
+            self.browsing_context.traverse_history(context=self.current_window_handle, delta=1)
+        else:
Evidence
Existing repo tests that exercise BiDi traverse_history explicitly wait for the resulting page
state to change (e.g., waiting for a new title) after invoking it, which indicates that
traverse_history may not itself provide Classic-like blocking and needs additional
synchronization. The PR changes back()/forward() to call traverse_history directly, and the
new test_back_and_forward_via_bidi asserts driver.current_url immediately after these calls with
no explicit wait, demonstrating a mismatch with prior synchronization patterns and a concrete risk
that state assertions occur before navigation completion under BiDi traversal.

py/test/selenium/webdriver/common/bidi_browsing_context_tests.py[457-486]
py/selenium/webdriver/remote/webdriver.py[714-727]
py/test/selenium/webdriver/common/bidi_navigation_tests.py[42-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When BiDi is enabled, `WebDriver.back()` and `WebDriver.forward()` call `browsing_context.traverse_history(...)`, but unlike Classic semantics (and unlike other navigation APIs that honor `pageLoadStrategy` readiness), this call path does not include a readiness/wait step; as a result, `back()/forward()` may return before the navigation reaches the expected ready state and tests/callers may observe the old URL/title/document state.

## Issue Context
Existing BiDi tests in this repo treat `traverse_history` as requiring an explicit wait for the page state to change (e.g., waiting for a title update), suggesting the operation can complete asynchronously relative to observable page readiness. The newly added BiDi navigation test asserts `driver.current_url` immediately after `back()`/`forward()`, which becomes timing-dependent if `traverse_history` does not block until the target readiness is reached.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[714-755]
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[42-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. BiDi routing unasserted 🐞 Bug ⚙ Maintainability ⭐ New
Description
bidi_navigation_tests.py claims to test that classic navigation commands are routed through BiDi,
but the assertions only validate end-state behavior (current_url/title) and never verify that
Classic navigation commands (GET/GO_BACK/GO_FORWARD/REFRESH) weren’t used. This means a future
regression that routes back through Classic endpoints could still pass these tests as long as the
page ends up in the expected state.
Code

py/test/selenium/webdriver/common/bidi_navigation_tests.py[R34-63]

+def test_get_navigates_via_bidi(driver, pages):
+    """driver.get() should load the page through BiDi and block until complete."""
+    url = pages.url("simpleTest.html")
+    driver.get(url)
+    assert driver.current_url == url
+    assert "Hello WebDriver" in driver.title
+
+
+def test_back_and_forward_via_bidi(driver, pages):
+    """back()/forward() should traverse history through BiDi."""
+    first = pages.url("simpleTest.html")
+    second = pages.url("formPage.html")
+
+    driver.get(first)
+    driver.get(second)
+    assert driver.current_url == second
+
+    driver.back()
+    assert driver.current_url == first
+
+    driver.forward()
+    assert driver.current_url == second
+
+
+def test_refresh_via_bidi(driver, pages):
+    """refresh() should reload the current context through BiDi."""
+    url = pages.url("formPage.html")
+    driver.get(url)
+    driver.refresh()
+    assert driver.current_url == url
Evidence
The test module explicitly states it expects routing through BiDi, but its assertions only check
browser state after navigation. The --bidi fixture setup only requests/activates BiDi; it does not
prove which transport was used for navigation, and the WebDriver.get() implementation still has an
else-branch that uses Classic navigation, which would also satisfy the current assertions in many
cases.

py/test/selenium/webdriver/common/bidi_navigation_tests.py[18-63]
py/conftest.py[386-395]
py/selenium/webdriver/remote/webdriver.py[500-519]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new BiDi navigation tests verify page outcomes after `get/back/forward/refresh`, but they do not assert that the implementation actually routed these calls through BiDi (vs Classic HTTP navigation endpoints). This reduces the tests’ ability to detect routing regressions.

### Issue Context
With `--bidi`, the test harness sets `options.web_socket_url = True`, and the driver is expected to use BiDi navigation when the remote end returns a `webSocketUrl`.

### Fix Focus Areas
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[18-63]
- py/conftest.py[386-395]
- py/selenium/webdriver/remote/webdriver.py[500-519]

### Suggested fix approach
Update `bidi_navigation_tests.py` to *observe routing*, not just outcomes. For example:
1. Monkeypatch/spy on `driver.execute` (or `driver.command_executor.execute`) during each test to record invoked WebDriver `Command` values.
2. After calling `driver.get()` / `back()` / `forward()` / `refresh()`, assert that the relevant Classic commands (e.g., `Command.GET`, `Command.GO_BACK`, `Command.GO_FORWARD`, `Command.REFRESH`) were **not** executed.
3. Allow other Classic calls needed for assertions (e.g., `GET_CURRENT_URL`, `GET_TITLE`) so the test can still validate state.

This makes the tests actually validate “routed through BiDi” rather than only “ended up on the right page.”

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. _page_load_readiness missing Returns 📘 Rule violation ✧ Quality
Description
The new private helper methods have docstrings but omit Google-style Returns: sections despite
returning non-None values. This violates the docstring standard and makes the helpers harder to
understand and maintain.
Code

py/selenium/webdriver/remote/webdriver.py[R736-754]

+    def _is_bidi_enabled(self) -> bool:
+        """Returns True if WebDriver BiDi is enabled for this session.
+
+        BiDi is enabled when the remote end advertised a ``webSocketUrl`` in
+        the session capabilities (i.e. the user requested it via options).
+        """
+        return bool(self.caps.get("webSocketUrl"))
+
+    def _page_load_readiness(self) -> ReadinessState:
+        """Maps the session's ``pageLoadStrategy`` capability to a BiDi readiness state.
+
+        ``normal`` -> COMPLETE, ``eager`` -> INTERACTIVE, ``none`` -> NONE.
+        Defaults to COMPLETE to match Classic ``get()`` blocking semantics.
+        """
+        return {
+            "normal": ReadinessState.COMPLETE,
+            "eager": ReadinessState.INTERACTIVE,
+            "none": ReadinessState.NONE,
+        }.get(self.caps.get("pageLoadStrategy", "normal"), ReadinessState.COMPLETE)
Evidence
PR Compliance ID 337804 requires Google-style docstrings and mandates a Returns: section when a
function returns a value. The added docstrings for _is_bidi_enabled and _page_load_readiness
describe behavior but do not include Returns:.

Rule 337804: Enforce Google-style docstrings with Args/Returns/Raises sections
py/selenium/webdriver/remote/webdriver.py[736-754]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new helper methods `_is_bidi_enabled` and `_page_load_readiness` return non-`None` values but their docstrings omit a Google-style `Returns:` section.

## Issue Context
Compliance requires a `Returns:` section when a function returns a non-`None` value.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[736-754]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. bidi_navigation_tests docstrings missing Args 📘 Rule violation ✧ Quality
Description
New test functions include docstrings but do not follow Google-style Args: documentation for
parameters like driver and pages. This reduces clarity and violates the project docstring
standard for new public functions.
Code

py/test/selenium/webdriver/common/bidi_navigation_tests.py[R29-68]

+def test_bidi_is_enabled(driver):
+    """The driver should report BiDi as enabled when web_socket_url is set."""
+    assert driver._is_bidi_enabled() is True
+
+
+def test_get_navigates_via_bidi(driver, pages):
+    """driver.get() should load the page through BiDi and block until complete."""
+    url = pages.url("simpleTest.html")
+    driver.get(url)
+    assert driver.current_url == url
+    assert "Hello WebDriver" in driver.title
+
+
+def test_back_and_forward_via_bidi(driver, pages):
+    """back()/forward() should traverse history through BiDi."""
+    first = pages.url("simpleTest.html")
+    second = pages.url("formPage.html")
+
+    driver.get(first)
+    driver.get(second)
+    assert driver.current_url == second
+
+    driver.back()
+    assert driver.current_url == first
+
+    driver.forward()
+    assert driver.current_url == second
+
+
+def test_refresh_via_bidi(driver, pages):
+    """refresh() should reload the current context through BiDi."""
+    url = pages.url("formPage.html")
+    driver.get(url)
+    driver.refresh()
+    assert driver.current_url == url
+
+
+def test_page_load_readiness_default_is_complete(driver):
+    """With no explicit pageLoadStrategy, readiness should default to COMPLETE."""
+    assert driver._page_load_readiness() == ReadinessState.COMPLETE
Evidence
PR Compliance ID 337804 requires Google-style docstrings including an Args: section when
parameters exist. The added test functions have parameters but their docstrings do not document them
under Args:.

Rule 337804: Enforce Google-style docstrings with Args/Returns/Raises sections
py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Test function docstrings do not include Google-style `Args:` sections even though the functions accept parameters (e.g., `driver`, `pages`).

## Issue Context
Compliance requires Google-style docstrings with `Args:` when parameters beyond `self`/`cls` exist.

## Fix Focus Areas
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Previous review results

Review updated until commit 98827cc

Results up to commit e77e3b1


🐞 Bugs (2) 📘 Rule violations (3) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


Action required
1. bidi_navigation_tests missing type hints 📘 Rule violation ✧ Quality
Description
New pytest test functions were added without parameter type annotations and without explicit `->
None` return annotations. This violates the requirement that new function signatures include full
type annotations.
Code

py/test/selenium/webdriver/common/bidi_navigation_tests.py[R29-68]

+def test_bidi_is_enabled(driver):
+    """The driver should report BiDi as enabled when web_socket_url is set."""
+    assert driver._is_bidi_enabled() is True
+
+
+def test_get_navigates_via_bidi(driver, pages):
+    """driver.get() should load the page through BiDi and block until complete."""
+    url = pages.url("simpleTest.html")
+    driver.get(url)
+    assert driver.current_url == url
+    assert "Hello WebDriver" in driver.title
+
+
+def test_back_and_forward_via_bidi(driver, pages):
+    """back()/forward() should traverse history through BiDi."""
+    first = pages.url("simpleTest.html")
+    second = pages.url("formPage.html")
+
+    driver.get(first)
+    driver.get(second)
+    assert driver.current_url == second
+
+    driver.back()
+    assert driver.current_url == first
+
+    driver.forward()
+    assert driver.current_url == second
+
+
+def test_refresh_via_bidi(driver, pages):
+    """refresh() should reload the current context through BiDi."""
+    url = pages.url("formPage.html")
+    driver.get(url)
+    driver.refresh()
+    assert driver.current_url == url
+
+
+def test_page_load_readiness_default_is_complete(driver):
+    """With no explicit pageLoadStrategy, readiness should default to COMPLETE."""
+    assert driver._page_load_readiness() == ReadinessState.COMPLETE
Evidence
PR Compliance ID 337802 requires explicit type annotations on all new function/method parameters and
return types. The added test functions define parameters like driver/pages without annotations
and omit -> None.

Rule 337802: Require type annotations on new function and method signatures
py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New test functions in `bidi_navigation_tests.py` are missing parameter and return type annotations (e.g., `driver`, `pages`, and `-> None`).

## Issue Context
Compliance requires explicit type annotations on newly added function/method signatures.

## Fix Focus Areas
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. webSocketUrl type unchecked 🐞 Bug ≡ Correctness
Description
_is_bidi_enabled() treats any truthy caps['webSocketUrl'] as BiDi-enabled, so a boolean value
can incorrectly route navigation through BiDi and then fail when BiDi startup uses that value as a
websocket URL.
Code

py/selenium/webdriver/remote/webdriver.py[R736-742]

+    def _is_bidi_enabled(self) -> bool:
+        """Returns True if WebDriver BiDi is enabled for this session.
+
+        BiDi is enabled when the remote end advertised a ``webSocketUrl`` in
+        the session capabilities (i.e. the user requested it via options).
+        """
+        return bool(self.caps.get("webSocketUrl"))
Evidence
The Grid node code documents that some remotes return webSocketUrl as a boolean and therefore
checks for instanceof String before enabling BiDi; Python’s BiDi startup assumes the capability is
directly usable as a websocket URL, and the new _is_bidi_enabled() truthiness check can therefore
incorrectly enable BiDi routing.

java/src/org/openqa/selenium/grid/node/local/LocalNode.java[1242-1250]
py/selenium/webdriver/remote/webdriver.py[1243-1256]
py/selenium/webdriver/remote/webdriver.py[736-754]
py/selenium/webdriver/common/options.py[46-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`WebDriver._is_bidi_enabled()` currently returns `True` for any truthy `webSocketUrl` capability value. Some remote ends can return `webSocketUrl` as a boolean (e.g., echoing the requested capability) rather than an actual websocket URL string. When this happens, navigation methods (`get/back/forward/refresh`) will take the BiDi path and eventually try to create a websocket connection using a non-string value.

### Issue Context
This PR newly gates core navigation behavior on `_is_bidi_enabled()`, so mis-detection now impacts common flows even if users don’t explicitly call BiDi APIs.

### Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[736-754]
- py/selenium/webdriver/remote/webdriver.py[1243-1256]

### What to change
- Make `_is_bidi_enabled()` return `True` only when `caps['webSocketUrl']` is a valid websocket URL **string** (and ideally validate it parses as `ws://` or `wss://`).
- Harden `_start_bidi()` to validate the type/value before constructing `WebSocketConnection`, and raise a clear `WebDriverException` if invalid.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Back/forward blocking changed 🐞 Bug ☼ Reliability
Description
back()/forward() now call BiDi browsing_context.traverse_history() without any readiness/wait
handling, which can change Classic blocking semantics and introduce races where these methods return
before navigation completes and tests (or callers) observe stale state. This is visible in the new
BiDi navigation test that asserts current_url immediately after back()/forward(), making
outcomes timing-dependent if traversal is asynchronous.
Code

py/selenium/webdriver/remote/webdriver.py[R717-726]

+        if self._is_bidi_enabled():
+            self.browsing_context.traverse_history(context=self.current_window_handle, delta=-1)
+        else:
+            self.execute(Command.GO_BACK)

    def forward(self) -> None:
        """Goes one step forward in the browser history."""
-        self.execute(Command.GO_FORWARD)
+        if self._is_bidi_enabled():
+            self.browsing_context.traverse_history(context=self.current_window_handle, delta=1)
+        else:
Evidence
Existing repo tests that exercise BiDi traverse_history explicitly wait for the resulting page
state to change (e.g., waiting for a new title) after invoking it, which indicates that
traverse_history may not itself provide Classic-like blocking and needs additional
synchronization. The PR changes back()/forward() to call traverse_history directly, and the
new test_back_and_forward_via_bidi asserts driver.current_url immediately after these calls with
no explicit wait, demonstrating a mismatch with prior synchronization patterns and a concrete risk
that state assertions occur before navigation completion under BiDi traversal.

py/test/selenium/webdriver/common/bidi_browsing_context_tests.py[457-486]
py/selenium/webdriver/remote/webdriver.py[714-727]
py/test/selenium/webdriver/common/bidi_navigation_tests.py[42-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When BiDi is enabled, `WebDriver.back()` and `WebDriver.forward()` call `browsing_context.traverse_history(...)`, but unlike Classic semantics (and unlike other navigation APIs that honor `pageLoadStrategy` readiness), this call path does not include a readiness/wait step; as a result, `back()/forward()` may return before the navigation reaches the expected ready state and tests/callers may observe the old URL/title/document state.

## Issue Context
Existing BiDi tests in this repo treat `traverse_history` as requiring an explicit wait for the page state to change (e.g., waiting for a title update), suggesting the operation can complete asynchronously relative to observable page readiness. The newly added BiDi navigation test asserts `driver.current_url` immediately after `back()`/`forward()`, which becomes timing-dependent if `traverse_history` does not block until the target readiness is reached.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[714-755]
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[42-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
4. bidi_navigation_tests docstrings missing Args 📘 Rule violation ✧ Quality
Description
New test functions include docstrings but do not follow Google-style Args: documentation for
parameters like driver and pages. This reduces clarity and violates the project docstring
standard for new public functions.
Code

py/test/selenium/webdriver/common/bidi_navigation_tests.py[R29-68]

+def test_bidi_is_enabled(driver):
+    """The driver should report BiDi as enabled when web_socket_url is set."""
+    assert driver._is_bidi_enabled() is True
+
+
+def test_get_navigates_via_bidi(driver, pages):
+    """driver.get() should load the page through BiDi and block until complete."""
+    url = pages.url("simpleTest.html")
+    driver.get(url)
+    assert driver.current_url == url
+    assert "Hello WebDriver" in driver.title
+
+
+def test_back_and_forward_via_bidi(driver, pages):
+    """back()/forward() should traverse history through BiDi."""
+    first = pages.url("simpleTest.html")
+    second = pages.url("formPage.html")
+
+    driver.get(first)
+    driver.get(second)
+    assert driver.current_url == second
+
+    driver.back()
+    assert driver.current_url == first
+
+    driver.forward()
+    assert driver.current_url == second
+
+
+def test_refresh_via_bidi(driver, pages):
+    """refresh() should reload the current context through BiDi."""
+    url = pages.url("formPage.html")
+    driver.get(url)
+    driver.refresh()
+    assert driver.current_url == url
+
+
+def test_page_load_readiness_default_is_complete(driver):
+    """With no explicit pageLoadStrategy, readiness should default to COMPLETE."""
+    assert driver._page_load_readiness() == ReadinessState.COMPLETE
Evidence
PR Compliance ID 337804 requires Google-style docstrings including an Args: section when
parameters exist. The added test functions have parameters but their docstrings do not document them
under Args:.

Rule 337804: Enforce Google-style docstrings with Args/Returns/Raises sections
py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Test function docstrings do not include Google-style `Args:` sections even though the functions accept parameters (e.g., `driver`, `pages`).

## Issue Context
Compliance requires Google-style docstrings with `Args:` when parameters beyond `self`/`cls` exist.

## Fix Focus Areas
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. _page_load_readiness missing Returns 📘 Rule violation ✧ Quality
Description
The new private helper methods have docstrings but omit Google-style Returns: sections despite
returning non-None values. This violates the docstring standard and makes the helpers harder to
understand and maintain.
Code

py/selenium/webdriver/remote/webdriver.py[R736-754]

+    def _is_bidi_enabled(self) -> bool:
+        """Returns True if WebDriver BiDi is enabled for this session.
+
+        BiDi is enabled when the remote end advertised a ``webSocketUrl`` in
+        the session capabilities (i.e. the user requested it via options).
+        """
+        return bool(self.caps.get("webSocketUrl"))
+
+    def _page_load_readiness(self) -> ReadinessState:
+        """Maps the session's ``pageLoadStrategy`` capability to a BiDi readiness state.
+
+        ``normal`` -> COMPLETE, ``eager`` -> INTERACTIVE, ``none`` -> NONE.
+        Defaults to COMPLETE to match Classic ``get()`` blocking semantics.
+        """
+        return {
+            "normal": ReadinessState.COMPLETE,
+            "eager": ReadinessState.INTERACTIVE,
+            "none": ReadinessState.NONE,
+        }.get(self.caps.get("pageLoadStrategy", "normal"), ReadinessState.COMPLETE)
Evidence
PR Compliance ID 337804 requires Google-style docstrings and mandates a Returns: section when a
function returns a value. The added docstrings for _is_bidi_enabled and _page_load_readiness
describe behavior but do not include Returns:.

Rule 337804: Enforce Google-style docstrings with Args/Returns/Raises sections
py/selenium/webdriver/remote/webdriver.py[736-754]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new helper methods `_is_bidi_enabled` and `_page_load_readiness` return non-`None` values but their docstrings omit a Google-style `Returns:` section.

## Issue Context
Compliance requires a `Returns:` section when a function returns a non-`None` value.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[736-754]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment on lines +29 to +68
def test_bidi_is_enabled(driver):
"""The driver should report BiDi as enabled when web_socket_url is set."""
assert driver._is_bidi_enabled() is True


def test_get_navigates_via_bidi(driver, pages):
"""driver.get() should load the page through BiDi and block until complete."""
url = pages.url("simpleTest.html")
driver.get(url)
assert driver.current_url == url
assert "Hello WebDriver" in driver.title


def test_back_and_forward_via_bidi(driver, pages):
"""back()/forward() should traverse history through BiDi."""
first = pages.url("simpleTest.html")
second = pages.url("formPage.html")

driver.get(first)
driver.get(second)
assert driver.current_url == second

driver.back()
assert driver.current_url == first

driver.forward()
assert driver.current_url == second


def test_refresh_via_bidi(driver, pages):
"""refresh() should reload the current context through BiDi."""
url = pages.url("formPage.html")
driver.get(url)
driver.refresh()
assert driver.current_url == url


def test_page_load_readiness_default_is_complete(driver):
"""With no explicit pageLoadStrategy, readiness should default to COMPLETE."""
assert driver._page_load_readiness() == ReadinessState.COMPLETE

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. bidi_navigation_tests missing type hints 📘 Rule violation ✧ Quality

New pytest test functions were added without parameter type annotations and without explicit `->
None` return annotations. This violates the requirement that new function signatures include full
type annotations.
Agent Prompt
## Issue description
New test functions in `bidi_navigation_tests.py` are missing parameter and return type annotations (e.g., `driver`, `pages`, and `-> None`).

## Issue Context
Compliance requires explicit type annotations on newly added function/method signatures.

## Fix Focus Areas
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[29-68]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +736 to +742
def _is_bidi_enabled(self) -> bool:
"""Returns True if WebDriver BiDi is enabled for this session.
BiDi is enabled when the remote end advertised a ``webSocketUrl`` in
the session capabilities (i.e. the user requested it via options).
"""
return bool(self.caps.get("webSocketUrl"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

4. Websocketurl type unchecked 🐞 Bug ≡ Correctness

_is_bidi_enabled() treats any truthy caps['webSocketUrl'] as BiDi-enabled, so a boolean value
can incorrectly route navigation through BiDi and then fail when BiDi startup uses that value as a
websocket URL.
Agent Prompt
### Issue description
`WebDriver._is_bidi_enabled()` currently returns `True` for any truthy `webSocketUrl` capability value. Some remote ends can return `webSocketUrl` as a boolean (e.g., echoing the requested capability) rather than an actual websocket URL string. When this happens, navigation methods (`get/back/forward/refresh`) will take the BiDi path and eventually try to create a websocket connection using a non-string value.

### Issue Context
This PR newly gates core navigation behavior on `_is_bidi_enabled()`, so mis-detection now impacts common flows even if users don’t explicitly call BiDi APIs.

### Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[736-754]
- py/selenium/webdriver/remote/webdriver.py[1243-1256]

### What to change
- Make `_is_bidi_enabled()` return `True` only when `caps['webSocketUrl']` is a valid websocket URL **string** (and ideally validate it parses as `ws://` or `wss://`).
- Harden `_start_bidi()` to validate the type/value before constructing `WebSocketConnection`, and raise a clear `WebDriverException` if invalid.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +717 to +726
if self._is_bidi_enabled():
self.browsing_context.traverse_history(context=self.current_window_handle, delta=-1)
else:
self.execute(Command.GO_BACK)

def forward(self) -> None:
"""Goes one step forward in the browser history."""
self.execute(Command.GO_FORWARD)
if self._is_bidi_enabled():
self.browsing_context.traverse_history(context=self.current_window_handle, delta=1)
else:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

5. Back/forward blocking changed 🐞 Bug ☼ Reliability

back()/forward() now call BiDi browsing_context.traverse_history() without any readiness/wait
handling, which can change Classic blocking semantics and introduce races where these methods return
before navigation completes and tests (or callers) observe stale state. This is visible in the new
BiDi navigation test that asserts current_url immediately after back()/forward(), making
outcomes timing-dependent if traversal is asynchronous.
Agent Prompt
## Issue description
When BiDi is enabled, `WebDriver.back()` and `WebDriver.forward()` call `browsing_context.traverse_history(...)`, but unlike Classic semantics (and unlike other navigation APIs that honor `pageLoadStrategy` readiness), this call path does not include a readiness/wait step; as a result, `back()/forward()` may return before the navigation reaches the expected ready state and tests/callers may observe the old URL/title/document state.

## Issue Context
Existing BiDi tests in this repo treat `traverse_history` as requiring an explicit wait for the page state to change (e.g., waiting for a title update), suggesting the operation can complete asynchronously relative to observable page readiness. The newly added BiDi navigation test asserts `driver.current_url` immediately after `back()`/`forward()`, which becomes timing-dependent if `traverse_history` does not block until the target readiness is reached.

## Fix Focus Areas
- py/selenium/webdriver/remote/webdriver.py[714-755]
- py/test/selenium/webdriver/common/bidi_navigation_tests.py[42-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 98827cc

else:
self.execute(Command.REFRESH)

def _is_bidi_enabled(self) -> bool:

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.

I'm not sure this method is needed. Elsewhere in this class, we already use self._websocket_connection to check for BiDi (it is None when BiDi is not enabled).

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.

4 participants