From cf4af2a92414010ca49c5ba14e59cf73dc8badea Mon Sep 17 00:00:00 2001 From: Taylor McGinnis Date: Mon, 24 Aug 2026 15:18:33 -0700 Subject: [PATCH] Add REMOTE_WEBDRIVER_TIMEOUT setting for Remote WebDriver HTTP requests Selenium's ClientConfig defaults its HTTP timeout to socket.getdefaulttimeout(), which is None, so a WebDriver command to a remote Selenium server blocks forever if a grid node dies with the TCP connection half-open. The only way to bound this today is to monkeypatch ClientConfig, because get_remote_driver() passes no client_config to webdriver.Remote. Add a REMOTE_WEBDRIVER_TIMEOUT setting (default None, which keeps the existing behavior). When set, get_remote_driver() builds a ClientConfig with that timeout and passes it to webdriver.Remote, so every HTTP request to the remote server gets a finite client-side deadline and a dead node surfaces as a ReadTimeoutError instead of a hang. The setting works with --settings-file like PAGE_LOAD_TIMEOUT. Co-Authored-By: Claude Fable 5 --- seleniumbase/config/settings.py | 8 ++++++++ seleniumbase/core/browser_launcher.py | 13 +++++++++++++ seleniumbase/core/settings_parser.py | 2 ++ 3 files changed, 23 insertions(+) diff --git a/seleniumbase/config/settings.py b/seleniumbase/config/settings.py index 15b32fbadb5..c2b6690cef9 100644 --- a/seleniumbase/config/settings.py +++ b/seleniumbase/config/settings.py @@ -23,6 +23,14 @@ # In global Selenium settings, this value is set to 300 seconds by default. PAGE_LOAD_TIMEOUT = 120 +# Default client-side timeout (in seconds) for each HTTP request that a +# Remote WebDriver client sends to a Selenium server / Selenium Grid. +# When set to None, requests have no client-side timeout, which means +# a WebDriver command can block forever if a grid node dies mid-session. +# If used, set this higher than PAGE_LOAD_TIMEOUT, and higher than the +# time a new-session request may wait in your grid's session queue. +REMOTE_WEBDRIVER_TIMEOUT = None + # Default page load strategy. # ["normal", "eager", "none"] # Selenium default = "normal" diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py index fe4d359a22a..e45f4d1cd38 100644 --- a/seleniumbase/core/browser_launcher.py +++ b/seleniumbase/core/browser_launcher.py @@ -19,6 +19,7 @@ from selenium.common.exceptions import SessionNotCreatedException from selenium.webdriver.chrome.service import Service as ChromeService from selenium.webdriver.common import utils as common_utils +from selenium.webdriver.remote.client_config import ClientConfig from seleniumbase import config as sb_config from seleniumbase import decorators from seleniumbase import drivers # webdriver storage folder for SeleniumBase @@ -3719,6 +3720,12 @@ def get_remote_driver( address += "wd/hub" else: address += "/wd/hub" + client_config = None + if settings.REMOTE_WEBDRIVER_TIMEOUT: + client_config = ClientConfig( + remote_server_addr=address, + timeout=settings.REMOTE_WEBDRIVER_TIMEOUT, + ) downloads_path = DOWNLOADS_FOLDER desired_caps = {} extra_caps = {} @@ -3845,6 +3852,7 @@ def get_remote_driver( chrome_options.set_capability(key, ext_caps[key]) driver = webdriver.Remote( command_executor=address, + client_config=client_config, options=chrome_options, ) return extend_driver(driver) @@ -3910,6 +3918,7 @@ def get_remote_driver( firefox_options.set_capability(key, ext_caps[key]) driver = webdriver.Remote( command_executor=address, + client_config=client_config, options=firefox_options, ) return extend_driver(driver) @@ -3919,6 +3928,7 @@ def get_remote_driver( remote_options.set_capability("cloud:options", desired_caps) driver = webdriver.Remote( command_executor=address, + client_config=client_config, options=remote_options, ) return extend_driver(driver) @@ -4018,6 +4028,7 @@ def get_remote_driver( edge_options.set_capability(key, ext_caps[key]) driver = webdriver.Remote( command_executor=address, + client_config=client_config, options=edge_options, ) return extend_driver(driver) @@ -4027,6 +4038,7 @@ def get_remote_driver( remote_options.set_capability("cloud:options", desired_caps) driver = webdriver.Remote( command_executor=address, + client_config=client_config, options=remote_options, ) return extend_driver(driver) @@ -4039,6 +4051,7 @@ def get_remote_driver( remote_options.set_capability("bstack:options", desired_caps) driver = webdriver.Remote( command_executor=address, + client_config=client_config, options=remote_options, ) return extend_driver(driver) diff --git a/seleniumbase/core/settings_parser.py b/seleniumbase/core/settings_parser.py index fafed50e58f..473c02d6f38 100644 --- a/seleniumbase/core/settings_parser.py +++ b/seleniumbase/core/settings_parser.py @@ -73,6 +73,8 @@ def set_settings(settings_file): settings.EXTREME_TIMEOUT = override_settings[key] elif key == "PAGE_LOAD_TIMEOUT": settings.PAGE_LOAD_TIMEOUT = override_settings[key] + elif key == "REMOTE_WEBDRIVER_TIMEOUT": + settings.REMOTE_WEBDRIVER_TIMEOUT = override_settings[key] elif key == "ARCHIVE_EXISTING_LOGS": settings.ARCHIVE_EXISTING_LOGS = override_settings[key] elif key == "ARCHIVE_EXISTING_DOWNLOADS":