From 78ce161e059455ffaeba3fcd319ed194939412a7 Mon Sep 17 00:00:00 2001 From: Krishna Date: Mon, 16 Mar 2026 22:54:35 -0400 Subject: [PATCH 1/3] [java][bidi] Route navigation commands through BiDi Implements get(), back(), forward(), and refresh() via BrowsingContext when webSocketUrl capability is present, falling back to Classic otherwise. Maps pageLoadStrategy to ReadinessState: normal->COMPLETE, eager->INTERACTIVE, none->NONE. Adds integration tests covering all navigation commands with BiDi enabled. Fixes #13995 --- .../org/openqa/selenium/remote/BUILD.bazel | 1 + .../selenium/remote/RemoteWebDriver.java | 38 +++++- .../browsingcontext/BiDiNavigationTest.java | 113 ++++++++++++++++++ 3 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 java/test/org/openqa/selenium/bidi/browsingcontext/BiDiNavigationTest.java diff --git a/java/src/org/openqa/selenium/remote/BUILD.bazel b/java/src/org/openqa/selenium/remote/BUILD.bazel index 71bfde2e5d0b5..4fc796ddcb3d7 100644 --- a/java/src/org/openqa/selenium/remote/BUILD.bazel +++ b/java/src/org/openqa/selenium/remote/BUILD.bazel @@ -63,6 +63,7 @@ java_library( deps = [ "//java/src/org/openqa/selenium:core", "//java/src/org/openqa/selenium/bidi", + "//java/src/org/openqa/selenium/bidi/browsingcontext", "//java/src/org/openqa/selenium/bidi/log", "//java/src/org/openqa/selenium/bidi/module", "//java/src/org/openqa/selenium/bidi/network", diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index 0ed6e13909865..beb11967e782b 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -77,6 +77,8 @@ import org.openqa.selenium.WindowType; import org.openqa.selenium.bidi.BiDi; import org.openqa.selenium.bidi.HasBiDi; +import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; +import org.openqa.selenium.bidi.browsingcontext.ReadinessState; import org.openqa.selenium.devtools.DevTools; import org.openqa.selenium.devtools.HasDevTools; import org.openqa.selenium.federatedcredentialmanagement.FederatedCredentialManagementDialog; @@ -370,7 +372,22 @@ public Capabilities getCapabilities() { @Override public void get(String url) { - execute(DriverCommand.GET(url)); + if (getCapabilities().getCapability("webSocketUrl") != null) { + new BrowsingContext(this, getWindowHandle()) + .navigate(url, getReadinessState()); + } else { + execute(DriverCommand.GET(url)); + } + } + + private ReadinessState getReadinessState() { + Object strategy = getCapabilities().getCapability(CapabilityType.PAGE_LOAD_STRATEGY); + if ("eager".equals(strategy)) { + return ReadinessState.INTERACTIVE; + } else if ("none".equals(strategy)) { + return ReadinessState.NONE; + } + return ReadinessState.COMPLETE; } @Override @@ -1214,12 +1231,20 @@ private class RemoteNavigation implements Navigation { @Override public void back() { - execute(DriverCommand.GO_BACK); + if (getCapabilities().getCapability("webSocketUrl") != null) { + new BrowsingContext(RemoteWebDriver.this, getWindowHandle()).back(); + } else { + execute(DriverCommand.GO_BACK); + } } @Override public void forward() { - execute(DriverCommand.GO_FORWARD); + if (getCapabilities().getCapability("webSocketUrl") != null) { + new BrowsingContext(RemoteWebDriver.this, getWindowHandle()).forward(); + } else { + execute(DriverCommand.GO_FORWARD); + } } @Override @@ -1234,7 +1259,12 @@ public void to(URL url) { @Override public void refresh() { - execute(DriverCommand.REFRESH); + if (getCapabilities().getCapability("webSocketUrl") != null) { + new BrowsingContext(RemoteWebDriver.this, getWindowHandle()) + .reload(getReadinessState()); + } else { + execute(DriverCommand.REFRESH); + } } } diff --git a/java/test/org/openqa/selenium/bidi/browsingcontext/BiDiNavigationTest.java b/java/test/org/openqa/selenium/bidi/browsingcontext/BiDiNavigationTest.java new file mode 100644 index 0000000000000..fc1febe132db1 --- /dev/null +++ b/java/test/org/openqa/selenium/bidi/browsingcontext/BiDiNavigationTest.java @@ -0,0 +1,113 @@ +// 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. + +package org.openqa.selenium.bidi.browsingcontext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openqa.selenium.support.ui.ExpectedConditions.titleIs; +import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated; +import static org.openqa.selenium.testing.drivers.Browser.EDGE; + +import java.net.MalformedURLException; +import java.net.URL; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.testing.JupiterTestBase; +import org.openqa.selenium.testing.NeedsFreshDriver; +import org.openqa.selenium.testing.NotYetImplemented; + +class BiDiNavigationTest extends JupiterTestBase { + + @Test + @NeedsFreshDriver + void driverGetNavigatesToUrlViaBiDi() { + String url = appServer.whereIs("formPage.html"); + driver.get(url); + assertThat(driver.getCurrentUrl()).contains("formPage.html"); + assertThat(driver.getTitle()).isEqualTo("We Leave From Here"); + } + + @Test + @NeedsFreshDriver + void driverGetNavigatesToSecondUrlViaBiDi() { + driver.get(pages.formPage); + String url = appServer.whereIs("simpleTest.html"); + driver.get(url); + assertThat(driver.getCurrentUrl()).contains("simpleTest.html"); + } + + @Test + @NeedsFreshDriver + @NotYetImplemented(EDGE) + void navigateToStringUrlViaNavigationTo() { + String url = appServer.whereIs("formPage.html"); + driver.navigate().to(url); + assertThat(driver.getCurrentUrl()).contains("formPage.html"); + assertThat(driver.getTitle()).isEqualTo("We Leave From Here"); + } + + @Test + @NeedsFreshDriver + @NotYetImplemented(EDGE) + void navigateToUrlObjectViaNavigationTo() throws MalformedURLException { + URL url = new URL(appServer.whereIs("formPage.html")); + driver.navigate().to(url); + assertThat(driver.getCurrentUrl()).contains("formPage.html"); + assertThat(driver.getTitle()).isEqualTo("We Leave From Here"); + } + + @Test + @NeedsFreshDriver + @NotYetImplemented(EDGE) + void navigateBackTraversesHistory() { + driver.get(pages.formPage); + wait.until(visibilityOfElementLocated(By.id("imageButton"))).submit(); + wait.until(titleIs("We Arrive Here")); + + driver.navigate().back(); + wait.until(titleIs("We Leave From Here")); + } + + @Test + @NeedsFreshDriver + @NotYetImplemented(EDGE) + void navigateForwardTraversesHistory() { + driver.get(pages.formPage); + wait.until(visibilityOfElementLocated(By.id("imageButton"))).submit(); + wait.until(titleIs("We Arrive Here")); + + driver.navigate().back(); + wait.until(titleIs("We Leave From Here")); + + driver.navigate().forward(); + wait.until(titleIs("We Arrive Here")); + } + + @Test + @NeedsFreshDriver + @NotYetImplemented(EDGE) + void refreshReloadsCurrentPage() { + String url = appServer.whereIs("formPage.html"); + driver.get(url); + assertThat(driver.getTitle()).isEqualTo("We Leave From Here"); + + driver.navigate().refresh(); + + assertThat(driver.getCurrentUrl()).contains("formPage.html"); + assertThat(driver.getTitle()).isEqualTo("We Leave From Here"); + } +} From 50a7aac3f17b74014df2f3beecc154c204165021 Mon Sep 17 00:00:00 2001 From: Krishna Date: Wed, 18 Mar 2026 08:58:52 -0400 Subject: [PATCH 2/3] [java][bidi] Fix BiDi detection and readiness state mapping - Guard BiDi path with instanceof HasBiDi check to prevent IllegalArgumentException on plain RemoteWebDriver instances - Check webSocketUrl instanceof String (not just != null) to avoid false positive on Boolean.TRUE during session setup - Handle PageLoadStrategy as both enum and String when mapping to ReadinessState to fix eager/none being silently ignored Addresses review feedback on #13995 --- .../selenium/remote/RemoteWebDriver.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index beb11967e782b..4e15431059cb3 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -61,6 +61,7 @@ import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.NoAlertPresentException; +import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.NoSuchFrameException; import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.OutputType; @@ -372,7 +373,7 @@ public Capabilities getCapabilities() { @Override public void get(String url) { - if (getCapabilities().getCapability("webSocketUrl") != null) { + if (isBiDiEnabled()) { new BrowsingContext(this, getWindowHandle()) .navigate(url, getReadinessState()); } else { @@ -380,11 +381,24 @@ public void get(String url) { } } + // BiDi is active when the driver implements HasBiDi and the session returned a WebSocket URL + // (a String), not just the boolean request capability that was sent at session creation. + private boolean isBiDiEnabled() { + return this instanceof HasBiDi + && getCapabilities().getCapability("webSocketUrl") instanceof String; + } + private ReadinessState getReadinessState() { - Object strategy = getCapabilities().getCapability(CapabilityType.PAGE_LOAD_STRATEGY); - if ("eager".equals(strategy)) { + Object raw = getCapabilities().getCapability(CapabilityType.PAGE_LOAD_STRATEGY); + // The capability may be a PageLoadStrategy enum (set locally) or a String (deserialized from + // JSON), so normalise to the enum via toString() before comparing. + PageLoadStrategy strategy = + raw instanceof PageLoadStrategy + ? (PageLoadStrategy) raw + : PageLoadStrategy.fromString(raw == null ? null : raw.toString()); + if (PageLoadStrategy.EAGER.equals(strategy)) { return ReadinessState.INTERACTIVE; - } else if ("none".equals(strategy)) { + } else if (PageLoadStrategy.NONE.equals(strategy)) { return ReadinessState.NONE; } return ReadinessState.COMPLETE; @@ -1231,7 +1245,7 @@ private class RemoteNavigation implements Navigation { @Override public void back() { - if (getCapabilities().getCapability("webSocketUrl") != null) { + if (isBiDiEnabled()) { new BrowsingContext(RemoteWebDriver.this, getWindowHandle()).back(); } else { execute(DriverCommand.GO_BACK); @@ -1240,7 +1254,7 @@ public void back() { @Override public void forward() { - if (getCapabilities().getCapability("webSocketUrl") != null) { + if (isBiDiEnabled()) { new BrowsingContext(RemoteWebDriver.this, getWindowHandle()).forward(); } else { execute(DriverCommand.GO_FORWARD); @@ -1259,7 +1273,7 @@ public void to(URL url) { @Override public void refresh() { - if (getCapabilities().getCapability("webSocketUrl") != null) { + if (isBiDiEnabled()) { new BrowsingContext(RemoteWebDriver.this, getWindowHandle()) .reload(getReadinessState()); } else { From b33f9a189f78c6d07b41e78ca284cd8e7283d6b9 Mon Sep 17 00:00:00 2001 From: Krishna Date: Wed, 18 Mar 2026 14:58:50 -0400 Subject: [PATCH 3/3] [java][bidi] Fix code formatting --- java/src/org/openqa/selenium/remote/RemoteWebDriver.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java index 4e15431059cb3..f921af004c8d7 100644 --- a/java/src/org/openqa/selenium/remote/RemoteWebDriver.java +++ b/java/src/org/openqa/selenium/remote/RemoteWebDriver.java @@ -61,10 +61,10 @@ import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.NoAlertPresentException; -import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.NoSuchFrameException; import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.OutputType; +import org.openqa.selenium.PageLoadStrategy; import org.openqa.selenium.Pdf; import org.openqa.selenium.Platform; import org.openqa.selenium.Point; @@ -374,8 +374,7 @@ public Capabilities getCapabilities() { @Override public void get(String url) { if (isBiDiEnabled()) { - new BrowsingContext(this, getWindowHandle()) - .navigate(url, getReadinessState()); + new BrowsingContext(this, getWindowHandle()).navigate(url, getReadinessState()); } else { execute(DriverCommand.GET(url)); } @@ -1274,8 +1273,7 @@ public void to(URL url) { @Override public void refresh() { if (isBiDiEnabled()) { - new BrowsingContext(RemoteWebDriver.this, getWindowHandle()) - .reload(getReadinessState()); + new BrowsingContext(RemoteWebDriver.this, getWindowHandle()).reload(getReadinessState()); } else { execute(DriverCommand.REFRESH); }