From 83b358bcb67c9e82c19f253f522dc6395bb70677 Mon Sep 17 00:00:00 2001 From: Alex Popov Date: Wed, 1 Oct 2025 21:22:50 +0700 Subject: [PATCH 1/4] Add WebSocket timeout and interval to ClientConfig --- .../selenium/remote/http/ClientConfig.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/java/src/org/openqa/selenium/remote/http/ClientConfig.java b/java/src/org/openqa/selenium/remote/http/ClientConfig.java index bfb0a95e3759f..c635dd50e056b 100644 --- a/java/src/org/openqa/selenium/remote/http/ClientConfig.java +++ b/java/src/org/openqa/selenium/remote/http/ClientConfig.java @@ -35,6 +35,8 @@ public class ClientConfig { private final URI baseUri; private final Duration connectionTimeout; private final Duration readTimeout; + private final Duration webSocketTimeout; + private final Duration webSocketInterval; private final Filter filters; private final Proxy proxy; private final Credentials credentials; @@ -45,6 +47,8 @@ protected ClientConfig( URI baseUri, Duration connectionTimeout, Duration readTimeout, + Duration webSocketTimeout, + Duration webSocketInterval, Filter filters, Proxy proxy, Credentials credentials, @@ -53,6 +57,8 @@ protected ClientConfig( this.baseUri = baseUri; this.connectionTimeout = Require.nonNegative("Connection timeout", connectionTimeout); this.readTimeout = Require.nonNegative("Read timeout", readTimeout); + this.webSocketTimeout = Require.nonNegative("WebSocket timeout", webSocketTimeout); + this.webSocketInterval = Require.nonNegative("WebSocket interval", webSocketInterval); this.filters = Require.nonNull("Filters", filters); this.proxy = proxy; this.credentials = credentials; @@ -67,6 +73,10 @@ public static ClientConfig defaultConfig() { Long.parseLong(System.getProperty("webdriver.httpclient.connectionTimeout", "10"))), Duration.ofSeconds( Long.parseLong(System.getProperty("webdriver.httpclient.readTimeout", "180"))), + Duration.ofSeconds( + Long.parseLong(System.getProperty("webdriver.httpclient.webSocketTimeout", "30"))), + Duration.ofMillis( + Long.parseLong(System.getProperty("webdriver.httpclient.webSocketInterval", "100"))), DEFAULT_FILTER, null, null, @@ -79,6 +89,8 @@ public ClientConfig baseUri(URI baseUri) { Require.nonNull("Base URI", baseUri), connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filters, proxy, credentials, @@ -111,6 +123,8 @@ public ClientConfig connectionTimeout(Duration timeout) { baseUri, Require.nonNull("Connection timeout", timeout), readTimeout, + webSocketTimeout, + webSocketInterval, filters, proxy, credentials, @@ -127,6 +141,8 @@ public ClientConfig readTimeout(Duration timeout) { baseUri, connectionTimeout, Require.nonNull("Read timeout", timeout), + webSocketTimeout, + webSocketInterval, filters, proxy, credentials, @@ -138,12 +154,50 @@ public Duration readTimeout() { return readTimeout; } + public ClientConfig webSocketTimeout(Duration timeout) { + return new ClientConfig( + baseUri, + connectionTimeout, + readTimeout, + Require.nonNull("WebSocket timeout", timeout), + webSocketInterval, + filters, + proxy, + credentials, + sslContext, + version); + } + + public Duration webSocketTimeout() { + return webSocketTimeout; + } + + public ClientConfig webSocketInterval(Duration interval) { + return new ClientConfig( + baseUri, + connectionTimeout, + readTimeout, + webSocketTimeout, + Require.nonNull("WebSocket interval", interval), + filters, + proxy, + credentials, + sslContext, + version); + } + + public Duration webSocketInterval() { + return webSocketInterval; + } + public ClientConfig withFilter(Filter filter) { Require.nonNull("Filter", filter); return new ClientConfig( baseUri, connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filter.andThen(DEFAULT_FILTER), proxy, credentials, @@ -156,6 +210,8 @@ public ClientConfig withRetries() { baseUri, connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filters.andThen(RETRY_FILTER), proxy, credentials, @@ -172,6 +228,8 @@ public ClientConfig proxy(Proxy proxy) { baseUri, connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filters, Require.nonNull("Proxy", proxy), credentials, @@ -188,6 +246,8 @@ public ClientConfig authenticateAs(Credentials credentials) { baseUri, connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filters, proxy, Require.nonNull("Credentials", credentials), @@ -204,6 +264,8 @@ public ClientConfig sslContext(SSLContext sslContext) { baseUri, connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filters, proxy, credentials, @@ -220,6 +282,8 @@ public ClientConfig version(String version) { baseUri, connectionTimeout, readTimeout, + webSocketTimeout, + webSocketInterval, filters, proxy, credentials, @@ -240,6 +304,10 @@ public String toString() { + connectionTimeout + ", readTimeout=" + readTimeout + + ", webSocketTimeout=" + + webSocketTimeout + + ", webSocketInterval=" + + webSocketInterval + ", filters=" + filters + ", proxy=" From f8e319165d53a43d14146649bababade50a5484c Mon Sep 17 00:00:00 2001 From: Alex Popov Date: Wed, 1 Oct 2025 21:38:57 +0700 Subject: [PATCH 2/4] Add WebSocket timeout and interval to JdkHttpClient --- .../openqa/selenium/remote/http/jdk/JdkHttpClient.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/java/src/org/openqa/selenium/remote/http/jdk/JdkHttpClient.java b/java/src/org/openqa/selenium/remote/http/jdk/JdkHttpClient.java index f2d844a21b152..703bf1e7e7ccd 100644 --- a/java/src/org/openqa/selenium/remote/http/jdk/JdkHttpClient.java +++ b/java/src/org/openqa/selenium/remote/http/jdk/JdkHttpClient.java @@ -80,6 +80,8 @@ public class JdkHttpClient implements HttpClient { private final ExecutorService executorService; private final Duration readTimeout; private final Duration connectTimeout; + private final Duration webSocketTimeout; + private final Duration webSocketInterval; JdkHttpClient(ClientConfig config) { Objects.requireNonNull(config, "Client config must be set"); @@ -87,6 +89,8 @@ public class JdkHttpClient implements HttpClient { this.messages = new JdkHttpMessages(config); this.readTimeout = config.readTimeout(); this.connectTimeout = config.connectionTimeout(); + this.webSocketTimeout = config.webSocketTimeout(); + this.webSocketInterval = config.webSocketInterval(); this.websockets = new ArrayList<>(); this.handler = config.filter().andFinally(this::execute0); @@ -244,7 +248,7 @@ public void onError(java.net.http.WebSocket webSocket, Throwable error) { try { underlyingSocket = - webSocketCompletableFuture.get(readTimeout.toMillis(), TimeUnit.MILLISECONDS); + webSocketCompletableFuture.get(webSocketTimeout.toMillis(), TimeUnit.MILLISECONDS); } catch (CancellationException e) { throw new ConnectionFailedException("JdkWebSocket initial request canceled", e); } catch (ExecutionException e) { @@ -314,7 +318,7 @@ public WebSocket send(Message message) { long start = System.currentTimeMillis(); CompletableFuture future = makeCall.get(); try { - future.get(readTimeout.toMillis(), TimeUnit.MILLISECONDS); + future.get(webSocketTimeout.toMillis(), TimeUnit.MILLISECONDS); } catch (CancellationException e) { throw new WebDriverException(e.getMessage(), e); } catch (ExecutionException e) { From 53b667b3918354f901b066399ed5fa4a64bd84c5 Mon Sep 17 00:00:00 2001 From: Alex Popov Date: Fri, 3 Oct 2025 19:03:39 +0700 Subject: [PATCH 3/4] Add WebSocket timeout and interval configuration to Chromium and Firefox drivers --- .../selenium/chromium/ChromiumDriver.java | 24 ++++++- .../selenium/chromium/ChromiumOptions.java | 66 ++++++++++++++++++- .../selenium/firefox/FirefoxDriver.java | 24 ++++++- .../selenium/firefox/FirefoxOptions.java | 51 ++++++++++++++ 4 files changed, 158 insertions(+), 7 deletions(-) diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java index 437d7063801d6..5534b91b5ad76 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java @@ -23,6 +23,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -119,7 +120,21 @@ protected ChromiumDriver( return null; }); - this.biDi = createBiDi(biDiUri); + // Extract WebSocket configuration from capabilities + Duration webSocketTimeout = Duration.ofSeconds(30); // Default value + Duration webSocketInterval = Duration.ofMillis(100); // Default value + + Object timeoutCapability = originalCapabilities.getCapability("se:webSocketTimeout"); + if (timeoutCapability instanceof Number) { + webSocketTimeout = Duration.ofSeconds(((Number) timeoutCapability).longValue()); + } + + Object intervalCapability = originalCapabilities.getCapability("se:webSocketInterval"); + if (intervalCapability instanceof Number) { + webSocketInterval = Duration.ofMillis(((Number) intervalCapability).longValue()); + } + + this.biDi = createBiDi(biDiUri, webSocketTimeout, webSocketInterval); Optional reportedUri = CdpEndpointFinder.getReportedUri(capabilityKey, originalCapabilities); @@ -295,7 +310,7 @@ public Optional maybeGetDevTools() { return devTools; } - private Optional createBiDi(Optional biDiUri) { + private Optional createBiDi(Optional biDiUri, Duration webSocketTimeout, Duration webSocketInterval) { if (biDiUri.isEmpty()) { return Optional.empty(); } @@ -308,7 +323,10 @@ private Optional createBiDi(Optional biDiUri) { + " capability is set.")); HttpClient.Factory clientFactory = HttpClient.Factory.createDefault(); - ClientConfig wsConfig = ClientConfig.defaultConfig().baseUri(wsUri); + ClientConfig wsConfig = ClientConfig.defaultConfig() + .baseUri(wsUri) + .webSocketTimeout(webSocketTimeout) + .webSocketInterval(webSocketInterval); HttpClient wsClient = clientFactory.createClient(wsConfig); org.openqa.selenium.bidi.Connection biDiConnection = diff --git a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java index b23ce29d2a567..7d9b93940881a 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.time.Duration; import java.util.*; import java.util.stream.Stream; import org.openqa.selenium.Capabilities; @@ -61,6 +62,8 @@ public class ChromiumOptions> private final List extensions = new ArrayList<>(); private final Map experimentalOptions = new HashMap<>(); private Map androidOptions = new HashMap<>(); + private Duration webSocketTimeout = Duration.ofSeconds(30); + private Duration webSocketInterval = Duration.ofMillis(100); private final String capabilityName; @@ -219,14 +222,67 @@ private T setAndroidCapability(String name, Object value) { return (T) this; } + /** + * Sets the WebSocket response wait timeout (in seconds) used for communicating with the browser. + * + * @param timeout WebSocket response wait timeout + * @return A self reference. + */ + public T setWebSocketTimeout(Duration timeout) { + this.webSocketTimeout = Require.nonNull("WebSocket timeout", timeout); + return (T) this; + } + + /** + * Gets the WebSocket response wait timeout (in seconds) used for communicating with the browser. + * + * @return WebSocket response wait timeout + */ + public Duration getWebSocketTimeout() { + return webSocketTimeout; + } + + /** + * Sets the WebSocket response wait interval (in seconds) used for communicating with the browser. + * + * @param interval WebSocket response wait interval + * @return A self reference. + */ + public T setWebSocketInterval(Duration interval) { + this.webSocketInterval = Require.nonNull("WebSocket interval", interval); + return (T) this; + } + + /** + * Gets the WebSocket response wait interval (in seconds) used for communicating with the browser. + * + * @return WebSocket response wait interval + */ + public Duration getWebSocketInterval() { + return webSocketInterval; + } + @Override protected Set getExtraCapabilityNames() { - return Collections.singleton(capabilityName); + Set names = new HashSet<>(); + names.add(capabilityName); + names.add("se:webSocketTimeout"); + names.add("se:webSocketInterval"); + return names; } @Override protected Object getExtraCapability(String capabilityName) { Require.nonNull("Capability name", capabilityName); + + // Handle WebSocket configuration capabilities + if ("se:webSocketTimeout".equals(capabilityName)) { + return webSocketTimeout.toSeconds(); + } + if ("se:webSocketInterval".equals(capabilityName)) { + return webSocketInterval.toMillis(); + } + if (!this.capabilityName.equals(capabilityName)) { return null; } @@ -319,6 +375,14 @@ protected void mergeInPlace(Capabilities capabilities) { Optional.ofNullable(options.androidOptions) .ifPresent(opts -> opts.forEach(this::setAndroidCapability)); + + // Merge WebSocket configuration + if (options.webSocketTimeout != null) { + setWebSocketTimeout(options.webSocketTimeout); + } + if (options.webSocketInterval != null) { + setWebSocketInterval(options.webSocketInterval); + } } } diff --git a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java index 4de4858123196..a8b7de6ba8446 100644 --- a/java/src/org/openqa/selenium/firefox/FirefoxDriver.java +++ b/java/src/org/openqa/selenium/firefox/FirefoxDriver.java @@ -22,6 +22,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; +import java.time.Duration; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; @@ -156,7 +157,21 @@ private FirefoxDriver( return null; }); - this.biDi = createBiDi(biDiUri); + // Extract WebSocket configuration from capabilities + Duration webSocketTimeout = Duration.ofSeconds(30); // default + Duration webSocketInterval = Duration.ofMillis(100); // default + + Object timeoutCapability = capabilities.getCapability("se:webSocketTimeout"); + if (timeoutCapability instanceof Number) { + webSocketTimeout = Duration.ofMillis(((Number) timeoutCapability).longValue()); + } + + Object intervalCapability = capabilities.getCapability("se:webSocketInterval"); + if (intervalCapability instanceof Number) { + webSocketInterval = Duration.ofMillis(((Number) intervalCapability).longValue()); + } + + this.biDi = createBiDi(biDiUri, webSocketTimeout, webSocketInterval); this.capabilities = new ImmutableCapabilities(capabilities); } @@ -240,7 +255,7 @@ public void setContext(FirefoxCommandContext commandContext) { context.setContext(commandContext); } - private Optional createBiDi(Optional biDiUri) { + private Optional createBiDi(Optional biDiUri, Duration webSocketTimeout, Duration webSocketInterval) { if (biDiUri.isEmpty()) { return Optional.empty(); } @@ -253,7 +268,10 @@ private Optional createBiDi(Optional biDiUri) { + " capability is set.")); HttpClient.Factory clientFactory = HttpClient.Factory.createDefault(); - ClientConfig wsConfig = ClientConfig.defaultConfig().baseUri(wsUri); + ClientConfig wsConfig = ClientConfig.defaultConfig() + .baseUri(wsUri) + .webSocketTimeout(webSocketTimeout) + .webSocketInterval(webSocketInterval); HttpClient wsClient = clientFactory.createClient(wsConfig); org.openqa.selenium.bidi.Connection biDiConnection = diff --git a/java/src/org/openqa/selenium/firefox/FirefoxOptions.java b/java/src/org/openqa/selenium/firefox/FirefoxOptions.java index 4737d8cab7e99..b59081033ca92 100644 --- a/java/src/org/openqa/selenium/firefox/FirefoxOptions.java +++ b/java/src/org/openqa/selenium/firefox/FirefoxOptions.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -55,6 +56,8 @@ public class FirefoxOptions extends AbstractDriverOptions { public static final String FIREFOX_OPTIONS = "moz:firefoxOptions"; private Map firefoxOptions = Collections.unmodifiableMap(new TreeMap<>()); + private Duration webSocketTimeout = Duration.ofSeconds(30); + private Duration webSocketInterval = Duration.ofMillis(100); public FirefoxOptions() { setCapability(CapabilityType.BROWSER_NAME, FIREFOX.browserName()); @@ -265,11 +268,53 @@ public FirefoxOptions enableBiDi() { return this; } + /** + * Sets the WebSocket timeout for BiDi connections. + * + * @param webSocketTimeout the WebSocket timeout duration + * @return A self reference. + */ + public FirefoxOptions setWebSocketTimeout(Duration webSocketTimeout) { + this.webSocketTimeout = Require.nonNull("WebSocket timeout", webSocketTimeout); + return this; + } + + /** + * Gets the WebSocket timeout for BiDi connections. + * + * @return the WebSocket timeout duration + */ + public Duration getWebSocketTimeout() { + return webSocketTimeout; + } + + /** + * Sets the WebSocket interval for BiDi connections. + * + * @param webSocketInterval the WebSocket interval duration + * @return A self reference. + */ + public FirefoxOptions setWebSocketInterval(Duration webSocketInterval) { + this.webSocketInterval = Require.nonNull("WebSocket interval", webSocketInterval); + return this; + } + + /** + * Gets the WebSocket interval for BiDi connections. + * + * @return the WebSocket interval duration + */ + public Duration getWebSocketInterval() { + return webSocketInterval; + } + @Override protected Set getExtraCapabilityNames() { Set names = new TreeSet<>(); names.add(FIREFOX_OPTIONS); + names.add("se:webSocketTimeout"); + names.add("se:webSocketInterval"); return Collections.unmodifiableSet(names); } @@ -281,6 +326,12 @@ protected Object getExtraCapability(String capabilityName) { if (FIREFOX_OPTIONS.equals(capabilityName)) { return Collections.unmodifiableMap(firefoxOptions); } + if ("se:webSocketTimeout".equals(capabilityName)) { + return webSocketTimeout.toMillis(); + } + if ("se:webSocketInterval".equals(capabilityName)) { + return webSocketInterval.toMillis(); + } return null; } From 7fd7bf7d8e0e9345bc021aed42293f22de5446a7 Mon Sep 17 00:00:00 2001 From: Alex Popov Date: Sun, 12 Oct 2025 20:28:20 +0700 Subject: [PATCH 4/4] Update WebSocket timeout handling to use milliseconds instead of seconds --- java/src/org/openqa/selenium/chromium/ChromiumDriver.java | 2 +- java/src/org/openqa/selenium/chromium/ChromiumOptions.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java index 5534b91b5ad76..1e81fb585cf41 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumDriver.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumDriver.java @@ -126,7 +126,7 @@ protected ChromiumDriver( Object timeoutCapability = originalCapabilities.getCapability("se:webSocketTimeout"); if (timeoutCapability instanceof Number) { - webSocketTimeout = Duration.ofSeconds(((Number) timeoutCapability).longValue()); + webSocketTimeout = Duration.ofMillis(((Number) timeoutCapability).longValue()); } Object intervalCapability = originalCapabilities.getCapability("se:webSocketInterval"); diff --git a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java index 7d9b93940881a..4d2feebd395ed 100644 --- a/java/src/org/openqa/selenium/chromium/ChromiumOptions.java +++ b/java/src/org/openqa/selenium/chromium/ChromiumOptions.java @@ -277,7 +277,7 @@ protected Object getExtraCapability(String capabilityName) { // Handle WebSocket configuration capabilities if ("se:webSocketTimeout".equals(capabilityName)) { - return webSocketTimeout.toSeconds(); + return webSocketTimeout.toMillis(); } if ("se:webSocketInterval".equals(capabilityName)) { return webSocketInterval.toMillis();