From 413a1308a9f5f4f4855a9829e2ff4b96da9648cf Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 20 Mar 2025 17:34:50 +0530 Subject: [PATCH 1/5] add check for ``--connect-existing` --- .../selenium/firefox/GeckoDriverService.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/java/src/org/openqa/selenium/firefox/GeckoDriverService.java b/java/src/org/openqa/selenium/firefox/GeckoDriverService.java index 7602085d506f6..3b8ac95b26bd9 100644 --- a/java/src/org/openqa/selenium/firefox/GeckoDriverService.java +++ b/java/src/org/openqa/selenium/firefox/GeckoDriverService.java @@ -222,13 +222,26 @@ protected List createArgs() { List args = new ArrayList<>(); args.add(String.format(Locale.ROOT, "--port=%d", getPort())); - int wsPort = PortProber.findFreePort(); - args.add(String.format("--websocket-port=%d", wsPort)); + // Check if we're connecting to an existing Firefox instance + boolean connectExisting = false; + for (String arg : args) { + if (arg.contains("--connect-existing")) { + connectExisting = true; + break; + } + } - args.add("--allow-origins"); - args.add(String.format("http://127.0.0.1:%d", wsPort)); - args.add(String.format("http://localhost:%d", wsPort)); - args.add(String.format("http://[::1]:%d", wsPort)); + // Only allocate a free port for the websocket when not connecting to an existing instance + // This avoids conflicts when multiple Firefox instances are started + if (!connectExisting) { + int wsPort = PortProber.findFreePort(); + args.add(String.format("--websocket-port=%d", wsPort)); + + args.add("--allow-origins"); + args.add(String.format("http://127.0.0.1:%d", wsPort)); + args.add(String.format("http://localhost:%d", wsPort)); + args.add(String.format("http://[::1]:%d", wsPort)); + } if (logLevel != null) { args.add("--log"); From f1eac5fe26060e3ced2fef28ec3ee9ce7fc88fac Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 20 Mar 2025 17:36:43 +0530 Subject: [PATCH 2/5] add firefox test for BiDi multiple sessions --- .../firefox/FirefoxDriverConcurrentTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java index 3b92e5b4e9008..6e5997e08b9d9 100644 --- a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java +++ b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java @@ -24,6 +24,7 @@ import java.util.Random; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; +import org.openqa.selenium.bidi.BiDi; import org.openqa.selenium.ParallelTestRunner; import org.openqa.selenium.ParallelTestRunner.Worker; import org.openqa.selenium.WebDriver; @@ -164,4 +165,35 @@ void shouldBeAbleToUseTheSameProfileMoreThanOnce() { if (two != null) two.quit(); } } + + @Test + void multipleFirefoxInstancesWithBiDiEnabledCanRunSimultaneously() { + // Create two Firefox instances with BiDi enabled + FirefoxOptions options1 = new FirefoxOptions().enableBiDi(); + FirefoxOptions options2 = new FirefoxOptions().enableBiDi(); + + FirefoxDriver driver1; + FirefoxDriver driver2; + + // Start the first Firefox instance + driver1 = new FirefoxDriver(options1); + BiDi biDi1 = driver1.getBiDi(); + assertThat(biDi1).isNotNull(); + + // Extract the BiDi websocket URL and port for the first instance + String webSocketUrl1 = (String) driver1.getCapabilities().getCapability("webSocketUrl"); + String port1 = webSocketUrl1.replaceAll("^ws://[^:]+:(\\d+)/.*$", "$1"); + + // Start the second Firefox instance + driver2 = new FirefoxDriver(options2); + BiDi biDi2 = driver2.getBiDi(); + assertThat(biDi2).isNotNull(); + + // Extract the BiDi websocket URL and port for the second instance + String webSocketUrl2 = (String) driver2.getCapabilities().getCapability("webSocketUrl"); + String port2 = webSocketUrl2.replaceAll("^ws://[^:]+:(\\d+)/.*$", "$1"); + + // Verify that the ports are different + assertThat(port1).isNotEqualTo(port2); + } } From 2efeb62fffdab7da5bf99af09d2a562da4071d4d Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 20 Mar 2025 20:15:06 +0530 Subject: [PATCH 3/5] run `format.sh` --- .../openqa/selenium/firefox/FirefoxDriverConcurrentTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java index 6e5997e08b9d9..deaa1dd75237a 100644 --- a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java +++ b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java @@ -24,11 +24,11 @@ import java.util.Random; import org.junit.jupiter.api.Test; import org.openqa.selenium.By; -import org.openqa.selenium.bidi.BiDi; import org.openqa.selenium.ParallelTestRunner; import org.openqa.selenium.ParallelTestRunner.Worker; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.bidi.BiDi; import org.openqa.selenium.testing.JupiterTestBase; import org.openqa.selenium.testing.drivers.WebDriverBuilder; From 15e4fdf9e4cafb8992c2e060e97e49bde28703a7 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Sun, 23 Mar 2025 22:59:12 +0530 Subject: [PATCH 4/5] use `WebDriverBuilder` for driver --- .../firefox/FirefoxDriverConcurrentTest.java | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java index deaa1dd75237a..fa4a2c12733b9 100644 --- a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java +++ b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java @@ -172,28 +172,38 @@ void multipleFirefoxInstancesWithBiDiEnabledCanRunSimultaneously() { FirefoxOptions options1 = new FirefoxOptions().enableBiDi(); FirefoxOptions options2 = new FirefoxOptions().enableBiDi(); - FirefoxDriver driver1; - FirefoxDriver driver2; + WebDriver driver1 = null; + WebDriver driver2 = null; - // Start the first Firefox instance - driver1 = new FirefoxDriver(options1); - BiDi biDi1 = driver1.getBiDi(); - assertThat(biDi1).isNotNull(); - - // Extract the BiDi websocket URL and port for the first instance - String webSocketUrl1 = (String) driver1.getCapabilities().getCapability("webSocketUrl"); - String port1 = webSocketUrl1.replaceAll("^ws://[^:]+:(\\d+)/.*$", "$1"); - - // Start the second Firefox instance - driver2 = new FirefoxDriver(options2); - BiDi biDi2 = driver2.getBiDi(); - assertThat(biDi2).isNotNull(); - - // Extract the BiDi websocket URL and port for the second instance - String webSocketUrl2 = (String) driver2.getCapabilities().getCapability("webSocketUrl"); - String port2 = webSocketUrl2.replaceAll("^ws://[^:]+:(\\d+)/.*$", "$1"); - - // Verify that the ports are different - assertThat(port1).isNotEqualTo(port2); + try { + driver1 = new WebDriverBuilder().get(options1); + BiDi biDi1 = ((FirefoxDriver) driver1).getBiDi(); + assertThat(biDi1).isNotNull(); + + // Extract the BiDi websocket URL and port for the first instance + String webSocketUrl1 = + (String) ((FirefoxDriver) driver1).getCapabilities().getCapability("webSocketUrl"); + String port1 = webSocketUrl1.replaceAll("^ws://[^:]+:(\\d+)/.*$", "$1"); + + driver2 = new WebDriverBuilder().get(options2); + BiDi biDi2 = ((FirefoxDriver) driver2).getBiDi(); + assertThat(biDi2).isNotNull(); + + // Extract the BiDi websocket URL and port for the second instance + String webSocketUrl2 = + (String) ((FirefoxDriver) driver2).getCapabilities().getCapability("webSocketUrl"); + String port2 = webSocketUrl2.replaceAll("^ws://[^:]+:(\\d+)/.*$", "$1"); + + // Verify that the ports are different + assertThat(port1).isNotEqualTo(port2); + } finally { + // Clean up + if (driver1 != null) { + driver1.quit(); + } + if (driver2 != null) { + driver2.quit(); + } + } } } From c27cfacd3145439e7c875bcab78755e08807e8a2 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Tue, 8 Jul 2025 15:42:18 +0530 Subject: [PATCH 5/5] add `connectToExisting` and `withWebSocketPort` methods --- .../selenium/firefox/GeckoDriverService.java | 64 +++++++++++++------ .../firefox/FirefoxDriverConcurrentTest.java | 26 +++++++- 2 files changed, 71 insertions(+), 19 deletions(-) diff --git a/java/src/org/openqa/selenium/firefox/GeckoDriverService.java b/java/src/org/openqa/selenium/firefox/GeckoDriverService.java index c76e247b5fe67..2a8720bdb4a68 100644 --- a/java/src/org/openqa/selenium/firefox/GeckoDriverService.java +++ b/java/src/org/openqa/selenium/firefox/GeckoDriverService.java @@ -147,6 +147,8 @@ public static class Builder private @Nullable FirefoxDriverLogLevel logLevel; private @Nullable Boolean logTruncate; private @Nullable File profileRoot; + private @Nullable Integer marionettePort; + private @Nullable Integer websocketPort; @Override public int score(Capabilities capabilities) { @@ -204,6 +206,31 @@ public GeckoDriverService.Builder withProfileRoot(@Nullable File root) { return this; } + /** + * Configures geckodriver to connect to an existing Firefox instance via the specified + * Marionette port. + * + * @param marionettePort The port where Marionette is listening on the existing Firefox + * instance. + * @return A self reference. + */ + public GeckoDriverService.Builder connectToExisting(int marionettePort) { + this.marionettePort = marionettePort; + return this; + } + + /** + * Configures the WebSocket port for BiDi. A value of 0 will automatically allocate a free port. + * + * @param websocketPort The port to use for WebSocket communication, or 0 for automatic + * allocation. + * @return A self reference. + */ + public GeckoDriverService.Builder withWebSocketPort(@Nullable Integer websocketPort) { + this.websocketPort = websocketPort; + return this; + } + @Override protected void loadSystemProperties() { parseLogOutput(GECKO_DRIVER_LOG_PROPERTY); @@ -229,27 +256,28 @@ protected List createArgs() { List args = new ArrayList<>(); args.add(String.format(Locale.ROOT, "--port=%d", getPort())); - // Check if we're connecting to an existing Firefox instance - boolean connectExisting = false; - for (String arg : args) { - if (arg.contains("--connect-existing")) { - connectExisting = true; - break; + // Check if marionette port is specified via connectToExisting method + if (marionettePort != null) { + args.add("--connect-existing"); + args.add("--marionette-port"); + args.add(String.valueOf(marionettePort)); + } else { + // Configure websocket port for BiDi communication + if (websocketPort != null) { + args.add("--websocket-port"); + args.add(String.valueOf(websocketPort)); + + args.add("--allow-origins"); + args.add(String.format("http://127.0.0.1:%d", websocketPort)); + args.add(String.format("http://localhost:%d", websocketPort)); + args.add(String.format("http://[::1]:%d", websocketPort)); + } else { + // Use 0 to auto-allocate a free port + args.add("--websocket-port"); + args.add("0"); } } - // Only allocate a free port for the websocket when not connecting to an existing instance - // This avoids conflicts when multiple Firefox instances are started - if (!connectExisting) { - int wsPort = PortProber.findFreePort(); - args.add(String.format("--websocket-port=%d", wsPort)); - - args.add("--allow-origins"); - args.add(String.format("http://127.0.0.1:%d", wsPort)); - args.add(String.format("http://localhost:%d", wsPort)); - args.add(String.format("http://[::1]:%d", wsPort)); - } - if (logLevel != null) { args.add("--log"); args.add(logLevel.toString()); diff --git a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java index fa4a2c12733b9..9b78c1d985f6b 100644 --- a/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java +++ b/java/test/org/openqa/selenium/firefox/FirefoxDriverConcurrentTest.java @@ -168,7 +168,7 @@ void shouldBeAbleToUseTheSameProfileMoreThanOnce() { @Test void multipleFirefoxInstancesWithBiDiEnabledCanRunSimultaneously() { - // Create two Firefox instances with BiDi enabled + // Create two Firefox instances with BiDi enabled, should use different ports FirefoxOptions options1 = new FirefoxOptions().enableBiDi(); FirefoxOptions options2 = new FirefoxOptions().enableBiDi(); @@ -206,4 +206,28 @@ void multipleFirefoxInstancesWithBiDiEnabledCanRunSimultaneously() { } } } + + @Test + void geckoDriverServiceConnectToExistingFirefox() { + GeckoDriverService.Builder builder = new GeckoDriverService.Builder(); + + // Test connectToExisting method + builder.connectToExisting(2829); + GeckoDriverService service = builder.build(); + + assertThat(service).isNotNull(); + service.stop(); + } + + @Test + void geckoDriverServiceCustomWebSocketPort() { + GeckoDriverService.Builder builder = new GeckoDriverService.Builder(); + + // Test withWebSocketPort method + builder.withWebSocketPort(9225); + GeckoDriverService service = builder.build(); + + assertThat(service).isNotNull(); + service.stop(); + } }