Skip to content

Commit 8ddb020

Browse files
authored
[py] Configure WebSocket timeout and wait interval via ClientConfig (#16248)
1 parent 09da0ef commit 8ddb020

File tree

6 files changed

+81
-25
lines changed

6 files changed

+81
-25
lines changed

py/conftest.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,10 @@ def firefox_options(request):
489489
except (AttributeError, TypeError):
490490
raise Exception("This test requires a --driver to be specified")
491491

492+
# skip if not Firefox or Remote
493+
if driver_class not in ("firefox", "remote"):
494+
pytest.skip(f"This test requires Firefox or Remote. Got {driver_class}")
495+
492496
# skip tests in the 'remote' directory if run with a local driver
493497
if request.node.path.parts[-2] == "remote" and getattr(_supported_drivers, driver_class) != "Remote":
494498
pytest.skip(f"Remote tests can't be run with driver '{driver_class}'")
@@ -506,15 +510,17 @@ def chromium_options(request):
506510
except (AttributeError, TypeError):
507511
raise Exception("This test requires a --driver to be specified")
508512

509-
# Skip if not Chrome or Edge
510-
if driver_class not in ("chrome", "edge"):
511-
pytest.skip(f"This test requires Chrome or Edge, got {driver_class}")
513+
# skip if not Chrome, Edge, or Remote
514+
if driver_class not in ("chrome", "edge", "remote"):
515+
pytest.skip(f"This test requires Chrome, Edge, or Remote. Got {driver_class}")
512516

513517
# skip tests in the 'remote' directory if run with a local driver
514518
if request.node.path.parts[-2] == "remote" and getattr(_supported_drivers, driver_class) != "Remote":
515519
pytest.skip(f"Remote tests can't be run with driver '{driver_class}'")
516520

517-
if driver_class in ("chrome", "edge"):
518-
options = Driver.clean_options(driver_class, request)
521+
if driver_class in ("chrome", "remote"):
522+
options = Driver.clean_options("chrome", request)
523+
else:
524+
options = Driver.clean_options("edge", request)
519525

520526
return options

py/selenium/webdriver/remote/client_config.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,22 +50,19 @@ class ClientConfig:
5050
keep_alive = _ClientConfigDescriptor("_keep_alive")
5151
"""Gets and Sets Keep Alive value."""
5252
proxy = _ClientConfigDescriptor("_proxy")
53-
"""Gets and Sets the proxy used for communicating to the driver/server."""
53+
"""Gets and Sets the proxy used for communicating with the driver/server."""
5454
ignore_certificates = _ClientConfigDescriptor("_ignore_certificates")
5555
"""Gets and Sets the ignore certificate check value."""
5656
init_args_for_pool_manager = _ClientConfigDescriptor("_init_args_for_pool_manager")
5757
"""Gets and Sets the ignore certificate check."""
5858
timeout = _ClientConfigDescriptor("_timeout")
59-
"""Gets and Sets the timeout (in seconds) used for communicating to the
60-
driver/server."""
59+
"""Gets and Sets the timeout (in seconds) used for communicating with the driver/server."""
6160
ca_certs = _ClientConfigDescriptor("_ca_certs")
6261
"""Gets and Sets the path to bundle of CA certificates."""
6362
username = _ClientConfigDescriptor("_username")
64-
"""Gets and Sets the username used for basic authentication to the
65-
remote."""
63+
"""Gets and Sets the username used for basic authentication to the remote."""
6664
password = _ClientConfigDescriptor("_password")
67-
"""Gets and Sets the password used for basic authentication to the
68-
remote."""
65+
"""Gets and Sets the password used for basic authentication to the remote."""
6966
auth_type = _ClientConfigDescriptor("_auth_type")
7067
"""Gets and Sets the type of authentication to the remote server."""
7168
token = _ClientConfigDescriptor("_token")
@@ -74,6 +71,10 @@ class ClientConfig:
7471
"""Gets and Sets user agent to be added to the request headers."""
7572
extra_headers = _ClientConfigDescriptor("_extra_headers")
7673
"""Gets and Sets extra headers to be added to the request."""
74+
websocket_timeout = _ClientConfigDescriptor("_websocket_timeout")
75+
"""Gets and Sets the WebSocket response wait timeout (in seconds) used for communicating with the browser."""
76+
websocket_interval = _ClientConfigDescriptor("_websocket_interval")
77+
"""Gets and Sets the WebSocket response wait interval (in seconds) used for communicating with the browser."""
7778

7879
def __init__(
7980
self,
@@ -90,6 +91,8 @@ def __init__(
9091
token: Optional[str] = None,
9192
user_agent: Optional[str] = None,
9293
extra_headers: Optional[dict] = None,
94+
websocket_timeout: Optional[float] = 30.0,
95+
websocket_interval: Optional[float] = 0.1,
9396
) -> None:
9497
self.remote_server_addr = remote_server_addr
9598
self.keep_alive = keep_alive
@@ -103,6 +106,8 @@ def __init__(
103106
self.token = token
104107
self.user_agent = user_agent
105108
self.extra_headers = extra_headers
109+
self.websocket_timeout = websocket_timeout
110+
self.websocket_interval = websocket_interval
106111

107112
self.ca_certs = (
108113
(os.getenv("REQUESTS_CA_BUNDLE") if "REQUESTS_CA_BUNDLE" in os.environ else certifi.where())

py/selenium/webdriver/remote/webdriver.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,7 +1211,11 @@ def start_devtools(self):
12111211
return self._devtools, self._websocket_connection
12121212
if self.caps["browserName"].lower() == "firefox":
12131213
raise RuntimeError("CDP support for Firefox has been removed. Please switch to WebDriver BiDi.")
1214-
self._websocket_connection = WebSocketConnection(ws_url)
1214+
self._websocket_connection = WebSocketConnection(
1215+
ws_url,
1216+
self.command_executor.client_config.websocket_timeout,
1217+
self.command_executor.client_config.websocket_interval,
1218+
)
12151219
targets = self._websocket_connection.execute(self._devtools.target.get_targets())
12161220
for target in targets:
12171221
if target.target_id == self.current_window_handle:
@@ -1260,7 +1264,11 @@ def _start_bidi(self):
12601264
else:
12611265
raise WebDriverException("Unable to find url to connect to from capabilities")
12621266

1263-
self._websocket_connection = WebSocketConnection(ws_url)
1267+
self._websocket_connection = WebSocketConnection(
1268+
ws_url,
1269+
self.command_executor.client_config.websocket_timeout,
1270+
self.command_executor.client_config.websocket_interval,
1271+
)
12641272

12651273
@property
12661274
def network(self):

py/selenium/webdriver/remote/websocket_connection.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# KIND, either express or implied. See the License for the
1515
# specific language governing permissions and limitations
1616
# under the License.
17+
1718
import json
1819
import logging
1920
from ssl import CERT_NONE
@@ -28,16 +29,20 @@
2829

2930

3031
class WebSocketConnection:
31-
_response_wait_timeout = 30
32-
_response_wait_interval = 0.1
33-
3432
_max_log_message_size = 9999
3533

36-
def __init__(self, url):
37-
self.callbacks = {}
38-
self.session_id = None
34+
def __init__(self, url, timeout, interval):
35+
if not isinstance(timeout, (int, float)) or timeout < 0:
36+
raise WebDriverException("timeout must be a positive number")
37+
if not isinstance(interval, (int, float)) or timeout < 0:
38+
raise WebDriverException("interval must be a positive number")
39+
3940
self.url = url
41+
self.response_wait_timeout = timeout
42+
self.response_wait_interval = interval
4043

44+
self.callbacks = {}
45+
self.session_id = None
4146
self._id = 0
4247
self._messages = {}
4348
self._started = False
@@ -46,7 +51,7 @@ def __init__(self, url):
4651
self._wait_until(lambda: self._started)
4752

4853
def close(self):
49-
self._ws_thread.join(timeout=self._response_wait_timeout)
54+
self._ws_thread.join(timeout=self.response_wait_timeout)
5055
self._ws.close()
5156
self._started = False
5257
self._ws = None
@@ -142,8 +147,8 @@ def _process_message(self, message):
142147
Thread(target=callback, args=(params,)).start()
143148

144149
def _wait_until(self, condition):
145-
timeout = self._response_wait_timeout
146-
interval = self._response_wait_interval
150+
timeout = self.response_wait_timeout
151+
interval = self.response_wait_interval
147152

148153
while timeout > 0:
149154
result = condition()

py/test/selenium/webdriver/remote/remote_connection_tests.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
# under the License.
1717

1818
import base64
19+
import time
1920

2021
import filetype
2122
import pytest
@@ -40,8 +41,8 @@ def test_remote_webdriver_with_http_timeout(firefox_options, webserver):
4041
set less than the implicit wait timeout, and verifies the http timeout
4142
is triggered first when waiting for an element.
4243
"""
43-
http_timeout = 6
44-
wait_timeout = 8
44+
http_timeout = 4
45+
wait_timeout = 6
4546
server_addr = f"http://{webserver.host}:{webserver.port}"
4647
client_config = ClientConfig(remote_server_addr=server_addr, timeout=http_timeout)
4748
assert client_config.timeout == http_timeout
@@ -50,3 +51,28 @@ def test_remote_webdriver_with_http_timeout(firefox_options, webserver):
5051
driver.implicitly_wait(wait_timeout)
5152
with pytest.raises(ReadTimeoutError):
5253
driver.find_element(By.ID, "no_element_to_be_found")
54+
55+
56+
def test_remote_webdriver_with_websocket_timeout(firefox_options, webserver):
57+
"""This test starts a remote webdriver that uses websockets, and has a websocket
58+
client timeout less than the default. It verifies the websocket times out according
59+
to this value.
60+
"""
61+
websocket_timeout = 2.0
62+
websocket_interval = 1.0
63+
64+
server_addr = f"http://{webserver.host}:{webserver.port}"
65+
client_config = ClientConfig(
66+
remote_server_addr=server_addr, websocket_timeout=websocket_timeout, websocket_interval=websocket_interval
67+
)
68+
assert client_config.websocket_timeout == websocket_timeout
69+
firefox_options.enable_bidi = True
70+
with webdriver.Remote(options=firefox_options, client_config=client_config) as driver:
71+
driver._start_bidi()
72+
assert driver._websocket_connection.response_wait_timeout == websocket_timeout
73+
assert driver._websocket_connection.response_wait_interval == websocket_interval
74+
start = time.time()
75+
driver._websocket_connection.close()
76+
elapsed = time.time() - start
77+
assert elapsed >= websocket_timeout
78+
assert elapsed < websocket_timeout + 10

py/test/unit/selenium/webdriver/remote/remote_connection_tests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ def test_execute_custom_command(mock_request, remote_connection):
5757
assert response == {"status": 200, "value": "OK"}
5858

5959

60+
def test_default_websocket_settings():
61+
config = ClientConfig(remote_server_addr="http://localhost:4444")
62+
assert config.websocket_timeout == 30.0
63+
assert config.websocket_interval == 0.1
64+
65+
6066
def test_get_remote_connection_headers_defaults():
6167
url = "http://remote"
6268
headers = RemoteConnection.get_remote_connection_headers(parse.urlparse(url))

0 commit comments

Comments
 (0)