Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions py/selenium/webdriver/remote/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
)
from selenium.webdriver.common.bidi.browser import Browser
from selenium.webdriver.common.bidi.browsing_context import BrowsingContext
from selenium.webdriver.common.bidi.browsing_context import ReadinessState
from selenium.webdriver.common.bidi.emulation import Emulation
from selenium.webdriver.common.bidi.input import Input
from selenium.webdriver.common.bidi.network import Network
Expand Down Expand Up @@ -509,7 +510,12 @@ def get(self, url: str) -> None:
Example:
`driver.get("https://example.com")`
"""
self.execute(Command.GET, {"url": url})
if self._is_bidi_enabled():
self.browsing_context.navigate(
context=self.current_window_handle, url=url, wait=self._page_load_readiness()
)
else:
self.execute(Command.GET, {"url": url})

@property
def title(self) -> str:
Expand Down Expand Up @@ -708,15 +714,44 @@ def switch_to(self) -> SwitchTo:
# Navigation
def back(self) -> None:
"""Goes one step backward in the browser history."""
self.execute(Command.GO_BACK)
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:
Comment on lines +717 to +726

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

self.execute(Command.GO_FORWARD)

def refresh(self) -> None:
"""Refreshes the current page."""
self.execute(Command.REFRESH)
if self._is_bidi_enabled():
self.browsing_context.reload(context=self.current_window_handle, wait=self._page_load_readiness())
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).

"""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"))
Comment on lines +736 to +742

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


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)

def get_cookies(self) -> list[dict]:
"""Get all cookies visible to the current WebDriver instance.
Expand Down
68 changes: 68 additions & 0 deletions py/test/selenium/webdriver/common/bidi_navigation_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""Tests that the classic navigation commands are routed through WebDriver BiDi.

These run under the ``--bidi`` pytest flag, which sets ``web_socket_url = True``
on the options. With BiDi enabled, ``driver.get()``, ``back()``, ``forward()``
and ``refresh()`` are expected to go through the BiDi ``browsingContext`` module
instead of the Classic HTTP endpoints, while preserving the same behaviour.
"""

from selenium.webdriver.common.bidi.browsing_context import ReadinessState


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
Comment on lines +29 to +68

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