From e77e3b1fc3d9c874e5b17963033a89b744a61cff Mon Sep 17 00:00:00 2001 From: Mohammad Saddam Hossain Date: Wed, 24 Jun 2026 08:35:23 +0530 Subject: [PATCH] [py][bidi] Route navigation commands through BiDi 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 (#14094). Part of #13995. Co-Authored-By: Claude Opus 4.8 --- py/selenium/webdriver/remote/webdriver.py | 43 ++++++++++-- .../webdriver/common/bidi_navigation_tests.py | 68 +++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 py/test/selenium/webdriver/common/bidi_navigation_tests.py diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 232637c313ee4..19d630be82067 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -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 @@ -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: @@ -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: + 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: + """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) def get_cookies(self) -> list[dict]: """Get all cookies visible to the current WebDriver instance. diff --git a/py/test/selenium/webdriver/common/bidi_navigation_tests.py b/py/test/selenium/webdriver/common/bidi_navigation_tests.py new file mode 100644 index 0000000000000..8596ce36114c9 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_navigation_tests.py @@ -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