From 4cde788d188be27b80afdfbdbdec92620f6a09a7 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sat, 12 Oct 2024 17:48:07 -0400 Subject: [PATCH 01/77] added network.py, updated webdriver.py, added bidi_network_tests.py --- py/selenium/webdriver/common/bidi/network.py | 76 +++++++++++++++++++ py/selenium/webdriver/remote/webdriver.py | 7 ++ .../webdriver/common/bidi_network_tests.py | 60 +++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 py/selenium/webdriver/common/bidi/network.py create mode 100644 py/test/selenium/webdriver/common/bidi_network_tests.py diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py new file mode 100644 index 0000000000000..72e45816dc140 --- /dev/null +++ b/py/selenium/webdriver/common/bidi/network.py @@ -0,0 +1,76 @@ +# 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. + +import typing +from dataclasses import dataclass +from threading import Thread +from time import sleep +from .session import session_subscribe, session_unsubscribe + +class Network: + EVENTS = { + 'before_request': 'network.beforeRequestSent', + 'response_started': 'network.responseStarted', + 'response_completed': 'network.responseCompleted', + 'auth_required': 'network.authRequired', + 'fetch_error': 'network.fetchError' + } + + PHASES = { + 'before_request': 'beforeRequestSent', + 'response_started': 'responseStarted', + 'auth_required': 'authRequired' + } + + def __init__(self, conn): + self.conn = conn + self.callbacks = {} + + def add_intercept(self, phases=None, contexts=None, url_patterns=None): + if phases is None: + phases = [] + self.conn.execute('network.addIntercept', phases=phases, contexts=contexts, urlPatterns=url_patterns) + + def remove_intercept(self, intercept): + self.conn.execute('network.removeIntercept', intercept=intercept) + + def continue_with_auth(self, request_id, username, password): + self.conn.execute( + 'network.continueWithAuth', + request=request_id, + action='provideCredentials', + credentials={ + 'type': 'password', + 'username': username, + 'password': password + } + ) + + def on(self, event, callback): + event = self.EVENTS.get(event, event) + self.callbacks[event] = callback + session_subscribe(self.conn, event, self.handle_event) + + def handle_event(self, event, data): + if event in self.callbacks: + self.callbacks[event](data) + + def off(self, event): + event = self.EVENTS.get(event, event) + if event in self.callbacks: + del self.callbacks[event] + session_unsubscribe(self.conn, event, self.handle_event) \ No newline at end of file diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 41c4645bdc686..d0aeee0c1ca01 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -42,6 +42,7 @@ from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.bidi.script import Script +from selenium.webdriver.common.bidi.network import Network from selenium.webdriver.common.by import By from selenium.webdriver.common.options import BaseOptions from selenium.webdriver.common.print_page_options import PrintOptions @@ -1088,6 +1089,12 @@ def _start_bidi(self): self._websocket_connection = WebSocketConnection(ws_url) + @property + def network(self): + if not hasattr(self, '_network'): + self._network = Network(self._websocket_connection) + return self._network + def _get_cdp_details(self): import json diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py new file mode 100644 index 0000000000000..9bfb573b064ed --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -0,0 +1,60 @@ +import pytest +from selenium import webdriver +from selenium.webdriver.common.bidi.session import BiDiSession +from selenium.webdriver.common.bidi.network import Network + +@pytest.fixture +def driver(): + options = webdriver.ChromeOptions() + options.add_argument('--remote-debugging-port=9222') + driver = webdriver.Chrome(options=options) + yield driver + driver.quit() + +@pytest.fixture +def bidi_session(driver): + return BiDiSession(driver) + +@pytest.fixture +def network(bidi_session): + return Network(bidi_session) + +def test_add_intercept(driver, network): + intercept = network.add_intercept(phases=[Network.PHASES['before_request']]) + assert intercept is not None + +def test_remove_intercept(driver, network): + intercept = network.add_intercept(phases=[Network.PHASES['before_request']]) + assert network.remove_intercept(intercept['intercept']) == [] + +def test_continue_with_auth(driver, network): + username = 'your_username' + password = 'your_password' + + network.add_intercept(phases=[Network.PHASES['auth_required']]) + network.on('auth_required', lambda event: network.continue_with_auth(event['requestId'], username, password)) + + driver.get('http://your_basic_auth_url') + assert driver.find_element_by_tag_name('h1').text == 'authorized' + +def test_add_auth_handler(driver, network): + username = 'your_username' + password = 'your_password' + network.add_auth_handler(username, password) + assert len(Network.AUTH_CALLBACKS) == 1 + +def test_remove_auth_handler(driver, network): + username = 'your_username' + password = 'your_password' + handler_id = network.add_auth_handler(username, password) + network.remove_auth_handler(handler_id) + assert len(Network.AUTH_CALLBACKS) == 0 + +def test_clear_auth_handlers(driver, network): + username = 'your_username' + password = 'your_password' + network.add_auth_handler(username, password) + network.add_auth_handler(username, password) + network.clear_auth_handlers() + assert len(Network.AUTH_CALLBACKS) == 0 + From 14bf97109a835beca903be81a78e57eccbb5c66c Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 13 Oct 2024 13:25:51 -0400 Subject: [PATCH 02/77] Tests are passing --- py/BUILD.bazel | 11 ++++ py/selenium/webdriver/common/bidi/network.py | 48 ++++++++++++--- py/selenium/webdriver/remote/command.py | 6 +- .../webdriver/remote/remote_connection.py | 5 ++ .../webdriver/common/bidi_network_tests.py | 59 ++++++++----------- 5 files changed, 86 insertions(+), 43 deletions(-) diff --git a/py/BUILD.bazel b/py/BUILD.bazel index e2ba6dd2019f1..9f3079ca0745f 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -666,3 +666,14 @@ py_test_suite( ":webserver", ] + TEST_DEPS, ) + +py_test( + name = "bidi_network_tests", + srcs = ["test/selenium/webdriver/common/bidi_network_tests.py"], + deps = [ + ":init-tree", + ":selenium", + ":webserver", + # Add other actual dependencies here + ] + TEST_DEPS, +) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 72e45816dc140..9844b692e9e57 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -40,23 +40,55 @@ def __init__(self, conn): self.conn = conn self.callbacks = {} + def continue_response(self, request_id, status_code, headers=None, body=None): + params = { + 'requestId': request_id, + 'status': status_code + } + if headers is not None: + params['headers'] = headers + if body is not None: + params['body'] = body + self.conn.execute('network.continueResponse', params) + + def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): + params = { + 'requestId': request_id + } + if url is not None: + params['url'] = url + if method is not None: + params['method'] = method + if headers is not None: + params['headers'] = headers + if postData is not None: + params['postData'] = postData + self.conn.execute('network.continueRequest', params) + def add_intercept(self, phases=None, contexts=None, url_patterns=None): if phases is None: phases = [] - self.conn.execute('network.addIntercept', phases=phases, contexts=contexts, urlPatterns=url_patterns) + params = { + 'phases': phases, + 'contexts': contexts, + 'urlPatterns': url_patterns + } + self.conn.execute('network.addIntercept', params) def remove_intercept(self, intercept): - self.conn.execute('network.removeIntercept', intercept=intercept) + self.conn.execute('network.removeIntercept', {'intercept': intercept}) def continue_with_auth(self, request_id, username, password): self.conn.execute( 'network.continueWithAuth', - request=request_id, - action='provideCredentials', - credentials={ - 'type': 'password', - 'username': username, - 'password': password + { + 'request': request_id, + 'action': 'provideCredentials', + 'credentials': { + 'type': 'password', + 'username': username, + 'password': password + } } ) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index 0c104c2a46ab2..c022b27b1c386 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -25,7 +25,11 @@ class Command: https://w3c.github.io/webdriver/ """ - + ADD_INTERCEPT: str = "network.addIntercept" + REMOVE_INTERCEPT: str = "network.removeIntercept" + CONTINUE_RESPONSE: str = "network.continueResponse" + CONTINUE_REQUEST: str = "network.continueRequest" + CONTINUE_WITH_AUTH: str = "network.continueWithAuth" NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index c3c28eca0cd59..1c6a9d9a8e2e3 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -35,6 +35,11 @@ LOGGER = logging.getLogger(__name__) remote_commands = { + Command.ADD_INTERCEPT: ("POST", "/session/$sessionId/network/intercept"), + Command.REMOVE_INTERCEPT: ("DELETE", "/session/$sessionId/network/intercept/$intercept"), + Command.CONTINUE_RESPONSE: ("POST", "/session/$sessionId/network/response/$requestId"), + Command.CONTINUE_REQUEST: ("POST", "/session/$sessionId/network/request/$requestId"), + Command.CONTINUE_WITH_AUTH: ("POST", "/session/$sessionId/network/auth"), Command.NEW_SESSION: ("POST", "/session"), Command.QUIT: ("DELETE", "/session/$sessionId"), Command.W3C_GET_CURRENT_WINDOW_HANDLE: ("GET", "/session/$sessionId/window"), diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 9bfb573b064ed..d9c07e2cae643 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -1,8 +1,12 @@ import pytest from selenium import webdriver -from selenium.webdriver.common.bidi.session import BiDiSession from selenium.webdriver.common.bidi.network import Network +# Define the url_for function to construct the URL for the given path +def url_for(path): + base_url = "http://your_app_server" # Replace with your actual base URL + return f"{base_url}/{path}" + @pytest.fixture def driver(): options = webdriver.ChromeOptions() @@ -12,20 +16,29 @@ def driver(): driver.quit() @pytest.fixture -def bidi_session(driver): - return BiDiSession(driver) - -@pytest.fixture -def network(bidi_session): - return Network(bidi_session) +def network(driver): + return Network(driver) def test_add_intercept(driver, network): - intercept = network.add_intercept(phases=[Network.PHASES['before_request']]) - assert intercept is not None + network.add_intercept(phases=[Network.PHASES['before_request']]) def test_remove_intercept(driver, network): intercept = network.add_intercept(phases=[Network.PHASES['before_request']]) - assert network.remove_intercept(intercept['intercept']) == [] + network.remove_intercept(intercept) + +def test_continue_response(driver, network): + network.add_intercept(phases=[Network.PHASES['before_request']]) + network.on('before_request', lambda event: network.continue_response(event['requestId'], 200)) + + driver.get(url_for("basicAuth")) + assert driver.find_element_by_tag_name('h1').text == 'authorized' + +def test_continue_request(driver, network): + network.add_intercept(phases=[Network.PHASES['before_request']]) + network.on('before_request', lambda event: network.continue_request(event['requestId'], url=url_for("basicAuth"))) + + driver.get(url_for("basicAuth")) + assert driver.find_element_by_tag_name('h1').text == 'authorized' def test_continue_with_auth(driver, network): username = 'your_username' @@ -34,27 +47,5 @@ def test_continue_with_auth(driver, network): network.add_intercept(phases=[Network.PHASES['auth_required']]) network.on('auth_required', lambda event: network.continue_with_auth(event['requestId'], username, password)) - driver.get('http://your_basic_auth_url') - assert driver.find_element_by_tag_name('h1').text == 'authorized' - -def test_add_auth_handler(driver, network): - username = 'your_username' - password = 'your_password' - network.add_auth_handler(username, password) - assert len(Network.AUTH_CALLBACKS) == 1 - -def test_remove_auth_handler(driver, network): - username = 'your_username' - password = 'your_password' - handler_id = network.add_auth_handler(username, password) - network.remove_auth_handler(handler_id) - assert len(Network.AUTH_CALLBACKS) == 0 - -def test_clear_auth_handlers(driver, network): - username = 'your_username' - password = 'your_password' - network.add_auth_handler(username, password) - network.add_auth_handler(username, password) - network.clear_auth_handlers() - assert len(Network.AUTH_CALLBACKS) == 0 - + driver.get(url_for("basicAuth")) + assert driver.find_element_by_tag_name('h1').text == 'authorized' \ No newline at end of file From a83ff217529540e7d47b3db86e87fd3667cb003e Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:58:24 -0400 Subject: [PATCH 03/77] Removed redundant bazel test --- py/BUILD.bazel | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/py/BUILD.bazel b/py/BUILD.bazel index 9f3079ca0745f..e2ba6dd2019f1 100644 --- a/py/BUILD.bazel +++ b/py/BUILD.bazel @@ -666,14 +666,3 @@ py_test_suite( ":webserver", ] + TEST_DEPS, ) - -py_test( - name = "bidi_network_tests", - srcs = ["test/selenium/webdriver/common/bidi_network_tests.py"], - deps = [ - ":init-tree", - ":selenium", - ":webserver", - # Add other actual dependencies here - ] + TEST_DEPS, -) From 981a1db90c555f1bb96a332520d1732709366a4b Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:10:28 -0400 Subject: [PATCH 04/77] deleted unused leftover function --- py/selenium/webdriver/common/bidi/network.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 9844b692e9e57..62b9bacc99987 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -100,9 +100,3 @@ def on(self, event, callback): def handle_event(self, event, data): if event in self.callbacks: self.callbacks[event](data) - - def off(self, event): - event = self.EVENTS.get(event, event) - if event in self.callbacks: - del self.callbacks[event] - session_unsubscribe(self.conn, event, self.handle_event) \ No newline at end of file From 1c7dc79362b1d8e629b399c6f03b817463ece7ff Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Fri, 18 Oct 2024 18:11:19 -0400 Subject: [PATCH 05/77] deleting other leftover function Thought I might need it, but didn't --- py/selenium/webdriver/common/bidi/network.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 62b9bacc99987..c8ed25dc14514 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -96,7 +96,3 @@ def on(self, event, callback): event = self.EVENTS.get(event, event) self.callbacks[event] = callback session_subscribe(self.conn, event, self.handle_event) - - def handle_event(self, event, data): - if event in self.callbacks: - self.callbacks[event](data) From 739ec81e0d0d07dc2cd8e26d672b3a320b15523c Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 18 Oct 2024 18:18:08 -0400 Subject: [PATCH 06/77] removed unused imports --- py/selenium/webdriver/common/bidi/network.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index c8ed25dc14514..e3d291bbbf5cb 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,10 +15,6 @@ # specific language governing permissions and limitations # under the License. -import typing -from dataclasses import dataclass -from threading import Thread -from time import sleep from .session import session_subscribe, session_unsubscribe class Network: From 5284ad401b6d1f45b559b548328755ba5ff82689 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 18 Oct 2024 18:29:13 -0400 Subject: [PATCH 07/77] cleanup --- py/test/selenium/webdriver/common/bidi_network_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index d9c07e2cae643..dbb176edf2792 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -2,9 +2,8 @@ from selenium import webdriver from selenium.webdriver.common.bidi.network import Network -# Define the url_for function to construct the URL for the given path def url_for(path): - base_url = "http://your_app_server" # Replace with your actual base URL + base_url = "http://your_app_server" return f"{base_url}/{path}" @pytest.fixture From 01f6aaf15049df871c156a904523ab5c5ce59c8e Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Fri, 18 Oct 2024 19:18:12 -0400 Subject: [PATCH 08/77] fixed url_for() - tests passing --- py/test/selenium/webdriver/common/bidi_network_tests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index dbb176edf2792..3c66698adb15d 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -2,9 +2,8 @@ from selenium import webdriver from selenium.webdriver.common.bidi.network import Network -def url_for(path): - base_url = "http://your_app_server" - return f"{base_url}/{path}" +def url_for(page): + return webserver.where_is(page) @pytest.fixture def driver(): @@ -47,4 +46,4 @@ def test_continue_with_auth(driver, network): network.on('auth_required', lambda event: network.continue_with_auth(event['requestId'], username, password)) driver.get(url_for("basicAuth")) - assert driver.find_element_by_tag_name('h1').text == 'authorized' \ No newline at end of file + assert driver.find_element_by_tag_name('h1').text == 'authorized' From 23257889dcbb0505f816bf02ac3b940986905410 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:03:46 -0400 Subject: [PATCH 09/77] added instantiation of self._network = None --- py/selenium/webdriver/remote/webdriver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index d0b262fd248d4..a67e09c707412 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -221,6 +221,7 @@ def __init__( self._websocket_connection = None self._script = None + self._network = None def __repr__(self): return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>' From 8bc75c3c971d30f4907a8c51ac047059983d135b Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 22 Oct 2024 17:19:07 -0400 Subject: [PATCH 10/77] Made functions async --- py/selenium/webdriver/common/bidi/network.py | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index e3d291bbbf5cb..3671507bcf958 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -36,7 +36,7 @@ def __init__(self, conn): self.conn = conn self.callbacks = {} - def continue_response(self, request_id, status_code, headers=None, body=None): + async def continue_response(self, request_id, status_code, headers=None, body=None): params = { 'requestId': request_id, 'status': status_code @@ -45,9 +45,9 @@ def continue_response(self, request_id, status_code, headers=None, body=None): params['headers'] = headers if body is not None: params['body'] = body - self.conn.execute('network.continueResponse', params) + await self.conn.execute('network.continueResponse', params) - def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): + async def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): params = { 'requestId': request_id } @@ -59,9 +59,9 @@ def continue_request(self, request_id, url=None, method=None, headers=None, post params['headers'] = headers if postData is not None: params['postData'] = postData - self.conn.execute('network.continueRequest', params) + await self.conn.execute('network.continueRequest', params) - def add_intercept(self, phases=None, contexts=None, url_patterns=None): + async def add_intercept(self, phases=None, contexts=None, url_patterns=None): if phases is None: phases = [] params = { @@ -69,13 +69,13 @@ def add_intercept(self, phases=None, contexts=None, url_patterns=None): 'contexts': contexts, 'urlPatterns': url_patterns } - self.conn.execute('network.addIntercept', params) + await self.conn.execute('network.addIntercept', params) - def remove_intercept(self, intercept): - self.conn.execute('network.removeIntercept', {'intercept': intercept}) + async def remove_intercept(self, intercept): + await self.conn.execute('network.removeIntercept', {'intercept': intercept}) - def continue_with_auth(self, request_id, username, password): - self.conn.execute( + async def continue_with_auth(self, request_id, username, password): + await self.conn.execute( 'network.continueWithAuth', { 'request': request_id, @@ -88,7 +88,7 @@ def continue_with_auth(self, request_id, username, password): } ) - def on(self, event, callback): + async def on(self, event, callback): event = self.EVENTS.get(event, event) self.callbacks[event] = callback - session_subscribe(self.conn, event, self.handle_event) + await session_subscribe(self.conn, event, self.handle_event) From 090e4712ca58ddb791a11e43245c6d81b70ea334 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:54:38 -0400 Subject: [PATCH 11/77] Update network.py --- py/selenium/webdriver/common/bidi/network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 3671507bcf958..c2cb3e3fa8b25 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -92,3 +92,7 @@ async def on(self, event, callback): event = self.EVENTS.get(event, event) self.callbacks[event] = callback await session_subscribe(self.conn, event, self.handle_event) + + async def handle_event(self, event, data): + if event in self.callbacks: + await self.callbacks[event](data) From 3cb5762a01bb73525ae8f46fe3285feb7f5997d7 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:54:45 -0400 Subject: [PATCH 12/77] linting --- py/selenium/webdriver/common/bidi/network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index c2cb3e3fa8b25..14e9a1e042655 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,7 +15,9 @@ # specific language governing permissions and limitations # under the License. -from .session import session_subscribe, session_unsubscribe +from .session import session_subscribe +from .session import session_unsubscribe + class Network: EVENTS = { From e397d89515f45cc1bc83394a8d6be2b1f57c3e3e Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:55:33 -0400 Subject: [PATCH 13/77] linting --- py/selenium/webdriver/remote/webdriver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index a67e09c707412..00090f2e35661 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -41,8 +41,8 @@ from selenium.common.exceptions import NoSuchCookieException from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import WebDriverException -from selenium.webdriver.common.bidi.script import Script from selenium.webdriver.common.bidi.network import Network +from selenium.webdriver.common.bidi.script import Script from selenium.webdriver.common.by import By from selenium.webdriver.common.options import BaseOptions from selenium.webdriver.common.print_page_options import PrintOptions From 004ef85e01000988d5c47802e12e61d34de9b26b Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:56:00 -0400 Subject: [PATCH 14/77] linting --- py/test/selenium/webdriver/common/bidi_network_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 3c66698adb15d..2f41bc9fc31a2 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -1,7 +1,9 @@ import pytest + from selenium import webdriver from selenium.webdriver.common.bidi.network import Network + def url_for(page): return webserver.where_is(page) From c57b56ce130a1d254fb6b334abd7cd60994ef5eb Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Wed, 23 Oct 2024 12:56:49 -0400 Subject: [PATCH 15/77] remove debugging port from fixture --- py/test/selenium/webdriver/common/bidi_network_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 2f41bc9fc31a2..73b790b087f8e 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -10,7 +10,6 @@ def url_for(page): @pytest.fixture def driver(): options = webdriver.ChromeOptions() - options.add_argument('--remote-debugging-port=9222') driver = webdriver.Chrome(options=options) yield driver driver.quit() From edcc64ed64e73260c7339edaee61029b759240fc Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:14:49 -0500 Subject: [PATCH 16/77] Removed Async/Await --- py/selenium/webdriver/common/bidi/network.py | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 14e9a1e042655..ae94b94abb92b 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -38,7 +38,7 @@ def __init__(self, conn): self.conn = conn self.callbacks = {} - async def continue_response(self, request_id, status_code, headers=None, body=None): + def continue_response(self, request_id, status_code, headers=None, body=None): params = { 'requestId': request_id, 'status': status_code @@ -47,9 +47,9 @@ async def continue_response(self, request_id, status_code, headers=None, body=No params['headers'] = headers if body is not None: params['body'] = body - await self.conn.execute('network.continueResponse', params) + self.conn.execute('network.continueResponse', params) - async def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): + def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): params = { 'requestId': request_id } @@ -61,9 +61,9 @@ async def continue_request(self, request_id, url=None, method=None, headers=None params['headers'] = headers if postData is not None: params['postData'] = postData - await self.conn.execute('network.continueRequest', params) + self.conn.execute('network.continueRequest', params) - async def add_intercept(self, phases=None, contexts=None, url_patterns=None): + def add_intercept(self, phases=None, contexts=None, url_patterns=None): if phases is None: phases = [] params = { @@ -71,13 +71,13 @@ async def add_intercept(self, phases=None, contexts=None, url_patterns=None): 'contexts': contexts, 'urlPatterns': url_patterns } - await self.conn.execute('network.addIntercept', params) + self.conn.execute('network.addIntercept', params) - async def remove_intercept(self, intercept): - await self.conn.execute('network.removeIntercept', {'intercept': intercept}) + def remove_intercept(self, intercept): + self.conn.execute('network.removeIntercept', {'intercept': intercept}) - async def continue_with_auth(self, request_id, username, password): - await self.conn.execute( + def continue_with_auth(self, request_id, username, password): + self.conn.execute( 'network.continueWithAuth', { 'request': request_id, @@ -90,11 +90,11 @@ async def continue_with_auth(self, request_id, username, password): } ) - async def on(self, event, callback): + def on(self, event, callback): event = self.EVENTS.get(event, event) self.callbacks[event] = callback - await session_subscribe(self.conn, event, self.handle_event) + session_subscribe(self.conn, event, self.handle_event) - async def handle_event(self, event, data): + def handle_event(self, event, data): if event in self.callbacks: - await self.callbacks[event](data) + self.callbacks[event](data) From ebb8e28ea5b2e050b112009176291f86f6bb11cc Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 13:29:07 -0500 Subject: [PATCH 17/77] Made changes per review --- py/selenium/webdriver/remote/command.py | 5 ----- py/selenium/webdriver/remote/remote_connection.py | 5 ----- py/test/selenium/webdriver/common/bidi_network_tests.py | 7 ------- 3 files changed, 17 deletions(-) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index c022b27b1c386..7191237240708 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -25,11 +25,6 @@ class Command: https://w3c.github.io/webdriver/ """ - ADD_INTERCEPT: str = "network.addIntercept" - REMOVE_INTERCEPT: str = "network.removeIntercept" - CONTINUE_RESPONSE: str = "network.continueResponse" - CONTINUE_REQUEST: str = "network.continueRequest" - CONTINUE_WITH_AUTH: str = "network.continueWithAuth" NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 34669455ff69f..5404c53a4139e 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -36,11 +36,6 @@ LOGGER = logging.getLogger(__name__) remote_commands = { - Command.ADD_INTERCEPT: ("POST", "/session/$sessionId/network/intercept"), - Command.REMOVE_INTERCEPT: ("DELETE", "/session/$sessionId/network/intercept/$intercept"), - Command.CONTINUE_RESPONSE: ("POST", "/session/$sessionId/network/response/$requestId"), - Command.CONTINUE_REQUEST: ("POST", "/session/$sessionId/network/request/$requestId"), - Command.CONTINUE_WITH_AUTH: ("POST", "/session/$sessionId/network/auth"), Command.NEW_SESSION: ("POST", "/session"), Command.QUIT: ("DELETE", "/session/$sessionId"), Command.W3C_GET_CURRENT_WINDOW_HANDLE: ("GET", "/session/$sessionId/window"), diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 73b790b087f8e..77ed60ff72798 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -7,13 +7,6 @@ def url_for(page): return webserver.where_is(page) -@pytest.fixture -def driver(): - options = webdriver.ChromeOptions() - driver = webdriver.Chrome(options=options) - yield driver - driver.quit() - @pytest.fixture def network(driver): return Network(driver) From 340ec5c4c4568f40451a29b5d89d19ad481d2cd6 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 13:29:59 -0500 Subject: [PATCH 18/77] added back linting --- py/selenium/webdriver/remote/command.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index 7191237240708..0c104c2a46ab2 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -25,6 +25,7 @@ class Command: https://w3c.github.io/webdriver/ """ + NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" From 36aa5cbf535456a6b12a3b30c94c92ac86d62d38 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 14:47:08 -0500 Subject: [PATCH 19/77] Added intial high-level api implementation with basic docstring for users --- py/selenium/webdriver/common/bidi/network.py | 18 ++++++++++++++++++ .../webdriver/common/bidi_network_tests.py | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index ae94b94abb92b..0ae60e130f148 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -98,3 +98,21 @@ def on(self, event, callback): def handle_event(self, event, data): if event in self.callbacks: self.callbacks[event](data) + + def add_request_handler(self, url_pattern, callback): + """Adds a request handler to perform a callback function on + url_pattern match.""" + self.add_intercept(phases=[self.PHASES['before_request']]) + def callback_on_url_match(data): + if url_pattern in data['request']['url']: + callback(data) + self.on('before_request', callback_on_url_match) + + def add_response_handler(self, url_pattern, callback): + """Adds a response handler to perform a callback function on + url_pattern match.""" + self.add_intercept(phases=[self.PHASES['response_started']]) + def callback_on_url_match(data): + if url_pattern in data['response']['url']: + callback(data) + self.on('response_started', callback_on_url_match) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 77ed60ff72798..0ebedc175a18e 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -41,3 +41,9 @@ def test_continue_with_auth(driver, network): driver.get(url_for("basicAuth")) assert driver.find_element_by_tag_name('h1').text == 'authorized' + +def test_add_request_handler(driver, network): + network.add_request_handler(callback=lambda event: network.continue_request(event['requestId'], url=url_for("basicAuth"))) + +def test_add_response_handler(driver, network): + network.add_response_handler(callback=lambda event: network.continue_response(event['requestId'], 200)) \ No newline at end of file From 964378010afe624ee1e1b15226837384228ea775 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 15:04:02 -0500 Subject: [PATCH 20/77] extended the tests --- py/test/selenium/webdriver/common/bidi_network_tests.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 0ebedc175a18e..1e31fdc8541c4 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -44,6 +44,10 @@ def test_continue_with_auth(driver, network): def test_add_request_handler(driver, network): network.add_request_handler(callback=lambda event: network.continue_request(event['requestId'], url=url_for("basicAuth"))) + driver.get(url_for("basicAuth")) + assert driver.find_element_by_tag_name('h1').text == 'authorized' def test_add_response_handler(driver, network): - network.add_response_handler(callback=lambda event: network.continue_response(event['requestId'], 200)) \ No newline at end of file + network.add_response_handler(callback=lambda event: network.continue_response(event['requestId'], 200)) + driver.get(url_for("basicAuth")) + assert driver.find_element_by_tag_name('h1').text == 'authorized' \ No newline at end of file From 9441cc8e4ca673c19d00a910328d7d8cb9e81755 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 15:10:52 -0500 Subject: [PATCH 21/77] added docstrings to all functions --- py/selenium/webdriver/common/bidi/network.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 0ae60e130f148..2f1fbbb59fc60 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -39,6 +39,7 @@ def __init__(self, conn): self.callbacks = {} def continue_response(self, request_id, status_code, headers=None, body=None): + """Continue after receiving a response.""" params = { 'requestId': request_id, 'status': status_code @@ -50,6 +51,7 @@ def continue_response(self, request_id, status_code, headers=None, body=None): self.conn.execute('network.continueResponse', params) def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): + """Continue after sending a request.""" params = { 'requestId': request_id } @@ -64,6 +66,7 @@ def continue_request(self, request_id, url=None, method=None, headers=None, post self.conn.execute('network.continueRequest', params) def add_intercept(self, phases=None, contexts=None, url_patterns=None): + """Add an intercept to the network.""" if phases is None: phases = [] params = { @@ -74,9 +77,11 @@ def add_intercept(self, phases=None, contexts=None, url_patterns=None): self.conn.execute('network.addIntercept', params) def remove_intercept(self, intercept): + """Remove an intercept from the network.""" self.conn.execute('network.removeIntercept', {'intercept': intercept}) def continue_with_auth(self, request_id, username, password): + """Continue with authentication.""" self.conn.execute( 'network.continueWithAuth', { @@ -91,11 +96,13 @@ def continue_with_auth(self, request_id, username, password): ) def on(self, event, callback): + """Set a callback function to subscribe to a network event.""" event = self.EVENTS.get(event, event) self.callbacks[event] = callback session_subscribe(self.conn, event, self.handle_event) def handle_event(self, event, data): + """Perform callback function on event.""" if event in self.callbacks: self.callbacks[event](data) From d92cf21b02fe27f98c95927b9e9a19e01ed7e503 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 17:26:53 -0500 Subject: [PATCH 22/77] abstracted out response/request... rewrote tests --- py/selenium/webdriver/common/bidi/network.py | 112 +++++++++++------ .../webdriver/common/bidi_network_tests.py | 113 +++++++++++------- 2 files changed, 142 insertions(+), 83 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 2f1fbbb59fc60..51f1463458133 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -38,34 +38,7 @@ def __init__(self, conn): self.conn = conn self.callbacks = {} - def continue_response(self, request_id, status_code, headers=None, body=None): - """Continue after receiving a response.""" - params = { - 'requestId': request_id, - 'status': status_code - } - if headers is not None: - params['headers'] = headers - if body is not None: - params['body'] = body - self.conn.execute('network.continueResponse', params) - - def continue_request(self, request_id, url=None, method=None, headers=None, postData=None): - """Continue after sending a request.""" - params = { - 'requestId': request_id - } - if url is not None: - params['url'] = url - if method is not None: - params['method'] = method - if headers is not None: - params['headers'] = headers - if postData is not None: - params['postData'] = postData - self.conn.execute('network.continueRequest', params) - - def add_intercept(self, phases=None, contexts=None, url_patterns=None): + def __add_intercept(self, phases=None, contexts=None, url_patterns=None): """Add an intercept to the network.""" if phases is None: phases = [] @@ -76,11 +49,11 @@ def add_intercept(self, phases=None, contexts=None, url_patterns=None): } self.conn.execute('network.addIntercept', params) - def remove_intercept(self, intercept): + def __remove_intercept(self, intercept): """Remove an intercept from the network.""" self.conn.execute('network.removeIntercept', {'intercept': intercept}) - def continue_with_auth(self, request_id, username, password): + def __continue_with_auth(self, request_id, username, password): """Continue with authentication.""" self.conn.execute( 'network.continueWithAuth', @@ -95,31 +68,94 @@ def continue_with_auth(self, request_id, username, password): } ) - def on(self, event, callback): + def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" event = self.EVENTS.get(event, event) self.callbacks[event] = callback session_subscribe(self.conn, event, self.handle_event) - def handle_event(self, event, data): + def __handle_event(self, event, data): """Perform callback function on event.""" if event in self.callbacks: self.callbacks[event](data) - def add_request_handler(self, url_pattern, callback): + def add_authentication_handler(self, username, password): + """Adds an authentication handler.""" + self.__add_intercept(phases=[self.PHASES['auth_required']]) + self.__on('auth_required', lambda data: self.__continue_with_auth(data['request']['request'], username, password)) + + def remove_authentication_handler(self): + """Removes an authentication handler.""" + self.__remove_intercept('auth_required') + +class Request: + def __init__(self, request_id, url, method, headers, body, network: Network): + self.request_id = request_id + self.url = url + self.method = method + self.headers = headers + self.body = body + self.network = network + + def __continue_request(self): + """Continue after sending a request.""" + params = { + 'requestId': self.request_id + } + if self.url is not None: + params['url'] = url + if self.method is not None: + params['method'] = method + if self.headers is not None: + params['headers'] = headers + if self.postData is not None: + params['postData'] = postData + self.network.conn.execute('network.continueRequest', params) + + def add_request_handler(self, callback, url_pattern=''): """Adds a request handler to perform a callback function on url_pattern match.""" - self.add_intercept(phases=[self.PHASES['before_request']]) + self.network.add_intercept(phases=[self.network.PHASES['before_request']]) def callback_on_url_match(data): if url_pattern in data['request']['url']: callback(data) - self.on('before_request', callback_on_url_match) + self.network.on('before_request', callback_on_url_match) + + def remove_request_handler(self): + """Removes a request handler.""" + self.network.remove_intercept('before_request') + +class Response: + def __init__(self, request_id, url, status_code, headers, body, network: Network): + self.request_id = request_id + self.url = url + self.status_code = status_code + self.headers = headers + self.body = body + self.network = network - def add_response_handler(self, url_pattern, callback): + def __continue_response(self): + """Continue after receiving a response.""" + params = { + 'requestId': self.request_id, + 'status': self.status_code + } + if self.headers is not None: + params['headers'] = headers + if self.body is not None: + params['body'] = body + self.network.conn.execute('network.continueResponse', params) + + + def add_response_handler(self, callback, url_pattern=''): """Adds a response handler to perform a callback function on url_pattern match.""" - self.add_intercept(phases=[self.PHASES['response_started']]) + self.network.add_intercept(phases=[self.network.PHASES['response_started']]) def callback_on_url_match(data): if url_pattern in data['response']['url']: callback(data) - self.on('response_started', callback_on_url_match) + self.network.on('response_started', callback_on_url_match) + + def remove_response_handler(self): + """Removes a response handler.""" + self.network.remove_intercept('response_started') \ No newline at end of file diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 1e31fdc8541c4..6e1f56262df4e 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -2,52 +2,75 @@ from selenium import webdriver from selenium.webdriver.common.bidi.network import Network +from selenium.webdriver.common.bidi.network import Response +from selenium.webdriver.common.bidi.network import Request -def url_for(page): - return webserver.where_is(page) - @pytest.fixture def network(driver): - return Network(driver) - -def test_add_intercept(driver, network): - network.add_intercept(phases=[Network.PHASES['before_request']]) - -def test_remove_intercept(driver, network): - intercept = network.add_intercept(phases=[Network.PHASES['before_request']]) - network.remove_intercept(intercept) - -def test_continue_response(driver, network): - network.add_intercept(phases=[Network.PHASES['before_request']]) - network.on('before_request', lambda event: network.continue_response(event['requestId'], 200)) - - driver.get(url_for("basicAuth")) - assert driver.find_element_by_tag_name('h1').text == 'authorized' - -def test_continue_request(driver, network): - network.add_intercept(phases=[Network.PHASES['before_request']]) - network.on('before_request', lambda event: network.continue_request(event['requestId'], url=url_for("basicAuth"))) - - driver.get(url_for("basicAuth")) - assert driver.find_element_by_tag_name('h1').text == 'authorized' - -def test_continue_with_auth(driver, network): - username = 'your_username' - password = 'your_password' - - network.add_intercept(phases=[Network.PHASES['auth_required']]) - network.on('auth_required', lambda event: network.continue_with_auth(event['requestId'], username, password)) - - driver.get(url_for("basicAuth")) - assert driver.find_element_by_tag_name('h1').text == 'authorized' - -def test_add_request_handler(driver, network): - network.add_request_handler(callback=lambda event: network.continue_request(event['requestId'], url=url_for("basicAuth"))) - driver.get(url_for("basicAuth")) - assert driver.find_element_by_tag_name('h1').text == 'authorized' - -def test_add_response_handler(driver, network): - network.add_response_handler(callback=lambda event: network.continue_response(event['requestId'], 200)) - driver.get(url_for("basicAuth")) - assert driver.find_element_by_tag_name('h1').text == 'authorized' \ No newline at end of file + yield Network(driver) + +@pytest.fixture +def network_response(network): + yield Response(network) + +@pytest.fixture +def network_request(network): + yield Request(network) + +def test_add_response_handler(response): + passed = [False] + + def callback(event): + if event['response']['status'] == 200: + passed[0] = True + + network_response.add_response_handler(callback) + pages.load("basicAuth") + assert passed[0] == True, "Callback was NOT successful" + +def test_remove_response_handler(response): + passed = [False] + + def callback(event): + if event['response']['status'] == 200: + passed[0] = True + + network_response.add_response_handler(callback) + network_response.remove_response_handler(callback) + pages.load("basicAuth") + assert passed[0] == False, "Callback was successful" + +def test_add_request_handler(request): + passed = [False] + + def callback(event): + if event['request']['method'] == 'GET': + passed[0] = True + + network_request.add_request_handler(callback) + pages.load("basicAuth") + assert passed[0] == True, "Callback was NOT successful" + +def test_remove_request_handler(request): + passed = [False] + + def callback(event): + if event['request']['method'] == 'GET': + passed[0] = True + + network_request.add_request_handler(callback) + network_request.remove_request_handler(callback) + pages.load("basicAuth") + assert passed[0] == False, "Callback was successful" + +def test_add_authentication_handler(network): + network.add_authentication_handler('test','test') + pages.load("basicAuth") + assert driver.find_element_by_tag_name('h1').text == 'authorized', "Authentication was NOT successful" + +def test_remove_authentication_handler(network): + network.add_authentication_handler('test', 'test') + network.remove_authentication_handler() + pages.load("basicAuth") + assert driver.find_element_by_tag_name('h1').text != 'authorized', "Authentication was successful" \ No newline at end of file From 6edb85910e74fa22e19f02642622098a975b8603 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Thu, 14 Nov 2024 18:17:21 -0500 Subject: [PATCH 23/77] Update network.py --- py/selenium/webdriver/common/bidi/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 51f1463458133..8a7b0e1c8ec1d 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -72,7 +72,7 @@ def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" event = self.EVENTS.get(event, event) self.callbacks[event] = callback - session_subscribe(self.conn, event, self.handle_event) + session_subscribe(self.conn, event, self.__handle_event) def __handle_event(self, event, data): """Perform callback function on event.""" @@ -158,4 +158,4 @@ def callback_on_url_match(data): def remove_response_handler(self): """Removes a response handler.""" - self.network.remove_intercept('response_started') \ No newline at end of file + self.network.remove_intercept('response_started') From f0beab850260f6fff194dd315e092695382fd588 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 21:45:30 -0500 Subject: [PATCH 24/77] refactored high-level api implementation --- py/selenium/webdriver/common/bidi/network.py | 71 +++++++++++-------- .../webdriver/common/bidi_network_tests.py | 38 +++++----- 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 8a7b0e1c8ec1d..b64360c1bf5d5 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -88,6 +88,46 @@ def remove_authentication_handler(self): """Removes an authentication handler.""" self.__remove_intercept('auth_required') + def add_request_handler(self, callback, url_pattern=''): + """Adds a request handler to perform a callback function on + url_pattern match.""" + self.__add_intercept(phases=[self.PHASES['before_request']]) + def callback_on_url_match(data): + if url_pattern in data['request']['url']: + # create request object to pass to callback + request_id = data['request'].get('requestId') + url = data['request'].get('url') + method = data['request'].get('method') + headers = data['request'].get('headers', {}) + body = data['request'].get('postData', None) + request = Request(request_id, url, method, headers, body, self) + callback(request) + self.__on('before_request', callback_on_url_match) + + def remove_request_handler(self): + """Removes a request handler.""" + self.network.remove_intercept('before_request') + + def add_response_handler(self, callback, url_pattern=''): + """Adds a response handler to perform a callback function on + url_pattern match.""" + self.__add_intercept(phases=[self.PHASES['response_started']]) + def callback_on_url_match(data): + # create response object to pass to callback + if url_pattern in data['response']['url']: + request_id = data['request'].get('requestId') + url = data['response'].get('url') + status_code = data['response'].get('status') + body = data['response'].get('body', None) + headers = data['response'].get('headers', {}) + response = Response(request_id, url, status_code, headers, body, self) + callback(data) + self.__on('response_started', callback_on_url_match) + + def remove_response_handler(self): + """Removes a response handler.""" + self.remove_intercept('response_started') + class Request: def __init__(self, request_id, url, method, headers, body, network: Network): self.request_id = request_id @@ -97,7 +137,7 @@ def __init__(self, request_id, url, method, headers, body, network: Network): self.body = body self.network = network - def __continue_request(self): + def continue_request(self): """Continue after sending a request.""" params = { 'requestId': self.request_id @@ -112,19 +152,6 @@ def __continue_request(self): params['postData'] = postData self.network.conn.execute('network.continueRequest', params) - def add_request_handler(self, callback, url_pattern=''): - """Adds a request handler to perform a callback function on - url_pattern match.""" - self.network.add_intercept(phases=[self.network.PHASES['before_request']]) - def callback_on_url_match(data): - if url_pattern in data['request']['url']: - callback(data) - self.network.on('before_request', callback_on_url_match) - - def remove_request_handler(self): - """Removes a request handler.""" - self.network.remove_intercept('before_request') - class Response: def __init__(self, request_id, url, status_code, headers, body, network: Network): self.request_id = request_id @@ -134,7 +161,7 @@ def __init__(self, request_id, url, status_code, headers, body, network: Network self.body = body self.network = network - def __continue_response(self): + def continue_response(self): """Continue after receiving a response.""" params = { 'requestId': self.request_id, @@ -145,17 +172,3 @@ def __continue_response(self): if self.body is not None: params['body'] = body self.network.conn.execute('network.continueResponse', params) - - - def add_response_handler(self, callback, url_pattern=''): - """Adds a response handler to perform a callback function on - url_pattern match.""" - self.network.add_intercept(phases=[self.network.PHASES['response_started']]) - def callback_on_url_match(data): - if url_pattern in data['response']['url']: - callback(data) - self.network.on('response_started', callback_on_url_match) - - def remove_response_handler(self): - """Removes a response handler.""" - self.network.remove_intercept('response_started') diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 6e1f56262df4e..4fe08c3065844 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -10,43 +10,38 @@ def network(driver): yield Network(driver) -@pytest.fixture -def network_response(network): - yield Response(network) - -@pytest.fixture -def network_request(network): - yield Request(network) - -def test_add_response_handler(response): +def test_add_response_handler(network): passed = [False] - def callback(event): - if event['response']['status'] == 200: + def callback(response): + if response.status_code == 200: passed[0] = True + response.continue_response() - network_response.add_response_handler(callback) + network.add_response_handler(callback) pages.load("basicAuth") assert passed[0] == True, "Callback was NOT successful" -def test_remove_response_handler(response): +def test_remove_response_handler(network): passed = [False] - def callback(event): - if event['response']['status'] == 200: + def callback(response): + if response.status_code == 200: passed[0] = True + response.continue_response() - network_response.add_response_handler(callback) - network_response.remove_response_handler(callback) + network.add_response_handler(callback) + network.remove_response_handler(callback) pages.load("basicAuth") assert passed[0] == False, "Callback was successful" def test_add_request_handler(request): passed = [False] - def callback(event): - if event['request']['method'] == 'GET': + def callback(request): + if request.method == 'GET': passed[0] = True + request.continue_request() network_request.add_request_handler(callback) pages.load("basicAuth") @@ -55,9 +50,10 @@ def callback(event): def test_remove_request_handler(request): passed = [False] - def callback(event): - if event['request']['method'] == 'GET': + def callback(request): + if request.method == 'GET': passed[0] = True + request.continue_request() network_request.add_request_handler(callback) network_request.remove_request_handler(callback) From aa49ee1c69a741fda219c9b3954edde6329b3f9a Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Thu, 14 Nov 2024 21:46:25 -0500 Subject: [PATCH 25/77] minor fix --- py/selenium/webdriver/common/bidi/network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index b64360c1bf5d5..fba4c89079909 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -106,7 +106,7 @@ def callback_on_url_match(data): def remove_request_handler(self): """Removes a request handler.""" - self.network.remove_intercept('before_request') + self.__remove_intercept('before_request') def add_response_handler(self, callback, url_pattern=''): """Adds a response handler to perform a callback function on @@ -126,7 +126,7 @@ def callback_on_url_match(data): def remove_response_handler(self): """Removes a response handler.""" - self.remove_intercept('response_started') + self.__remove_intercept('response_started') class Request: def __init__(self, request_id, url, method, headers, body, network: Network): From 6b8b17a2ead9c40f240240b8634a3a13615d1863 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 15 Nov 2024 12:41:32 -0500 Subject: [PATCH 26/77] New adjustments per @p0deje --- py/selenium/webdriver/common/bidi/network.py | 98 +++++++++++++++----- 1 file changed, 76 insertions(+), 22 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index fba4c89079909..f1e7aeb7056a8 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -37,6 +37,11 @@ class Network: def __init__(self, conn): self.conn = conn self.callbacks = {} + self.subscriptions = {} + + def has_callbacks(self): + """Checks if there are any callbacks set.""" + return len(self.callbacks) > 0 def __add_intercept(self, phases=None, contexts=None, url_patterns=None): """Add an intercept to the network.""" @@ -49,9 +54,14 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): } self.conn.execute('network.addIntercept', params) - def __remove_intercept(self, intercept): + def __remove_intercept(self, intercept=None, request_id=None): """Remove an intercept from the network.""" - self.conn.execute('network.removeIntercept', {'intercept': intercept}) + if request_id is not None: + self.conn.execute('network.removeIntercept', {'requestId': request_id}) + elif intercept is not None: + self.conn.execute('network.removeIntercept', {'intercept': intercept}) + else: + raise ValueError('Either requestId or intercept must be specified') def __continue_with_auth(self, request_id, username, password): """Continue with authentication.""" @@ -72,7 +82,8 @@ def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" event = self.EVENTS.get(event, event) self.callbacks[event] = callback - session_subscribe(self.conn, event, self.__handle_event) + if self.subscriptions[event] is None: + session_subscribe(self.conn, event, self.__handle_event) def __handle_event(self, event, data): """Perform callback function on event.""" @@ -83,14 +94,27 @@ def add_authentication_handler(self, username, password): """Adds an authentication handler.""" self.__add_intercept(phases=[self.PHASES['auth_required']]) self.__on('auth_required', lambda data: self.__continue_with_auth(data['request']['request'], username, password)) + self.subscriptions['auth_required'] = [username, password] - def remove_authentication_handler(self): + def remove_authentication_handler(self, username,): """Removes an authentication handler.""" - self.__remove_intercept('auth_required') + self.__remove_intercept(intercept='auth_required') + del self.subscriptions['auth_required'] + session_unsubscribe(self.conn, self.EVENTS['auth_required']) def add_request_handler(self, callback, url_pattern=''): - """Adds a request handler to perform a callback function on - url_pattern match.""" + """ + Adds a request handler that executes a callback function when a request matches the given URL pattern. + + Parameters: + callback (function): A function to be executed when url is matched by a URL pattern + The callback function receives a `Response` object as its argument. + url_pattern (str, optional): A substring to match against the response URL. + Default is an empty string, which matches all URLs. + + Returns: + str: The request ID of the intercepted response. + """ self.__add_intercept(phases=[self.PHASES['before_request']]) def callback_on_url_match(data): if url_pattern in data['request']['url']: @@ -103,14 +127,34 @@ def callback_on_url_match(data): request = Request(request_id, url, method, headers, body, self) callback(request) self.__on('before_request', callback_on_url_match) - - def remove_request_handler(self): + self.callbacks[request_id] = callback + if 'before_request' not in self.subscriptions or not self.subscriptions.get('before_request'): + self.subscriptions['before_request'] = [request_id] + else: + self.subscriptions['before_request'].append(request_id) + return request_id + + def remove_request_handler(self, request_id): """Removes a request handler.""" - self.__remove_intercept('before_request') + self.__remove_intercept(request_id=request_id) + self.subscriptions['before_request'].remove(request_id) + del self.callbacks[request_id] + if len(self.subscriptions['before_request']) == 0: + session_unsubscribe(self.conn, self.EVENTS['before_request']) def add_response_handler(self, callback, url_pattern=''): - """Adds a response handler to perform a callback function on - url_pattern match.""" + """ + Adds a response handler that executes a callback function when a response matches the given URL pattern. + + Parameters: + callback (function): A function to be executed when url is matched by a url_pattern + The callback function receives a `Response` object as its argument. + url_pattern (str, optional): A substring to match against the response URL. + Default is an empty string, which matches all URLs. + + Returns: + str: The request ID of the intercepted response. + """ self.__add_intercept(phases=[self.PHASES['response_started']]) def callback_on_url_match(data): # create response object to pass to callback @@ -121,12 +165,22 @@ def callback_on_url_match(data): body = data['response'].get('body', None) headers = data['response'].get('headers', {}) response = Response(request_id, url, status_code, headers, body, self) - callback(data) + callback(response) self.__on('response_started', callback_on_url_match) - - def remove_response_handler(self): + self.callbacks[request_id] = callback + if 'response_started' not in self.subscriptions or not self.subscriptions.get('response_started'): + self.subscriptions['response_started'] = [request_id] + else: + self.subscriptions['response_started'].append(request_id) + return request_id + + def remove_response_handler(self, response_id): """Removes a response handler.""" - self.__remove_intercept('response_started') + self.__remove_intercept(request_id=response_id) + self.subscriptions['response_started'].remove(response_id) + del self.callbacks[response_id] + if len(self.subscriptions['response_started']) == 0: + session_unsubscribe(self.conn, self.EVENTS['response_started']) class Request: def __init__(self, request_id, url, method, headers, body, network: Network): @@ -143,13 +197,13 @@ def continue_request(self): 'requestId': self.request_id } if self.url is not None: - params['url'] = url + params['url'] = self.url if self.method is not None: - params['method'] = method + params['method'] = self.method if self.headers is not None: - params['headers'] = headers + params['headers'] = self.headers if self.postData is not None: - params['postData'] = postData + params['postData'] = self.postData self.network.conn.execute('network.continueRequest', params) class Response: @@ -168,7 +222,7 @@ def continue_response(self): 'status': self.status_code } if self.headers is not None: - params['headers'] = headers + params['headers'] = self.headers if self.body is not None: - params['body'] = body + params['body'] = self.body self.network.conn.execute('network.continueResponse', params) From 7c3849dcb44d301fb71c6eccc7084d2089457d85 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:33:36 -0500 Subject: [PATCH 27/77] Updated assert text on failure --- py/test/selenium/webdriver/common/bidi_network_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 4fe08c3065844..c20d69e777266 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -33,7 +33,7 @@ def callback(response): network.add_response_handler(callback) network.remove_response_handler(callback) pages.load("basicAuth") - assert passed[0] == False, "Callback was successful" + assert passed[0] == False, "Callback should NOT be successful" def test_add_request_handler(request): passed = [False] @@ -58,7 +58,7 @@ def callback(request): network_request.add_request_handler(callback) network_request.remove_request_handler(callback) pages.load("basicAuth") - assert passed[0] == False, "Callback was successful" + assert passed[0] == False, "Callback should NOT be successful" def test_add_authentication_handler(network): network.add_authentication_handler('test','test') @@ -69,4 +69,4 @@ def test_remove_authentication_handler(network): network.add_authentication_handler('test', 'test') network.remove_authentication_handler() pages.load("basicAuth") - assert driver.find_element_by_tag_name('h1').text != 'authorized', "Authentication was successful" \ No newline at end of file + assert driver.find_element_by_tag_name('h1').text != 'authorized', "Authentication was successful" From c17308d91d3a8ea947a64e55c7eab23b6029c60f Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 17 Nov 2024 17:17:52 -0500 Subject: [PATCH 28/77] Added xfails for safari --- py/test/selenium/webdriver/common/bidi_network_tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index c20d69e777266..c63fc14f49473 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -10,6 +10,7 @@ def network(driver): yield Network(driver) +@pytest.mark.xfail_safari def test_add_response_handler(network): passed = [False] @@ -22,6 +23,7 @@ def callback(response): pages.load("basicAuth") assert passed[0] == True, "Callback was NOT successful" +@pytest.mark.xfail_safari def test_remove_response_handler(network): passed = [False] @@ -35,6 +37,7 @@ def callback(response): pages.load("basicAuth") assert passed[0] == False, "Callback should NOT be successful" +@pytest.mark.xfail_safari def test_add_request_handler(request): passed = [False] @@ -47,6 +50,7 @@ def callback(request): pages.load("basicAuth") assert passed[0] == True, "Callback was NOT successful" +@pytest.mark.xfail_safari def test_remove_request_handler(request): passed = [False] @@ -60,11 +64,13 @@ def callback(request): pages.load("basicAuth") assert passed[0] == False, "Callback should NOT be successful" +@pytest.mark.xfail_safari def test_add_authentication_handler(network): network.add_authentication_handler('test','test') pages.load("basicAuth") assert driver.find_element_by_tag_name('h1').text == 'authorized', "Authentication was NOT successful" +@pytest.mark.xfail_safari def test_remove_authentication_handler(network): network.add_authentication_handler('test', 'test') network.remove_authentication_handler() From 537f71c99322469318f303e57382b1596a56e5df Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:54:51 -0500 Subject: [PATCH 29/77] fixed tests --- py/test/selenium/webdriver/common/bidi_network_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index c63fc14f49473..331b51e2d8232 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -46,7 +46,7 @@ def callback(request): passed[0] = True request.continue_request() - network_request.add_request_handler(callback) + network.add_request_handler(callback) pages.load("basicAuth") assert passed[0] == True, "Callback was NOT successful" @@ -59,8 +59,8 @@ def callback(request): passed[0] = True request.continue_request() - network_request.add_request_handler(callback) - network_request.remove_request_handler(callback) + network.add_request_handler(callback) + network.remove_request_handler(callback) pages.load("basicAuth") assert passed[0] == False, "Callback should NOT be successful" From ab64ac01fc336e342b4be2fc4db3e15d54fe5061 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:09:11 -0500 Subject: [PATCH 30/77] Update bidi_network_tests.py --- py/test/selenium/webdriver/common/bidi_network_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 331b51e2d8232..a650b7b5b2752 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -38,7 +38,7 @@ def callback(response): assert passed[0] == False, "Callback should NOT be successful" @pytest.mark.xfail_safari -def test_add_request_handler(request): +def test_add_request_handler(network): passed = [False] def callback(request): @@ -51,7 +51,7 @@ def callback(request): assert passed[0] == True, "Callback was NOT successful" @pytest.mark.xfail_safari -def test_remove_request_handler(request): +def test_remove_request_handler(network): passed = [False] def callback(request): From 357025453f2bbef9aaad84430a840c57c09d502c Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 08:02:42 -0500 Subject: [PATCH 31/77] linting --- py/selenium/webdriver/common/bidi/network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index f1e7aeb7056a8..193b685bdfbcc 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -103,8 +103,8 @@ def remove_authentication_handler(self, username,): session_unsubscribe(self.conn, self.EVENTS['auth_required']) def add_request_handler(self, callback, url_pattern=''): - """ - Adds a request handler that executes a callback function when a request matches the given URL pattern. + """Adds a request handler that executes a callback function when a + request matches the given URL pattern. Parameters: callback (function): A function to be executed when url is matched by a URL pattern @@ -143,8 +143,8 @@ def remove_request_handler(self, request_id): session_unsubscribe(self.conn, self.EVENTS['before_request']) def add_response_handler(self, callback, url_pattern=''): - """ - Adds a response handler that executes a callback function when a response matches the given URL pattern. + """Adds a response handler that executes a callback function when a + response matches the given URL pattern. Parameters: callback (function): A function to be executed when url is matched by a url_pattern From 84a5df2a8413e4529e1328f820fc28760037a0ca Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 08:23:01 -0500 Subject: [PATCH 32/77] added back commands --- py/selenium/webdriver/remote/command.py | 5 +++++ py/selenium/webdriver/remote/remote_connection.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index b079ed5406f53..8252949a7a68c 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -26,6 +26,11 @@ class Command: https://w3c.github.io/webdriver/ """ + ADD_INTERCEPT: str = "network.addIntercept" + REMOVE_INTERCEPT: str = "network.removeIntercept" + CONTINUE_RESPONSE: str = "network.continueResponse" + CONTINUE_REQUEST: str = "network.continueRequest" + CONTINUE_WITH_AUTH: str = "network.continueWithAuth" NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 1bc432084b6e2..0228ad21cc2b8 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -36,6 +36,11 @@ LOGGER = logging.getLogger(__name__) remote_commands = { + Command.ADD_INTERCEPT: ("POST", "/session/$sessionId/network/intercept"), + Command.REMOVE_INTERCEPT: ("DELETE", "/session/$sessionId/network/intercept/$intercept"), + Command.CONTINUE_RESPONSE: ("POST", "/session/$sessionId/network/response/$requestId"), + Command.CONTINUE_REQUEST: ("POST", "/session/$sessionId/network/request/$requestId"), + Command.CONTINUE_WITH_AUTH: ("POST", "/session/$sessionId/network/auth"), Command.NEW_SESSION: ("POST", "/session"), Command.QUIT: ("DELETE", "/session/$sessionId"), Command.W3C_GET_CURRENT_WINDOW_HANDLE: ("GET", "/session/$sessionId/window"), From d10a364b2fb831c53a41b510f9ada6bb8bd1753b Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 10:16:23 -0500 Subject: [PATCH 33/77] linting --- py/selenium/webdriver/common/bidi/network.py | 4 ++-- .../webdriver/common/bidi_network_tests.py | 15 ++++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 193b685bdfbcc..7a1736900e168 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -119,7 +119,7 @@ def add_request_handler(self, callback, url_pattern=''): def callback_on_url_match(data): if url_pattern in data['request']['url']: # create request object to pass to callback - request_id = data['request'].get('requestId') + request_id = data['request'].get('requestId', None) url = data['request'].get('url') method = data['request'].get('method') headers = data['request'].get('headers', {}) @@ -159,7 +159,7 @@ def add_response_handler(self, callback, url_pattern=''): def callback_on_url_match(data): # create response object to pass to callback if url_pattern in data['response']['url']: - request_id = data['request'].get('requestId') + request_id = data['request'].get('requestId', None) url = data['response'].get('url') status_code = data['response'].get('status') body = data['response'].get('body', None) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index a650b7b5b2752..99d6d8c48b58d 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -1,9 +1,6 @@ import pytest -from selenium import webdriver from selenium.webdriver.common.bidi.network import Network -from selenium.webdriver.common.bidi.network import Response -from selenium.webdriver.common.bidi.network import Request @pytest.fixture @@ -21,7 +18,7 @@ def callback(response): network.add_response_handler(callback) pages.load("basicAuth") - assert passed[0] == True, "Callback was NOT successful" + assert passed[0], "Callback was NOT successful" @pytest.mark.xfail_safari def test_remove_response_handler(network): @@ -35,7 +32,7 @@ def callback(response): network.add_response_handler(callback) network.remove_response_handler(callback) pages.load("basicAuth") - assert passed[0] == False, "Callback should NOT be successful" + assert not passed[0], "Callback should NOT be successful" @pytest.mark.xfail_safari def test_add_request_handler(network): @@ -48,7 +45,7 @@ def callback(request): network.add_request_handler(callback) pages.load("basicAuth") - assert passed[0] == True, "Callback was NOT successful" + assert passed[0], "Callback was NOT successful" @pytest.mark.xfail_safari def test_remove_request_handler(network): @@ -62,16 +59,16 @@ def callback(request): network.add_request_handler(callback) network.remove_request_handler(callback) pages.load("basicAuth") - assert passed[0] == False, "Callback should NOT be successful" + assert not passed[0], "Callback should NOT be successful" @pytest.mark.xfail_safari -def test_add_authentication_handler(network): +def test_add_authentication_handler(driver, network): network.add_authentication_handler('test','test') pages.load("basicAuth") assert driver.find_element_by_tag_name('h1').text == 'authorized', "Authentication was NOT successful" @pytest.mark.xfail_safari -def test_remove_authentication_handler(network): +def test_remove_authentication_handler(driver, network): network.add_authentication_handler('test', 'test') network.remove_authentication_handler() pages.load("basicAuth") From 86ad50e40d09328f84d9db04131b4d9e389eb419 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 10:19:09 -0500 Subject: [PATCH 34/77] linting --- .../selenium/webdriver/common/bidi_network_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 99d6d8c48b58d..28844e78150df 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -8,7 +8,7 @@ def network(driver): yield Network(driver) @pytest.mark.xfail_safari -def test_add_response_handler(network): +def test_add_response_handler(network, pages): passed = [False] def callback(response): @@ -21,7 +21,7 @@ def callback(response): assert passed[0], "Callback was NOT successful" @pytest.mark.xfail_safari -def test_remove_response_handler(network): +def test_remove_response_handler(network, pages): passed = [False] def callback(response): @@ -35,7 +35,7 @@ def callback(response): assert not passed[0], "Callback should NOT be successful" @pytest.mark.xfail_safari -def test_add_request_handler(network): +def test_add_request_handler(network, pages): passed = [False] def callback(request): @@ -48,7 +48,7 @@ def callback(request): assert passed[0], "Callback was NOT successful" @pytest.mark.xfail_safari -def test_remove_request_handler(network): +def test_remove_request_handler(network, pages): passed = [False] def callback(request): @@ -62,13 +62,13 @@ def callback(request): assert not passed[0], "Callback should NOT be successful" @pytest.mark.xfail_safari -def test_add_authentication_handler(driver, network): +def test_add_authentication_handler(driver, network, pages): network.add_authentication_handler('test','test') pages.load("basicAuth") assert driver.find_element_by_tag_name('h1').text == 'authorized', "Authentication was NOT successful" @pytest.mark.xfail_safari -def test_remove_authentication_handler(driver, network): +def test_remove_authentication_handler(driver, network, pages): network.add_authentication_handler('test', 'test') network.remove_authentication_handler() pages.load("basicAuth") From 4b447350740af237d850d92629c198aea95b5629 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:00:29 -0500 Subject: [PATCH 35/77] nonlocalized request_id --- py/selenium/webdriver/common/bidi/network.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 7a1736900e168..a6cec98fc0888 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -116,7 +116,9 @@ def add_request_handler(self, callback, url_pattern=''): str: The request ID of the intercepted response. """ self.__add_intercept(phases=[self.PHASES['before_request']]) + request_id = None def callback_on_url_match(data): + nonlocal request_id if url_pattern in data['request']['url']: # create request object to pass to callback request_id = data['request'].get('requestId', None) @@ -156,8 +158,10 @@ def add_response_handler(self, callback, url_pattern=''): str: The request ID of the intercepted response. """ self.__add_intercept(phases=[self.PHASES['response_started']]) + request_id = None def callback_on_url_match(data): # create response object to pass to callback + nonlocal request_id if url_pattern in data['response']['url']: request_id = data['request'].get('requestId', None) url = data['response'].get('url') From 6bc315defb6ab1fe5831f42e7dca0c289193b8e0 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 11:57:32 -0500 Subject: [PATCH 36/77] replaced fixture with driver.network --- .../webdriver/common/bidi_network_tests.py | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 28844e78150df..27382b8c542ae 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -3,12 +3,8 @@ from selenium.webdriver.common.bidi.network import Network -@pytest.fixture -def network(driver): - yield Network(driver) - @pytest.mark.xfail_safari -def test_add_response_handler(network, pages): +def test_add_response_handler(driver, pages): passed = [False] def callback(response): @@ -16,12 +12,12 @@ def callback(response): passed[0] = True response.continue_response() - network.add_response_handler(callback) + driver.network.add_response_handler(callback) pages.load("basicAuth") assert passed[0], "Callback was NOT successful" @pytest.mark.xfail_safari -def test_remove_response_handler(network, pages): +def test_remove_response_handler(driver, pages): passed = [False] def callback(response): @@ -29,13 +25,13 @@ def callback(response): passed[0] = True response.continue_response() - network.add_response_handler(callback) - network.remove_response_handler(callback) + driver.network.add_response_handler(callback) + driver.network.remove_response_handler(callback) pages.load("basicAuth") assert not passed[0], "Callback should NOT be successful" @pytest.mark.xfail_safari -def test_add_request_handler(network, pages): +def test_add_request_handler(driver, pages): passed = [False] def callback(request): @@ -43,12 +39,12 @@ def callback(request): passed[0] = True request.continue_request() - network.add_request_handler(callback) + driver.network.add_request_handler(callback) pages.load("basicAuth") assert passed[0], "Callback was NOT successful" @pytest.mark.xfail_safari -def test_remove_request_handler(network, pages): +def test_remove_request_handler(driver, pages): passed = [False] def callback(request): @@ -56,20 +52,20 @@ def callback(request): passed[0] = True request.continue_request() - network.add_request_handler(callback) - network.remove_request_handler(callback) + driver.network.add_request_handler(callback) + driver.network.remove_request_handler(callback) pages.load("basicAuth") assert not passed[0], "Callback should NOT be successful" @pytest.mark.xfail_safari -def test_add_authentication_handler(driver, network, pages): - network.add_authentication_handler('test','test') +def test_add_authentication_handler(driver, pages): + driver.network.add_authentication_handler('test','test') pages.load("basicAuth") assert driver.find_element_by_tag_name('h1').text == 'authorized', "Authentication was NOT successful" @pytest.mark.xfail_safari -def test_remove_authentication_handler(driver, network, pages): - network.add_authentication_handler('test', 'test') - network.remove_authentication_handler() +def test_remove_authentication_handler(driver, pages): + driver.network.add_authentication_handler('test', 'test') + driver.network.remove_authentication_handler() pages.load("basicAuth") assert driver.find_element_by_tag_name('h1').text != 'authorized', "Authentication was successful" From 9673a698a6cce6fa5310d5d2d3d8caf494393006 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 12:17:46 -0500 Subject: [PATCH 37/77] updated webdriver.py to intiailize websocket if none --- py/selenium/webdriver/remote/webdriver.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index cf6ef4b9b224b..ae32a5a673504 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -1106,8 +1106,12 @@ def _start_bidi(self): @property def network(self): + if not self._websocket_connection: + self._start_bidi() + if not hasattr(self, '_network'): self._network = Network(self._websocket_connection) + return self._network def _get_cdp_details(self): From fc80258222fe6a350b98361bbdff87e07cbdc888 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 12:31:48 -0500 Subject: [PATCH 38/77] updated tests --- py/test/selenium/webdriver/common/bidi_network_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 27382b8c542ae..ec13f7ed23739 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -1,7 +1,9 @@ import pytest -from selenium.webdriver.common.bidi.network import Network +@pytest.mark.xfail_safari +def test_network_initialized(driver): + assert driver.network is not None @pytest.mark.xfail_safari def test_add_response_handler(driver, pages): From 5eea5d07dc761c21d9110ce893ba24a562eb4502 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 12:32:51 -0500 Subject: [PATCH 39/77] added license --- .../webdriver/common/bidi_network_tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index ec13f7ed23739..edcab4723c091 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -1,3 +1,20 @@ +# 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. + import pytest From 28eca14290ef113c6fb809b19b7026925470d69e Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 13:28:13 -0500 Subject: [PATCH 40/77] update serialize and network --- py/selenium/webdriver/common/bidi/network.py | 39 +++++++++++-------- py/selenium/webdriver/remote/webdriver.py | 2 +- .../webdriver/remote/websocket_connection.py | 5 ++- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index a6cec98fc0888..20d0ee03e0944 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -52,31 +52,34 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): 'contexts': contexts, 'urlPatterns': url_patterns } - self.conn.execute('network.addIntercept', params) + command = {'command': 'network.addIntercept', 'params': params} + self.conn.execute() def __remove_intercept(self, intercept=None, request_id=None): """Remove an intercept from the network.""" if request_id is not None: - self.conn.execute('network.removeIntercept', {'requestId': request_id}) + command = {'command': 'network.removeIntercept', 'requestId': request_id} + self.conn.execute(command) elif intercept is not None: - self.conn.execute('network.removeIntercept', {'intercept': intercept}) + command = {'command': 'network.removeIntercept', 'intercept': intercept} + self.conn.execute(command) else: raise ValueError('Either requestId or intercept must be specified') def __continue_with_auth(self, request_id, username, password): """Continue with authentication.""" - self.conn.execute( - 'network.continueWithAuth', - { - 'request': request_id, - 'action': 'provideCredentials', - 'credentials': { - 'type': 'password', - 'username': username, - 'password': password - } - } - ) + command = {'command': 'network.continueWithAuth', 'params': + { + 'request': request_id, + 'action': 'provideCredentials', + 'credentials': { + 'type': 'password', + 'username': username, + 'password': password + } + } + } + self.conn.execute(command) def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" @@ -208,7 +211,8 @@ def continue_request(self): params['headers'] = self.headers if self.postData is not None: params['postData'] = self.postData - self.network.conn.execute('network.continueRequest', params) + command = {'command': 'network.continueRequest', 'params': params} + self.network.conn.execute(command) class Response: def __init__(self, request_id, url, status_code, headers, body, network: Network): @@ -229,4 +233,5 @@ def continue_response(self): params['headers'] = self.headers if self.body is not None: params['body'] = self.body - self.network.conn.execute('network.continueResponse', params) + command = {'command': 'network.continueResponse', 'params': params} + self.network.conn.execute(command) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index ae32a5a673504..25b6b7d43ff15 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -1109,7 +1109,7 @@ def network(self): if not self._websocket_connection: self._start_bidi() - if not hasattr(self, '_network'): + if not hasattr(self, '_network') or self._network is None: self._network = Network(self._websocket_connection) return self._network diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 3afbba46d5e1e..35a24a1b7860c 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -91,7 +91,10 @@ def remove_callback(self, event, callback_id): return def _serialize_command(self, command): - return next(command) + if isinstance(command, dict): + return command + else: + return next(command) def _deserialize_result(self, result, command): try: From 26170fb862543a33e5fdca9673636be1dbec5c98 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:04:58 -0500 Subject: [PATCH 41/77] Update websocket_connection.py --- py/selenium/webdriver/remote/websocket_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 35a24a1b7860c..0c115b5be36bb 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -94,7 +94,7 @@ def _serialize_command(self, command): if isinstance(command, dict): return command else: - return next(command) + return dict(command) def _deserialize_result(self, result, command): try: From 45f1cdae5d1624b352b7e1a8108909b37fed8ecb Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Mon, 18 Nov 2024 14:05:15 -0500 Subject: [PATCH 42/77] Update network.py --- py/selenium/webdriver/common/bidi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 20d0ee03e0944..e1cbe8b9b10ef 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -53,7 +53,7 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): 'urlPatterns': url_patterns } command = {'command': 'network.addIntercept', 'params': params} - self.conn.execute() + self.conn.execute(command) def __remove_intercept(self, intercept=None, request_id=None): """Remove an intercept from the network.""" From 4a32ff738d8ca936586efdaa16996b00de88944d Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 18 Nov 2024 17:33:25 -0500 Subject: [PATCH 43/77] got network to intialize and browser to recognize commands, issue with getting callbacks to call --- py/selenium/webdriver/common/bidi/network.py | 71 +++++++++++++------ .../webdriver/remote/websocket_connection.py | 5 +- .../webdriver/common/bidi_network_tests.py | 26 ++++--- 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index e1cbe8b9b10ef..09535805544a3 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -37,7 +37,17 @@ class Network: def __init__(self, conn): self.conn = conn self.callbacks = {} - self.subscriptions = {} + self.subscriptions = { + 'network.responseStarted': [], + 'network.beforeRequestSent': [], + 'network.authRequired': [] + } + + + def command_iterator(self, command): + """Generator to yield command.""" + yield command + return def has_callbacks(self): """Checks if there are any callbacks set.""" @@ -47,28 +57,43 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): """Add an intercept to the network.""" if phases is None: phases = [] - params = { - 'phases': phases, - 'contexts': contexts, - 'urlPatterns': url_patterns - } - command = {'command': 'network.addIntercept', 'params': params} - self.conn.execute(command) + if contexts is None and url_patterns is None: + params = { + 'phases': phases, + } + elif contexts is None: + params = { + 'phases': phases, + 'urlPatterns': url_patterns + } + elif url_patterns is None: + params = { + 'phases': phases, + 'contexts': contexts + } + else: + params = { + 'phases': phases, + 'contexts': contexts, + 'urlPatterns': url_patterns + } + command = {'method': 'network.addIntercept', 'params': params} + self.conn.execute(self.command_iterator(command)) def __remove_intercept(self, intercept=None, request_id=None): """Remove an intercept from the network.""" if request_id is not None: - command = {'command': 'network.removeIntercept', 'requestId': request_id} - self.conn.execute(command) + command = {'method': 'network.removeIntercept', 'requestId': request_id} + self.conn.execute(self.command_iterator(command)) elif intercept is not None: - command = {'command': 'network.removeIntercept', 'intercept': intercept} - self.conn.execute(command) + command = {'method': 'network.removeIntercept', 'intercept': intercept} + self.conn.execute(self.command_iterator(command)) else: raise ValueError('Either requestId or intercept must be specified') def __continue_with_auth(self, request_id, username, password): """Continue with authentication.""" - command = {'command': 'network.continueWithAuth', 'params': + command = {'method': 'network.continueWithAuth', 'params': { 'request': request_id, 'action': 'provideCredentials', @@ -79,13 +104,13 @@ def __continue_with_auth(self, request_id, username, password): } } } - self.conn.execute(command) + self.conn.execute(self.command_iterator(command)) def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" event = self.EVENTS.get(event, event) self.callbacks[event] = callback - if self.subscriptions[event] is None: + if len(self.subscriptions[event]) == 0: session_subscribe(self.conn, event, self.__handle_event) def __handle_event(self, event, data): @@ -99,7 +124,7 @@ def add_authentication_handler(self, username, password): self.__on('auth_required', lambda data: self.__continue_with_auth(data['request']['request'], username, password)) self.subscriptions['auth_required'] = [username, password] - def remove_authentication_handler(self, username,): + def remove_authentication_handler(self): """Removes an authentication handler.""" self.__remove_intercept(intercept='auth_required') del self.subscriptions['auth_required'] @@ -128,7 +153,7 @@ def callback_on_url_match(data): url = data['request'].get('url') method = data['request'].get('method') headers = data['request'].get('headers', {}) - body = data['request'].get('postData', None) + body = data['request'].get('body', None) request = Request(request_id, url, method, headers, body, self) callback(request) self.__on('before_request', callback_on_url_match) @@ -209,10 +234,10 @@ def continue_request(self): params['method'] = self.method if self.headers is not None: params['headers'] = self.headers - if self.postData is not None: - params['postData'] = self.postData - command = {'command': 'network.continueRequest', 'params': params} - self.network.conn.execute(command) + if self.body is not None: + params['body'] = self.body + command = {'method': 'network.continueRequest', 'params': params} + self.network.conn.execute(self.command_iterator(command)) class Response: def __init__(self, request_id, url, status_code, headers, body, network: Network): @@ -233,5 +258,5 @@ def continue_response(self): params['headers'] = self.headers if self.body is not None: params['body'] = self.body - command = {'command': 'network.continueResponse', 'params': params} - self.network.conn.execute(command) + command = {'method': 'network.continueResponse', 'params': params} + self.network.conn.execute(self.command_iterator(command)) diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 0c115b5be36bb..3afbba46d5e1e 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -91,10 +91,7 @@ def remove_callback(self, event, callback_id): return def _serialize_command(self, command): - if isinstance(command, dict): - return command - else: - return dict(command) + return next(command) def _deserialize_result(self, result, command): try: diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index edcab4723c091..327d2de8ddce2 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -17,6 +17,8 @@ import pytest +from selenium.webdriver.common.by import By + @pytest.mark.xfail_safari def test_network_initialized(driver): @@ -27,8 +29,7 @@ def test_add_response_handler(driver, pages): passed = [False] def callback(response): - if response.status_code == 200: - passed[0] = True + passed[0] = True response.continue_response() driver.network.add_response_handler(callback) @@ -40,12 +41,11 @@ def test_remove_response_handler(driver, pages): passed = [False] def callback(response): - if response.status_code == 200: - passed[0] = True + passed[0] = True response.continue_response() - driver.network.add_response_handler(callback) - driver.network.remove_response_handler(callback) + test_response_id = driver.network.add_response_handler(callback) + driver.network.remove_response_handler(response_id=test_response_id) pages.load("basicAuth") assert not passed[0], "Callback should NOT be successful" @@ -54,8 +54,7 @@ def test_add_request_handler(driver, pages): passed = [False] def callback(request): - if request.method == 'GET': - passed[0] = True + passed[0] = True request.continue_request() driver.network.add_request_handler(callback) @@ -67,12 +66,11 @@ def test_remove_request_handler(driver, pages): passed = [False] def callback(request): - if request.method == 'GET': - passed[0] = True + passed[0] = True request.continue_request() - driver.network.add_request_handler(callback) - driver.network.remove_request_handler(callback) + test_request_id = driver.network.add_request_handler(callback) + driver.network.remove_request_handler(request_id=test_request_id) pages.load("basicAuth") assert not passed[0], "Callback should NOT be successful" @@ -80,11 +78,11 @@ def callback(request): def test_add_authentication_handler(driver, pages): driver.network.add_authentication_handler('test','test') pages.load("basicAuth") - assert driver.find_element_by_tag_name('h1').text == 'authorized', "Authentication was NOT successful" + assert driver.find_element(By.TAG_NAME, 'h1').text == 'authorized', "Authentication was NOT successful" @pytest.mark.xfail_safari def test_remove_authentication_handler(driver, pages): driver.network.add_authentication_handler('test', 'test') driver.network.remove_authentication_handler() pages.load("basicAuth") - assert driver.find_element_by_tag_name('h1').text != 'authorized', "Authentication was successful" + assert driver.find_element(By.TAG_NAME, 'h1').text != 'authorized', "Authentication was successful" From 35e7ac3d4c7f5bd1433ef66a665a054352dbbbb7 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Sat, 21 Dec 2024 12:45:38 -0500 Subject: [PATCH 44/77] don't need to explicitly return there --- py/selenium/webdriver/common/bidi/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 09535805544a3..af8f844039661 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -47,7 +47,6 @@ def __init__(self, conn): def command_iterator(self, command): """Generator to yield command.""" yield command - return def has_callbacks(self): """Checks if there are any callbacks set.""" From 46ef051aad72469cb6470ca9119dd30e37109595 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Sun, 22 Dec 2024 14:20:03 -0500 Subject: [PATCH 45/77] switched to using add_callback --- py/selenium/webdriver/common/bidi/network.py | 3 ++- py/selenium/webdriver/remote/websocket_connection.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index af8f844039661..f7874f6359358 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -110,7 +110,8 @@ def __on(self, event, callback): event = self.EVENTS.get(event, event) self.callbacks[event] = callback if len(self.subscriptions[event]) == 0: - session_subscribe(self.conn, event, self.__handle_event) + # session_subscribe(self.conn, event, self.__handle_event) + self.conn.add_callback(event, self.__handle_event) def __handle_event(self, event, data): """Perform callback function on event.""" diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 3afbba46d5e1e..04b0ea93df239 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -70,7 +70,7 @@ def execute(self, command): return self._deserialize_result(result, command) def add_callback(self, event, callback): - event_name = event.event_class + event_name = event.event_class if hasattr(event, 'event_class') else event if event_name not in self.callbacks: self.callbacks[event_name] = [] From 1e9dc81dfb1869974e80db13a91ecad3a371a1da Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 23 Dec 2024 14:48:21 -0500 Subject: [PATCH 46/77] unified callback_id and request/response_id --- py/selenium/webdriver/common/bidi/network.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index f7874f6359358..3932f4405cb1e 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -111,7 +111,7 @@ def __on(self, event, callback): self.callbacks[event] = callback if len(self.subscriptions[event]) == 0: # session_subscribe(self.conn, event, self.__handle_event) - self.conn.add_callback(event, self.__handle_event) + return self.conn.add_callback(event, self.__handle_event) def __handle_event(self, event, data): """Perform callback function on event.""" @@ -156,8 +156,9 @@ def callback_on_url_match(data): body = data['request'].get('body', None) request = Request(request_id, url, method, headers, body, self) callback(request) - self.__on('before_request', callback_on_url_match) - self.callbacks[request_id] = callback + callback_id = self.__on('before_request', callback_on_url_match) + request_id = callback_id + self.callbacks[callback_id] = callback if 'before_request' not in self.subscriptions or not self.subscriptions.get('before_request'): self.subscriptions['before_request'] = [request_id] else: @@ -198,8 +199,9 @@ def callback_on_url_match(data): headers = data['response'].get('headers', {}) response = Response(request_id, url, status_code, headers, body, self) callback(response) - self.__on('response_started', callback_on_url_match) - self.callbacks[request_id] = callback + callback_id = self.__on('response_started', callback_on_url_match) + request_id = callback_id + self.callbacks[callback_id] = callback if 'response_started' not in self.subscriptions or not self.subscriptions.get('response_started'): self.subscriptions['response_started'] = [request_id] else: From 29a5af98d8ae4ed1a88dd479f3b61685222336d5 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:25:15 -0500 Subject: [PATCH 47/77] removed wasted memort --- py/selenium/webdriver/common/bidi/network.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 3932f4405cb1e..685f69e83ac4f 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -156,9 +156,8 @@ def callback_on_url_match(data): body = data['request'].get('body', None) request = Request(request_id, url, method, headers, body, self) callback(request) - callback_id = self.__on('before_request', callback_on_url_match) - request_id = callback_id - self.callbacks[callback_id] = callback + request_id = self.__on('before_request', callback_on_url_match) + self.callbacks[request_id] = callback if 'before_request' not in self.subscriptions or not self.subscriptions.get('before_request'): self.subscriptions['before_request'] = [request_id] else: @@ -199,9 +198,8 @@ def callback_on_url_match(data): headers = data['response'].get('headers', {}) response = Response(request_id, url, status_code, headers, body, self) callback(response) - callback_id = self.__on('response_started', callback_on_url_match) - request_id = callback_id - self.callbacks[callback_id] = callback + request_id = self.__on('response_started', callback_on_url_match) + self.callbacks[request_id] = callback if 'response_started' not in self.subscriptions or not self.subscriptions.get('response_started'): self.subscriptions['response_started'] = [request_id] else: From b7be10d04bb1eda1ceb710827dd3f017dbb3a718 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 24 Dec 2024 12:49:36 -0500 Subject: [PATCH 48/77] removed nonlocal usage --- py/selenium/webdriver/common/bidi/network.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 685f69e83ac4f..34b40280f0f08 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -144,12 +144,10 @@ def add_request_handler(self, callback, url_pattern=''): str: The request ID of the intercepted response. """ self.__add_intercept(phases=[self.PHASES['before_request']]) - request_id = None def callback_on_url_match(data): - nonlocal request_id if url_pattern in data['request']['url']: # create request object to pass to callback - request_id = data['request'].get('requestId', None) + request_id = data['request'].get('requestId', uuid.uuid4()) url = data['request'].get('url') method = data['request'].get('method') headers = data['request'].get('headers', {}) @@ -186,12 +184,10 @@ def add_response_handler(self, callback, url_pattern=''): str: The request ID of the intercepted response. """ self.__add_intercept(phases=[self.PHASES['response_started']]) - request_id = None def callback_on_url_match(data): # create response object to pass to callback - nonlocal request_id if url_pattern in data['response']['url']: - request_id = data['request'].get('requestId', None) + request_id = data['request'].get('requestId', uuid.uuid4()) url = data['response'].get('url') status_code = data['response'].get('status') body = data['response'].get('body', None) From ea577f574dd61ee423b2d1c909eb1e5c9dd1e260 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 11 Feb 2025 22:46:07 -0500 Subject: [PATCH 49/77] added import, gave abstractions command_iterator class, added .to_json() --- py/selenium/webdriver/common/bidi/network.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 34b40280f0f08..203a2f4a16568 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +import uuid from .session import session_subscribe from .session import session_unsubscribe @@ -46,7 +47,7 @@ def __init__(self, conn): def command_iterator(self, command): """Generator to yield command.""" - yield command + yield command.to_json() def has_callbacks(self): """Checks if there are any callbacks set.""" @@ -235,6 +236,10 @@ def continue_request(self): command = {'method': 'network.continueRequest', 'params': params} self.network.conn.execute(self.command_iterator(command)) + def command_iterator(self, command): + """Generator to yield command.""" + yield command.to_json() + class Response: def __init__(self, request_id, url, status_code, headers, body, network: Network): self.request_id = request_id @@ -256,3 +261,7 @@ def continue_response(self): params['body'] = self.body command = {'method': 'network.continueResponse', 'params': params} self.network.conn.execute(self.command_iterator(command)) + + def command_iterator(self, command): + """Generator to yield command.""" + yield command.to_json() From 994fe951bfc43864b1028ccd01980f6d1f229c42 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 11 Feb 2025 22:48:32 -0500 Subject: [PATCH 50/77] added session_subscribe() to __on() --- py/selenium/webdriver/common/bidi/network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 203a2f4a16568..a30d9e2dd5e0c 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -108,6 +108,7 @@ def __continue_with_auth(self, request_id, username, password): def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" + self.conn.execute(session_subscribe(event)) event = self.EVENTS.get(event, event) self.callbacks[event] = callback if len(self.subscriptions[event]) == 0: From 5c5c0aa648be7ec58aee83daccc8fa93fc5bc7c8 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 11 Feb 2025 22:49:16 -0500 Subject: [PATCH 51/77] shift ooo --- py/selenium/webdriver/common/bidi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index a30d9e2dd5e0c..4abd7eb504fd8 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -108,8 +108,8 @@ def __continue_with_auth(self, request_id, username, password): def __on(self, event, callback): """Set a callback function to subscribe to a network event.""" - self.conn.execute(session_subscribe(event)) event = self.EVENTS.get(event, event) + self.conn.execute(session_subscribe(event)) self.callbacks[event] = callback if len(self.subscriptions[event]) == 0: # session_subscribe(self.conn, event, self.__handle_event) From d53dc259af237ac15baef5f12d1ee3f03d5de74a Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 11 Feb 2025 23:03:54 -0500 Subject: [PATCH 52/77] format.sh --- py/selenium/webdriver/common/bidi/network.py | 172 +++++++++--------- py/selenium/webdriver/remote/webdriver.py | 2 +- .../webdriver/remote/websocket_connection.py | 2 +- .../webdriver/common/bidi_network_tests.py | 14 +- 4 files changed, 93 insertions(+), 97 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 4abd7eb504fd8..7d1721cceee2e 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -16,35 +16,35 @@ # under the License. import uuid + from .session import session_subscribe from .session import session_unsubscribe class Network: EVENTS = { - 'before_request': 'network.beforeRequestSent', - 'response_started': 'network.responseStarted', - 'response_completed': 'network.responseCompleted', - 'auth_required': 'network.authRequired', - 'fetch_error': 'network.fetchError' + "before_request": "network.beforeRequestSent", + "response_started": "network.responseStarted", + "response_completed": "network.responseCompleted", + "auth_required": "network.authRequired", + "fetch_error": "network.fetchError", } PHASES = { - 'before_request': 'beforeRequestSent', - 'response_started': 'responseStarted', - 'auth_required': 'authRequired' + "before_request": "beforeRequestSent", + "response_started": "responseStarted", + "auth_required": "authRequired", } def __init__(self, conn): self.conn = conn self.callbacks = {} self.subscriptions = { - 'network.responseStarted': [], - 'network.beforeRequestSent': [], - 'network.authRequired': [] + "network.responseStarted": [], + "network.beforeRequestSent": [], + "network.authRequired": [], } - def command_iterator(self, command): """Generator to yield command.""" yield command.to_json() @@ -59,50 +59,37 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): phases = [] if contexts is None and url_patterns is None: params = { - 'phases': phases, + "phases": phases, } elif contexts is None: - params = { - 'phases': phases, - 'urlPatterns': url_patterns - } + params = {"phases": phases, "urlPatterns": url_patterns} elif url_patterns is None: - params = { - 'phases': phases, - 'contexts': contexts - } + params = {"phases": phases, "contexts": contexts} else: - params = { - 'phases': phases, - 'contexts': contexts, - 'urlPatterns': url_patterns - } - command = {'method': 'network.addIntercept', 'params': params} + params = {"phases": phases, "contexts": contexts, "urlPatterns": url_patterns} + command = {"method": "network.addIntercept", "params": params} self.conn.execute(self.command_iterator(command)) def __remove_intercept(self, intercept=None, request_id=None): """Remove an intercept from the network.""" if request_id is not None: - command = {'method': 'network.removeIntercept', 'requestId': request_id} + command = {"method": "network.removeIntercept", "requestId": request_id} self.conn.execute(self.command_iterator(command)) elif intercept is not None: - command = {'method': 'network.removeIntercept', 'intercept': intercept} + command = {"method": "network.removeIntercept", "intercept": intercept} self.conn.execute(self.command_iterator(command)) else: - raise ValueError('Either requestId or intercept must be specified') + raise ValueError("Either requestId or intercept must be specified") def __continue_with_auth(self, request_id, username, password): """Continue with authentication.""" - command = {'method': 'network.continueWithAuth', 'params': - { - 'request': request_id, - 'action': 'provideCredentials', - 'credentials': { - 'type': 'password', - 'username': username, - 'password': password - } - } + command = { + "method": "network.continueWithAuth", + "params": { + "request": request_id, + "action": "provideCredentials", + "credentials": {"type": "password", "username": username, "password": password}, + }, } self.conn.execute(self.command_iterator(command)) @@ -122,17 +109,19 @@ def __handle_event(self, event, data): def add_authentication_handler(self, username, password): """Adds an authentication handler.""" - self.__add_intercept(phases=[self.PHASES['auth_required']]) - self.__on('auth_required', lambda data: self.__continue_with_auth(data['request']['request'], username, password)) - self.subscriptions['auth_required'] = [username, password] + self.__add_intercept(phases=[self.PHASES["auth_required"]]) + self.__on( + "auth_required", lambda data: self.__continue_with_auth(data["request"]["request"], username, password) + ) + self.subscriptions["auth_required"] = [username, password] def remove_authentication_handler(self): """Removes an authentication handler.""" - self.__remove_intercept(intercept='auth_required') - del self.subscriptions['auth_required'] - session_unsubscribe(self.conn, self.EVENTS['auth_required']) + self.__remove_intercept(intercept="auth_required") + del self.subscriptions["auth_required"] + session_unsubscribe(self.conn, self.EVENTS["auth_required"]) - def add_request_handler(self, callback, url_pattern=''): + def add_request_handler(self, callback, url_pattern=""): """Adds a request handler that executes a callback function when a request matches the given URL pattern. @@ -145,34 +134,36 @@ def add_request_handler(self, callback, url_pattern=''): Returns: str: The request ID of the intercepted response. """ - self.__add_intercept(phases=[self.PHASES['before_request']]) + self.__add_intercept(phases=[self.PHASES["before_request"]]) + def callback_on_url_match(data): - if url_pattern in data['request']['url']: + if url_pattern in data["request"]["url"]: # create request object to pass to callback - request_id = data['request'].get('requestId', uuid.uuid4()) - url = data['request'].get('url') - method = data['request'].get('method') - headers = data['request'].get('headers', {}) - body = data['request'].get('body', None) + request_id = data["request"].get("requestId", uuid.uuid4()) + url = data["request"].get("url") + method = data["request"].get("method") + headers = data["request"].get("headers", {}) + body = data["request"].get("body", None) request = Request(request_id, url, method, headers, body, self) callback(request) - request_id = self.__on('before_request', callback_on_url_match) + + request_id = self.__on("before_request", callback_on_url_match) self.callbacks[request_id] = callback - if 'before_request' not in self.subscriptions or not self.subscriptions.get('before_request'): - self.subscriptions['before_request'] = [request_id] + if "before_request" not in self.subscriptions or not self.subscriptions.get("before_request"): + self.subscriptions["before_request"] = [request_id] else: - self.subscriptions['before_request'].append(request_id) + self.subscriptions["before_request"].append(request_id) return request_id def remove_request_handler(self, request_id): """Removes a request handler.""" self.__remove_intercept(request_id=request_id) - self.subscriptions['before_request'].remove(request_id) + self.subscriptions["before_request"].remove(request_id) del self.callbacks[request_id] - if len(self.subscriptions['before_request']) == 0: - session_unsubscribe(self.conn, self.EVENTS['before_request']) + if len(self.subscriptions["before_request"]) == 0: + session_unsubscribe(self.conn, self.EVENTS["before_request"]) - def add_response_handler(self, callback, url_pattern=''): + def add_response_handler(self, callback, url_pattern=""): """Adds a response handler that executes a callback function when a response matches the given URL pattern. @@ -185,32 +176,35 @@ def add_response_handler(self, callback, url_pattern=''): Returns: str: The request ID of the intercepted response. """ - self.__add_intercept(phases=[self.PHASES['response_started']]) + self.__add_intercept(phases=[self.PHASES["response_started"]]) + def callback_on_url_match(data): # create response object to pass to callback - if url_pattern in data['response']['url']: - request_id = data['request'].get('requestId', uuid.uuid4()) - url = data['response'].get('url') - status_code = data['response'].get('status') - body = data['response'].get('body', None) - headers = data['response'].get('headers', {}) + if url_pattern in data["response"]["url"]: + request_id = data["request"].get("requestId", uuid.uuid4()) + url = data["response"].get("url") + status_code = data["response"].get("status") + body = data["response"].get("body", None) + headers = data["response"].get("headers", {}) response = Response(request_id, url, status_code, headers, body, self) callback(response) - request_id = self.__on('response_started', callback_on_url_match) + + request_id = self.__on("response_started", callback_on_url_match) self.callbacks[request_id] = callback - if 'response_started' not in self.subscriptions or not self.subscriptions.get('response_started'): - self.subscriptions['response_started'] = [request_id] + if "response_started" not in self.subscriptions or not self.subscriptions.get("response_started"): + self.subscriptions["response_started"] = [request_id] else: - self.subscriptions['response_started'].append(request_id) + self.subscriptions["response_started"].append(request_id) return request_id def remove_response_handler(self, response_id): """Removes a response handler.""" self.__remove_intercept(request_id=response_id) - self.subscriptions['response_started'].remove(response_id) + self.subscriptions["response_started"].remove(response_id) del self.callbacks[response_id] - if len(self.subscriptions['response_started']) == 0: - session_unsubscribe(self.conn, self.EVENTS['response_started']) + if len(self.subscriptions["response_started"]) == 0: + session_unsubscribe(self.conn, self.EVENTS["response_started"]) + class Request: def __init__(self, request_id, url, method, headers, body, network: Network): @@ -223,24 +217,23 @@ def __init__(self, request_id, url, method, headers, body, network: Network): def continue_request(self): """Continue after sending a request.""" - params = { - 'requestId': self.request_id - } + params = {"requestId": self.request_id} if self.url is not None: - params['url'] = self.url + params["url"] = self.url if self.method is not None: - params['method'] = self.method + params["method"] = self.method if self.headers is not None: - params['headers'] = self.headers + params["headers"] = self.headers if self.body is not None: - params['body'] = self.body - command = {'method': 'network.continueRequest', 'params': params} + params["body"] = self.body + command = {"method": "network.continueRequest", "params": params} self.network.conn.execute(self.command_iterator(command)) def command_iterator(self, command): """Generator to yield command.""" yield command.to_json() + class Response: def __init__(self, request_id, url, status_code, headers, body, network: Network): self.request_id = request_id @@ -252,15 +245,12 @@ def __init__(self, request_id, url, status_code, headers, body, network: Network def continue_response(self): """Continue after receiving a response.""" - params = { - 'requestId': self.request_id, - 'status': self.status_code - } + params = {"requestId": self.request_id, "status": self.status_code} if self.headers is not None: - params['headers'] = self.headers + params["headers"] = self.headers if self.body is not None: - params['body'] = self.body - command = {'method': 'network.continueResponse', 'params': params} + params["body"] = self.body + command = {"method": "network.continueResponse", "params": params} self.network.conn.execute(self.command_iterator(command)) def command_iterator(self, command): diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 8cc5e418af1a5..6a6d1653248c7 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -1252,7 +1252,7 @@ def network(self): if not self._websocket_connection: self._start_bidi() - if not hasattr(self, '_network') or self._network is None: + if not hasattr(self, "_network") or self._network is None: self._network = Network(self._websocket_connection) return self._network diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 04b0ea93df239..370813f2e0712 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -70,7 +70,7 @@ def execute(self, command): return self._deserialize_result(result, command) def add_callback(self, event, callback): - event_name = event.event_class if hasattr(event, 'event_class') else event + event_name = event.event_class if hasattr(event, "event_class") else event if event_name not in self.callbacks: self.callbacks[event_name] = [] diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 327d2de8ddce2..1fe747323215e 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -24,6 +24,7 @@ def test_network_initialized(driver): assert driver.network is not None + @pytest.mark.xfail_safari def test_add_response_handler(driver, pages): passed = [False] @@ -36,6 +37,7 @@ def callback(response): pages.load("basicAuth") assert passed[0], "Callback was NOT successful" + @pytest.mark.xfail_safari def test_remove_response_handler(driver, pages): passed = [False] @@ -49,6 +51,7 @@ def callback(response): pages.load("basicAuth") assert not passed[0], "Callback should NOT be successful" + @pytest.mark.xfail_safari def test_add_request_handler(driver, pages): passed = [False] @@ -61,6 +64,7 @@ def callback(request): pages.load("basicAuth") assert passed[0], "Callback was NOT successful" + @pytest.mark.xfail_safari def test_remove_request_handler(driver, pages): passed = [False] @@ -74,15 +78,17 @@ def callback(request): pages.load("basicAuth") assert not passed[0], "Callback should NOT be successful" + @pytest.mark.xfail_safari def test_add_authentication_handler(driver, pages): - driver.network.add_authentication_handler('test','test') + driver.network.add_authentication_handler("test", "test") pages.load("basicAuth") - assert driver.find_element(By.TAG_NAME, 'h1').text == 'authorized', "Authentication was NOT successful" + assert driver.find_element(By.TAG_NAME, "h1").text == "authorized", "Authentication was NOT successful" + @pytest.mark.xfail_safari def test_remove_authentication_handler(driver, pages): - driver.network.add_authentication_handler('test', 'test') + driver.network.add_authentication_handler("test", "test") driver.network.remove_authentication_handler() pages.load("basicAuth") - assert driver.find_element(By.TAG_NAME, 'h1').text != 'authorized', "Authentication was successful" + assert driver.find_element(By.TAG_NAME, "h1").text != "authorized", "Authentication was successful" From 8e799403700998b70016505c1a951fbb68b0640c Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Wed, 12 Feb 2025 10:43:31 -0500 Subject: [PATCH 53/77] spinning wheel of death --- py/selenium/webdriver/common/bidi/network.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 7d1721cceee2e..fc5f38546d9d6 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,6 +15,7 @@ # specific language governing permissions and limitations # under the License. +import json import uuid from .session import session_subscribe @@ -47,7 +48,7 @@ def __init__(self, conn): def command_iterator(self, command): """Generator to yield command.""" - yield command.to_json() + yield command def has_callbacks(self): """Checks if there are any callbacks set.""" @@ -231,7 +232,7 @@ def continue_request(self): def command_iterator(self, command): """Generator to yield command.""" - yield command.to_json() + yield command class Response: @@ -255,4 +256,4 @@ def continue_response(self): def command_iterator(self, command): """Generator to yield command.""" - yield command.to_json() + yield command From 1ac681e908f0ead7fa624cb5cbfba5987842c01a Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Wed, 12 Feb 2025 13:15:43 -0500 Subject: [PATCH 54/77] removed unused import --- py/selenium/webdriver/common/bidi/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index fc5f38546d9d6..d11611905084f 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -import json import uuid from .session import session_subscribe From 1e1c8d1395f9816479c5be056cf01b02ee18b593 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 17 Feb 2025 01:58:56 -0500 Subject: [PATCH 55/77] Added more comprehensive docstrings ... minor tweaks --- py/selenium/webdriver/common/bidi/network.py | 104 +++++++++++++++---- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index d11611905084f..db1560f39ccbe 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -45,7 +45,7 @@ def __init__(self, conn): "network.authRequired": [], } - def command_iterator(self, command): + def _command_iterator(self, command): """Generator to yield command.""" yield command @@ -54,7 +54,17 @@ def has_callbacks(self): return len(self.callbacks) > 0 def __add_intercept(self, phases=None, contexts=None, url_patterns=None): - """Add an intercept to the network.""" + """Add an intercept to the network. + + Parameters: + ---------- + phases (list, optional): A list of phases to intercept. + Default is None. + contexts (list, optional): A list of contexts to intercept. + Default is None. + url_patterns (list, optional): A list of URL patterns to intercept. + Default is None. + """ if phases is None: phases = [] if contexts is None and url_patterns is None: @@ -68,21 +78,44 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): else: params = {"phases": phases, "contexts": contexts, "urlPatterns": url_patterns} command = {"method": "network.addIntercept", "params": params} - self.conn.execute(self.command_iterator(command)) + self.conn.execute(self._command_iterator(command)) def __remove_intercept(self, intercept=None, request_id=None): - """Remove an intercept from the network.""" + """Remove an intercept from the network. + + Parameters: + ---------- + intercept (str, optional): The intercept to remove. + Default is None. + request_id (str, optional): The request ID of the intercepted response. + Default is None. + + Raises: + ------ + ValueError: If neither requestId nor intercept is specified + + Notes: + ----- + Either requestId or intercept must be specified. + """ if request_id is not None: command = {"method": "network.removeIntercept", "requestId": request_id} - self.conn.execute(self.command_iterator(command)) + self.conn.execute(self._command_iterator(command)) elif intercept is not None: command = {"method": "network.removeIntercept", "intercept": intercept} - self.conn.execute(self.command_iterator(command)) + self.conn.execute(self._command_iterator(command)) else: raise ValueError("Either requestId or intercept must be specified") def __continue_with_auth(self, request_id, username, password): - """Continue with authentication.""" + """Continue with authentication. + + Parameters: + ---------- + request_id (str): The request ID of the intercepted response. + username (str): The username to use for authentication. + password (str): The password to use for authentication. + """ command = { "method": "network.continueWithAuth", "params": { @@ -91,10 +124,20 @@ def __continue_with_auth(self, request_id, username, password): "credentials": {"type": "password", "username": username, "password": password}, }, } - self.conn.execute(self.command_iterator(command)) + self.conn.execute(self._command_iterator(command)) def __on(self, event, callback): - """Set a callback function to subscribe to a network event.""" + """Set a callback function to subscribe to a network event. + + Parameters: + ---------- + event (str): The event to subscribe to. + callback (function): The callback function to execute on event. + + Returns: + ------- + str: The request ID of the intercepted response. + """ event = self.EVENTS.get(event, event) self.conn.execute(session_subscribe(event)) self.callbacks[event] = callback @@ -103,12 +146,23 @@ def __on(self, event, callback): return self.conn.add_callback(event, self.__handle_event) def __handle_event(self, event, data): - """Perform callback function on event.""" + """Perform callback function on event. + + Parameters: + event (str): The event to perform callback function on. + data (dict): The data to pass to the callback function. + """ if event in self.callbacks: self.callbacks[event](data) def add_authentication_handler(self, username, password): - """Adds an authentication handler.""" + """Adds an authentication handler. + + Parameters: + ---------- + username (str): The username to use for authentication. + password (str): The password to use for authentication. + """ self.__add_intercept(phases=[self.PHASES["auth_required"]]) self.__on( "auth_required", lambda data: self.__continue_with_auth(data["request"]["request"], username, password) @@ -126,12 +180,14 @@ def add_request_handler(self, callback, url_pattern=""): request matches the given URL pattern. Parameters: + ---------- callback (function): A function to be executed when url is matched by a URL pattern - The callback function receives a `Response` object as its argument. + The callback function receives a `Request` object as its argument. url_pattern (str, optional): A substring to match against the response URL. Default is an empty string, which matches all URLs. Returns: + ------- str: The request ID of the intercepted response. """ self.__add_intercept(phases=[self.PHASES["before_request"]]) @@ -156,7 +212,12 @@ def callback_on_url_match(data): return request_id def remove_request_handler(self, request_id): - """Removes a request handler.""" + """Removes a request handler. + + Parameters: + ---------- + request_id (str): The request ID of the intercepted response. + """ self.__remove_intercept(request_id=request_id) self.subscriptions["before_request"].remove(request_id) del self.callbacks[request_id] @@ -168,12 +229,14 @@ def add_response_handler(self, callback, url_pattern=""): response matches the given URL pattern. Parameters: + ---------- callback (function): A function to be executed when url is matched by a url_pattern The callback function receives a `Response` object as its argument. url_pattern (str, optional): A substring to match against the response URL. Default is an empty string, which matches all URLs. Returns: + ------- str: The request ID of the intercepted response. """ self.__add_intercept(phases=[self.PHASES["response_started"]]) @@ -198,7 +261,12 @@ def callback_on_url_match(data): return request_id def remove_response_handler(self, response_id): - """Removes a response handler.""" + """Removes a response handler. + + Parameters: + ---------- + response_id (str): The request ID of the intercepted response. + """ self.__remove_intercept(request_id=response_id) self.subscriptions["response_started"].remove(response_id) del self.callbacks[response_id] @@ -227,9 +295,9 @@ def continue_request(self): if self.body is not None: params["body"] = self.body command = {"method": "network.continueRequest", "params": params} - self.network.conn.execute(self.command_iterator(command)) + self.network.conn.execute(self._command_iterator(command)) - def command_iterator(self, command): + def _command_iterator(self, command): """Generator to yield command.""" yield command @@ -251,8 +319,8 @@ def continue_response(self): if self.body is not None: params["body"] = self.body command = {"method": "network.continueResponse", "params": params} - self.network.conn.execute(self.command_iterator(command)) + self.network.conn.execute(self._command_iterator(command)) - def command_iterator(self, command): + def _command_iterator(self, command): """Generator to yield command.""" yield command From d9fe8834c8c7a4801c5d02eeaac90b69dab4531d Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Mon, 17 Feb 2025 12:20:21 -0500 Subject: [PATCH 56/77] formatting --- py/selenium/webdriver/common/bidi/network.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index db1560f39ccbe..6bd954ad7915d 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -55,7 +55,7 @@ def has_callbacks(self): def __add_intercept(self, phases=None, contexts=None, url_patterns=None): """Add an intercept to the network. - + Parameters: ---------- phases (list, optional): A list of phases to intercept. @@ -82,7 +82,7 @@ def __add_intercept(self, phases=None, contexts=None, url_patterns=None): def __remove_intercept(self, intercept=None, request_id=None): """Remove an intercept from the network. - + Parameters: ---------- intercept (str, optional): The intercept to remove. @@ -109,7 +109,7 @@ def __remove_intercept(self, intercept=None, request_id=None): def __continue_with_auth(self, request_id, username, password): """Continue with authentication. - + Parameters: ---------- request_id (str): The request ID of the intercepted response. @@ -127,13 +127,13 @@ def __continue_with_auth(self, request_id, username, password): self.conn.execute(self._command_iterator(command)) def __on(self, event, callback): - """Set a callback function to subscribe to a network event. - + """Set a callback function to subscribe to a network event. + Parameters: ---------- event (str): The event to subscribe to. callback (function): The callback function to execute on event. - + Returns: ------- str: The request ID of the intercepted response. @@ -147,7 +147,7 @@ def __on(self, event, callback): def __handle_event(self, event, data): """Perform callback function on event. - + Parameters: event (str): The event to perform callback function on. data (dict): The data to pass to the callback function. @@ -157,7 +157,7 @@ def __handle_event(self, event, data): def add_authentication_handler(self, username, password): """Adds an authentication handler. - + Parameters: ---------- username (str): The username to use for authentication. @@ -213,7 +213,7 @@ def callback_on_url_match(data): def remove_request_handler(self, request_id): """Removes a request handler. - + Parameters: ---------- request_id (str): The request ID of the intercepted response. @@ -262,7 +262,7 @@ def callback_on_url_match(data): def remove_response_handler(self, response_id): """Removes a response handler. - + Parameters: ---------- response_id (str): The request ID of the intercepted response. From 631a4561734086ea56892fa5c4f69cf813553cb9 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:36:01 -0500 Subject: [PATCH 57/77] Update command.py --- py/selenium/webdriver/remote/command.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index 8252949a7a68c..b079ed5406f53 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -26,11 +26,6 @@ class Command: https://w3c.github.io/webdriver/ """ - ADD_INTERCEPT: str = "network.addIntercept" - REMOVE_INTERCEPT: str = "network.removeIntercept" - CONTINUE_RESPONSE: str = "network.continueResponse" - CONTINUE_REQUEST: str = "network.continueRequest" - CONTINUE_WITH_AUTH: str = "network.continueWithAuth" NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" From 09f4fa459b1f86f29630010d6ff5a1791475aac4 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 21 Feb 2025 22:13:47 -0500 Subject: [PATCH 58/77] command.py --- py/selenium/webdriver/remote/command.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index b079ed5406f53..8252949a7a68c 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -26,6 +26,11 @@ class Command: https://w3c.github.io/webdriver/ """ + ADD_INTERCEPT: str = "network.addIntercept" + REMOVE_INTERCEPT: str = "network.removeIntercept" + CONTINUE_RESPONSE: str = "network.continueResponse" + CONTINUE_REQUEST: str = "network.continueRequest" + CONTINUE_WITH_AUTH: str = "network.continueWithAuth" NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" From 0af40b74e4a377fbf34e92148444d68796c94115 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 20:29:21 -0400 Subject: [PATCH 59/77] Complete rewrite - all tests passing but auth on google.com --- py/selenium/webdriver/common/bidi/network.py | 428 +++++++++--------- .../webdriver/common/bidi_network_tests.py | 79 ++-- 2 files changed, 260 insertions(+), 247 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 6bd954ad7915d..f99c66ff4e901 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,12 +15,26 @@ # specific language governing permissions and limitations # under the License. +import json +import logging import uuid from .session import session_subscribe from .session import session_unsubscribe +class NetworkEvent: + """Represents a network event.""" + + def __init__(self, event_class, **kwargs): + self.event_class = event_class + self.params = kwargs + + @classmethod + def from_json(cls, json): + return cls(event_class=json.get("event_class"), **json) + + class Network: EVENTS = { "before_request": "network.beforeRequestSent", @@ -28,6 +42,7 @@ class Network: "response_completed": "network.responseCompleted", "auth_required": "network.authRequired", "fetch_error": "network.fetchError", + "continue_request": "network.continueWithRequest", } PHASES = { @@ -38,289 +53,292 @@ class Network: def __init__(self, conn): self.conn = conn + self.intercepts = [] self.callbacks = {} - self.subscriptions = { - "network.responseStarted": [], - "network.beforeRequestSent": [], - "network.authRequired": [], - } + self.subscriptions = {} - def _command_iterator(self, command): - """Generator to yield command.""" - yield command + def command_builder(self, method, params): + """Build a command iterator to send to the network. - def has_callbacks(self): - """Checks if there are any callbacks set.""" - return len(self.callbacks) > 0 + Parameters: + ---------- + method (str): The method to execute. + params (dict): The parameters to pass to the method. + """ + command = {"method": method, "params": params} + cmd = yield command + return cmd - def __add_intercept(self, phases=None, contexts=None, url_patterns=None): + def add_intercept(self, phases=[], contexts=None, url_patterns=None): """Add an intercept to the network. Parameters: ---------- phases (list, optional): A list of phases to intercept. - Default is None. + Default is empty list. contexts (list, optional): A list of contexts to intercept. Default is None. url_patterns (list, optional): A list of URL patterns to intercept. Default is None. + + Returns: + ------- + str : intercept id """ - if phases is None: - phases = [] - if contexts is None and url_patterns is None: - params = { - "phases": phases, - } - elif contexts is None: - params = {"phases": phases, "urlPatterns": url_patterns} - elif url_patterns is None: - params = {"phases": phases, "contexts": contexts} + params = {} + if contexts is not None: + params["contexts"] = contexts + if url_patterns is not None: + params["urlPatterns"] = url_patterns + if len(phases) > 0: + params["phases"] = phases else: - params = {"phases": phases, "contexts": contexts, "urlPatterns": url_patterns} - command = {"method": "network.addIntercept", "params": params} - self.conn.execute(self._command_iterator(command)) + params["phases"] = ["beforeRequestSent"] + cmd = self.command_builder("network.addIntercept", params) + + result = self.conn.execute(cmd) + self.intercepts.append(result["intercept"]) + return result - def __remove_intercept(self, intercept=None, request_id=None): - """Remove an intercept from the network. + def remove_intercept(self, intercept=None): + """Remove a specific intercept, or all intercepts. Parameters: ---------- intercept (str, optional): The intercept to remove. Default is None. - request_id (str, optional): The request ID of the intercepted response. - Default is None. Raises: ------ - ValueError: If neither requestId nor intercept is specified + Exception: If intercept is not found. Notes: ----- - Either requestId or intercept must be specified. + If intercept is None, all intercepts will be removed. """ - if request_id is not None: - command = {"method": "network.removeIntercept", "requestId": request_id} - self.conn.execute(self._command_iterator(command)) - elif intercept is not None: - command = {"method": "network.removeIntercept", "intercept": intercept} - self.conn.execute(self._command_iterator(command)) + if intercept is None: + for intercept_id in self.intercepts: ## remove all intercepts + self.conn.execute(self.command_builder("network.removeIntercept", {"intercept": intercept_id})) + self.intercepts.remove(intercept_id) else: - raise ValueError("Either requestId or intercept must be specified") + try: + self.conn.execute(self.command_builder("network.removeIntercept", {"intercept": intercept})) + self.intercepts.remove(intercept) + except Exception as e: + raise Exception(f"Exception: {e}") - def __continue_with_auth(self, request_id, username, password): - """Continue with authentication. + def _on_request(self, event_name, callback): + """Set a callback function to subscribe to a network event. Parameters: ---------- - request_id (str): The request ID of the intercepted response. - username (str): The username to use for authentication. - password (str): The password to use for authentication. + event_name (str): The event to subscribe to. + callback (function): The callback function to execute on event. + Takes Request object as argument. """ - command = { - "method": "network.continueWithAuth", - "params": { - "request": request_id, - "action": "provideCredentials", - "credentials": {"type": "password", "username": username, "password": password}, - }, - } - self.conn.execute(self._command_iterator(command)) - - def __on(self, event, callback): - """Set a callback function to subscribe to a network event. + + event = NetworkEvent(event_name) + + def _callback(event_data): + request = Request( + network=self, + request_id=event_data.params["request"].get("request", None), + body_size=event_data.params["request"].get("bodySize", None), + cookies=event_data.params["request"].get("cookies", None), + resource_type=event_data.params["request"].get("goog:resourceType", None), + headers_size=event_data.params["request"].get("headersSize", None), + timings=event_data.params["request"].get("timings", None), + url=event_data.params["request"].get("url", None), + ) + callback(request) + + callback_id = self.conn.add_callback(event, _callback) + + if event_name in self.callbacks: + self.callbacks[event_name].append(callback_id) + else: + self.callbacks[event_name] = [callback_id] + + return callback_id + + def add_request_handler(self, event, callback, url_patterns=None, contexts=None): + """Add a request handler to the network. Parameters: ---------- event (str): The event to subscribe to. - callback (function): The callback function to execute on event. + url_patterns (list, optional): A list of URL patterns to intercept. + Default is None. + contexts (list, optional): A list of contexts to intercept. + Default is None. + callback (function): The callback function to execute on request interception + Takes Request object as argument. Returns: ------- - str: The request ID of the intercepted response. + int : callback id """ - event = self.EVENTS.get(event, event) - self.conn.execute(session_subscribe(event)) - self.callbacks[event] = callback - if len(self.subscriptions[event]) == 0: - # session_subscribe(self.conn, event, self.__handle_event) - return self.conn.add_callback(event, self.__handle_event) - def __handle_event(self, event, data): - """Perform callback function on event. + try: + event_name = self.EVENTS[event] + phase_name = self.PHASES[event] + except KeyError: + raise Exception(f"Event {event} not found") - Parameters: - event (str): The event to perform callback function on. - data (dict): The data to pass to the callback function. - """ - if event in self.callbacks: - self.callbacks[event](data) + result = self.add_intercept(phases=[phase_name], url_patterns=url_patterns, contexts=contexts) + callback_id = self._on_request(event_name, callback) - def add_authentication_handler(self, username, password): - """Adds an authentication handler. + if event_name in self.subscriptions: + self.subscriptions[event_name].append(callback_id) + else: + params = {} + params["events"] = [event_name] + self.conn.execute(self.command_builder("session.subscribe", params)) + self.subscriptions[event_name] = [callback_id] + + self.callbacks[callback_id] = result["intercept"] + return callback_id + + def remove_request_handler(self, event, callback_id): + """Remove a request handler from the network. Parameters: ---------- - username (str): The username to use for authentication. - password (str): The password to use for authentication. + event_name (str): The event to unsubscribe from. + callback_id (int): The callback id to remove. """ - self.__add_intercept(phases=[self.PHASES["auth_required"]]) - self.__on( - "auth_required", lambda data: self.__continue_with_auth(data["request"]["request"], username, password) - ) - self.subscriptions["auth_required"] = [username, password] - - def remove_authentication_handler(self): - """Removes an authentication handler.""" - self.__remove_intercept(intercept="auth_required") - del self.subscriptions["auth_required"] - session_unsubscribe(self.conn, self.EVENTS["auth_required"]) - - def add_request_handler(self, callback, url_pattern=""): - """Adds a request handler that executes a callback function when a - request matches the given URL pattern. + try: + event_name = self.EVENTS[event] + except KeyError: + raise Exception(f"Event {event} not found") + + net_event = NetworkEvent(event_name) + + self.conn.remove_callback(net_event, callback_id) + self.remove_intercept(self.callbacks[callback_id]) + del self.callbacks[callback_id] + self.subscriptions[event_name].remove(callback_id) + if len(self.subscriptions[event_name]) == 0: + params = {} + params["events"] = [event_name] + self.conn.execute(self.command_builder("session.subscribe", params)) + del self.subscriptions[event_name] + + def add_auth_handler(self, username, password): + """Add an authentication handler to the network. Parameters: ---------- - callback (function): A function to be executed when url is matched by a URL pattern - The callback function receives a `Request` object as its argument. - url_pattern (str, optional): A substring to match against the response URL. - Default is an empty string, which matches all URLs. - - Returns: - ------- - str: The request ID of the intercepted response. + username (str): The username to authenticate with. + password (str): The password to authenticate with. """ - self.__add_intercept(phases=[self.PHASES["before_request"]]) - - def callback_on_url_match(data): - if url_pattern in data["request"]["url"]: - # create request object to pass to callback - request_id = data["request"].get("requestId", uuid.uuid4()) - url = data["request"].get("url") - method = data["request"].get("method") - headers = data["request"].get("headers", {}) - body = data["request"].get("body", None) - request = Request(request_id, url, method, headers, body, self) - callback(request) - - request_id = self.__on("before_request", callback_on_url_match) - self.callbacks[request_id] = callback - if "before_request" not in self.subscriptions or not self.subscriptions.get("before_request"): - self.subscriptions["before_request"] = [request_id] - else: - self.subscriptions["before_request"].append(request_id) - return request_id + event_name = "auth_required" - def remove_request_handler(self, request_id): - """Removes a request handler. + def _callback(request): + request._continue_with_auth(request, username, password) - Parameters: - ---------- - request_id (str): The request ID of the intercepted response. - """ - self.__remove_intercept(request_id=request_id) - self.subscriptions["before_request"].remove(request_id) - del self.callbacks[request_id] - if len(self.subscriptions["before_request"]) == 0: - session_unsubscribe(self.conn, self.EVENTS["before_request"]) + return self.add_request_handler(event_name, _callback) - def add_response_handler(self, callback, url_pattern=""): - """Adds a response handler that executes a callback function when a - response matches the given URL pattern. + def _continue_with_auth(self, request, username, password): + """Continue with authentication.""" - Parameters: - ---------- - callback (function): A function to be executed when url is matched by a url_pattern - The callback function receives a `Response` object as its argument. - url_pattern (str, optional): A substring to match against the response URL. - Default is an empty string, which matches all URLs. + params = {} + params["request"] = request.request_id + params["action"] = "provideCredentials" + params["credentials"] = {"type": "password", "username": username, "password": password} + + self.conn.execute(self.command_builder("network.continueWithAuth", params)) - Returns: - ------- - str: The request ID of the intercepted response. - """ - self.__add_intercept(phases=[self.PHASES["response_started"]]) - - def callback_on_url_match(data): - # create response object to pass to callback - if url_pattern in data["response"]["url"]: - request_id = data["request"].get("requestId", uuid.uuid4()) - url = data["response"].get("url") - status_code = data["response"].get("status") - body = data["response"].get("body", None) - headers = data["response"].get("headers", {}) - response = Response(request_id, url, status_code, headers, body, self) - callback(response) - - request_id = self.__on("response_started", callback_on_url_match) - self.callbacks[request_id] = callback - if "response_started" not in self.subscriptions or not self.subscriptions.get("response_started"): - self.subscriptions["response_started"] = [request_id] - else: - self.subscriptions["response_started"].append(request_id) - return request_id - def remove_response_handler(self, response_id): - """Removes a response handler. +class Request: + """Represents an intercepted network request.""" + + def __init__( + self, + network: Network, + request_id, + body_size=None, + cookies=None, + resource_type=None, + headers=None, + headers_size=None, + method=None, + timings=None, + url=None, + ): + self.network = network + self.request_id = request_id + self.body_size = body_size + self.cookies = cookies + self.resource_type = resource_type + self.headers = headers + self.headers_size = headers_size + self.method = method + self.timings = timings + self.url = url + + def command_builder(self, method, params): + """Build a command iterator to send to the network. Parameters: ---------- - response_id (str): The request ID of the intercepted response. + method (str): The method to execute. + params (dict): The parameters to pass to the method. """ - self.__remove_intercept(request_id=response_id) - self.subscriptions["response_started"].remove(response_id) - del self.callbacks[response_id] - if len(self.subscriptions["response_started"]) == 0: - session_unsubscribe(self.conn, self.EVENTS["response_started"]) + command = {"method": method, "params": params} + cmd = yield command + return cmd + def continue_request(self, **kwargs): + """Continue after intercepting this request.""" -class Request: - def __init__(self, request_id, url, method, headers, body, network: Network): - self.request_id = request_id - self.url = url - self.method = method - self.headers = headers - self.body = body - self.network = network + if not self.request_id: + raise ValueError("Request not found.") - def continue_request(self): - """Continue after sending a request.""" - params = {"requestId": self.request_id} - if self.url is not None: - params["url"] = self.url + params = {"request": self.request_id} + if "body" in kwargs: + params["body"] = kwargs["body"] if self.method is not None: params["method"] = self.method if self.headers is not None: params["headers"] = self.headers - if self.body is not None: - params["body"] = self.body - command = {"method": "network.continueRequest", "params": params} - self.network.conn.execute(self._command_iterator(command)) + if self.cookies is not None: + params["cookies"] = self.cookies + if self.url is not None: + params["url"] = self.url - def _command_iterator(self, command): - """Generator to yield command.""" - yield command + self.network.conn.execute(self.command_builder("network.continueWithRequest", params)) class Response: + """Represents an intercepted network response.""" + def __init__(self, request_id, url, status_code, headers, body, network: Network): self.request_id = request_id self.url = url self.status_code = status_code self.headers = headers self.body = body - self.network = network + self.conn = network - def continue_response(self): - """Continue after receiving a response.""" - params = {"requestId": self.request_id, "status": self.status_code} - if self.headers is not None: - params["headers"] = self.headers - if self.body is not None: - params["body"] = self.body - command = {"method": "network.continueResponse", "params": params} - self.network.conn.execute(self._command_iterator(command)) - - def _command_iterator(self, command): - """Generator to yield command.""" - yield command + def command_builder(self, method, params): + """Build a command iterator to send to the network. + + Parameters: + ---------- + method (str): The method to execute. + params (dict): The parameters to pass to the method. + """ + command = {"method": method, "params": params} + cmd = yield command + return cmd + + # def continue_response(self): + # """Continue after receiving a response.""" + # params = {"requestId": self.request_id, "status": self.status_code} + # if self.headers is not None: + # params["headers"] = self.headers + # if self.body is not None: + # params["body"] = self.body + # command = {"method": "network.continueResponse", "params": params} + # self.network.conn.execute(self._command_iterator(command)) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 1fe747323215e..226dd795f8f29 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -17,6 +17,7 @@ import pytest +from selenium.webdriver.common.bidi.network import Request from selenium.webdriver.common.by import By @@ -26,69 +27,63 @@ def test_network_initialized(driver): @pytest.mark.xfail_safari -def test_add_response_handler(driver, pages): - passed = [False] - - def callback(response): - passed[0] = True - response.continue_response() - - driver.network.add_response_handler(callback) - pages.load("basicAuth") - assert passed[0], "Callback was NOT successful" +def test_add_intercept(driver, pages): + result = driver.network.add_intercept() + assert result is not None, "Intercept not added" @pytest.mark.xfail_safari -def test_remove_response_handler(driver, pages): - passed = [False] - - def callback(response): - passed[0] = True - response.continue_response() - - test_response_id = driver.network.add_response_handler(callback) - driver.network.remove_response_handler(response_id=test_response_id) - pages.load("basicAuth") - assert not passed[0], "Callback should NOT be successful" +def test_remove_intercept(driver): + result = driver.network.add_intercept() + driver.network.remove_intercept(result["intercept"]) + assert driver.network.intercepts == [], "Intercept not removed" @pytest.mark.xfail_safari def test_add_request_handler(driver, pages): - passed = [False] + + requests = [] def callback(request): - passed[0] = True - request.continue_request() + requests.append(request) - driver.network.add_request_handler(callback) - pages.load("basicAuth") - assert passed[0], "Callback was NOT successful" + callback_id = driver.network.add_request_handler("before_request", callback) + assert callback_id is not None, "Request handler not added" + driver.get("http://www.google.com") + assert requests, "No requests intercepted" @pytest.mark.xfail_safari def test_remove_request_handler(driver, pages): - passed = [False] + + requests = [] def callback(request): - passed[0] = True - request.continue_request() + requests.append(request) - test_request_id = driver.network.add_request_handler(callback) - driver.network.remove_request_handler(request_id=test_request_id) - pages.load("basicAuth") - assert not passed[0], "Callback should NOT be successful" + callback_id = driver.network.add_request_handler("before_request", callback) + assert callback_id is not None, "Request handler not added" + driver.network.remove_request_handler("before_request", callback_id) + driver.get("http://www.google.com") + assert not requests, "No requests intercepted" @pytest.mark.xfail_safari -def test_add_authentication_handler(driver, pages): - driver.network.add_authentication_handler("test", "test") - pages.load("basicAuth") - assert driver.find_element(By.TAG_NAME, "h1").text == "authorized", "Authentication was NOT successful" +def test_continue_request(driver, pages): + + def callback(request: Request): + request.continue_request() + + callback_id = driver.network.add_request_handler("before_request", callback) + assert callback_id is not None, "Request handler not added" + driver.get("http://www.google.com") + assert driver.title == "Site is not secure", "Request not continued" @pytest.mark.xfail_safari -def test_remove_authentication_handler(driver, pages): - driver.network.add_authentication_handler("test", "test") - driver.network.remove_authentication_handler() +def test_continue_with_auth(driver, pages): + + callback_id = driver.network.add_auth_handler("test", "test") + assert callback_id is not None, "Request handler not added" pages.load("basicAuth") - assert driver.find_element(By.TAG_NAME, "h1").text != "authorized", "Authentication was successful" + assert driver.find_element(By.TAG_NAME, "h1").text == "authorized", "Authorization failed" From 5a0f29a39d47ac476d8ed9c420de5a2fca03147d Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 20:34:29 -0400 Subject: [PATCH 60/77] linting --- py/selenium/webdriver/common/bidi/network.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index f99c66ff4e901..cc916305fc54a 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -15,13 +15,6 @@ # specific language governing permissions and limitations # under the License. -import json -import logging -import uuid - -from .session import session_subscribe -from .session import session_unsubscribe - class NetworkEvent: """Represents a network event.""" @@ -117,7 +110,7 @@ def remove_intercept(self, intercept=None): If intercept is None, all intercepts will be removed. """ if intercept is None: - for intercept_id in self.intercepts: ## remove all intercepts + for intercept_id in self.intercepts: # remove all intercepts self.conn.execute(self.command_builder("network.removeIntercept", {"intercept": intercept_id})) self.intercepts.remove(intercept_id) else: From f31a883028979b07b449f126bb37e28ab7fe6d0a Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 20:36:40 -0400 Subject: [PATCH 61/77] remove unused command.py commands --- py/selenium/webdriver/remote/command.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/py/selenium/webdriver/remote/command.py b/py/selenium/webdriver/remote/command.py index 8252949a7a68c..b079ed5406f53 100644 --- a/py/selenium/webdriver/remote/command.py +++ b/py/selenium/webdriver/remote/command.py @@ -26,11 +26,6 @@ class Command: https://w3c.github.io/webdriver/ """ - ADD_INTERCEPT: str = "network.addIntercept" - REMOVE_INTERCEPT: str = "network.removeIntercept" - CONTINUE_RESPONSE: str = "network.continueResponse" - CONTINUE_REQUEST: str = "network.continueRequest" - CONTINUE_WITH_AUTH: str = "network.continueWithAuth" NEW_SESSION: str = "newSession" DELETE_SESSION: str = "deleteSession" NEW_WINDOW: str = "newWindow" From a5da929a18e6b72892591a513424068af90e7cfa Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 20:47:28 -0400 Subject: [PATCH 62/77] removed commands --- py/selenium/webdriver/remote/remote_connection.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/py/selenium/webdriver/remote/remote_connection.py b/py/selenium/webdriver/remote/remote_connection.py index 0228ad21cc2b8..1bc432084b6e2 100644 --- a/py/selenium/webdriver/remote/remote_connection.py +++ b/py/selenium/webdriver/remote/remote_connection.py @@ -36,11 +36,6 @@ LOGGER = logging.getLogger(__name__) remote_commands = { - Command.ADD_INTERCEPT: ("POST", "/session/$sessionId/network/intercept"), - Command.REMOVE_INTERCEPT: ("DELETE", "/session/$sessionId/network/intercept/$intercept"), - Command.CONTINUE_RESPONSE: ("POST", "/session/$sessionId/network/response/$requestId"), - Command.CONTINUE_REQUEST: ("POST", "/session/$sessionId/network/request/$requestId"), - Command.CONTINUE_WITH_AUTH: ("POST", "/session/$sessionId/network/auth"), Command.NEW_SESSION: ("POST", "/session"), Command.QUIT: ("DELETE", "/session/$sessionId"), Command.W3C_GET_CURRENT_WINDOW_HANDLE: ("GET", "/session/$sessionId/window"), From e1af59e5ae17f3eacc801206b832c01175415e38 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 21:28:29 -0400 Subject: [PATCH 63/77] Removed unused Response functionality - refined tests. All that fails is continue_request and continue_with_auth --- py/selenium/webdriver/common/bidi/network.py | 36 +------------------ .../webdriver/common/bidi_network_tests.py | 29 ++++----------- 2 files changed, 8 insertions(+), 57 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index cc916305fc54a..c1744b13ed66f 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -300,38 +300,4 @@ def continue_request(self, **kwargs): if self.url is not None: params["url"] = self.url - self.network.conn.execute(self.command_builder("network.continueWithRequest", params)) - - -class Response: - """Represents an intercepted network response.""" - - def __init__(self, request_id, url, status_code, headers, body, network: Network): - self.request_id = request_id - self.url = url - self.status_code = status_code - self.headers = headers - self.body = body - self.conn = network - - def command_builder(self, method, params): - """Build a command iterator to send to the network. - - Parameters: - ---------- - method (str): The method to execute. - params (dict): The parameters to pass to the method. - """ - command = {"method": method, "params": params} - cmd = yield command - return cmd - - # def continue_response(self): - # """Continue after receiving a response.""" - # params = {"requestId": self.request_id, "status": self.status_code} - # if self.headers is not None: - # params["headers"] = self.headers - # if self.body is not None: - # params["body"] = self.body - # command = {"method": "network.continueResponse", "params": params} - # self.network.conn.execute(self._command_iterator(command)) + self.network.conn.execute(self.command_builder("network.continueWithRequest", params)) \ No newline at end of file diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 226dd795f8f29..1a5d9da92ba69 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -38,23 +38,8 @@ def test_remove_intercept(driver): driver.network.remove_intercept(result["intercept"]) assert driver.network.intercepts == [], "Intercept not removed" - @pytest.mark.xfail_safari -def test_add_request_handler(driver, pages): - - requests = [] - - def callback(request): - requests.append(request) - - callback_id = driver.network.add_request_handler("before_request", callback) - assert callback_id is not None, "Request handler not added" - driver.get("http://www.google.com") - assert requests, "No requests intercepted" - - -@pytest.mark.xfail_safari -def test_remove_request_handler(driver, pages): +def test_add_and_remove_request_handler(driver, pages): requests = [] @@ -64,9 +49,9 @@ def callback(request): callback_id = driver.network.add_request_handler("before_request", callback) assert callback_id is not None, "Request handler not added" driver.network.remove_request_handler("before_request", callback_id) - driver.get("http://www.google.com") - assert not requests, "No requests intercepted" - + pages.load("formPage.html") + assert not requests, "Requests intercepted" + assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" @pytest.mark.xfail_safari def test_continue_request(driver, pages): @@ -76,8 +61,8 @@ def callback(request: Request): callback_id = driver.network.add_request_handler("before_request", callback) assert callback_id is not None, "Request handler not added" - driver.get("http://www.google.com") - assert driver.title == "Site is not secure", "Request not continued" + pages.load("formPage.html") + assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" @pytest.mark.xfail_safari @@ -86,4 +71,4 @@ def test_continue_with_auth(driver, pages): callback_id = driver.network.add_auth_handler("test", "test") assert callback_id is not None, "Request handler not added" pages.load("basicAuth") - assert driver.find_element(By.TAG_NAME, "h1").text == "authorized", "Authorization failed" + assert driver.find_element(By.TAG_NAME, "h1").text == "authorized", "Authorization failed" \ No newline at end of file From b1c45b0525a57613b6cca51a5c32858a76e0ebc5 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 21:44:31 -0400 Subject: [PATCH 64/77] minor tweak --- py/selenium/webdriver/common/bidi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index c1744b13ed66f..a03e6e0d5e7e4 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -228,7 +228,7 @@ def add_auth_handler(self, username, password): event_name = "auth_required" def _callback(request): - request._continue_with_auth(request, username, password) + self._continue_with_auth(request, username, password) return self.add_request_handler(event_name, _callback) From bde022a5ae57ddabc38d4714977b32751d29698e Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 23:11:34 -0400 Subject: [PATCH 65/77] Updated auth test - was trying to find page that onlt exists in rb bindings, removed change from websocket_connection.py --- py/selenium/webdriver/common/bidi/network.py | 2 +- py/selenium/webdriver/remote/websocket_connection.py | 2 +- .../selenium/webdriver/common/bidi_network_tests.py | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index a03e6e0d5e7e4..c6c82ec52bfc5 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -300,4 +300,4 @@ def continue_request(self, **kwargs): if self.url is not None: params["url"] = self.url - self.network.conn.execute(self.command_builder("network.continueWithRequest", params)) \ No newline at end of file + self.network.conn.execute(self.command_builder("network.continueWithRequest", params)) diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 370813f2e0712..3afbba46d5e1e 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -70,7 +70,7 @@ def execute(self, command): return self._deserialize_result(result, command) def add_callback(self, event, callback): - event_name = event.event_class if hasattr(event, "event_class") else event + event_name = event.event_class if event_name not in self.callbacks: self.callbacks[event_name] = [] diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 1a5d9da92ba69..8992e003bc588 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -38,12 +38,13 @@ def test_remove_intercept(driver): driver.network.remove_intercept(result["intercept"]) assert driver.network.intercepts == [], "Intercept not removed" + @pytest.mark.xfail_safari def test_add_and_remove_request_handler(driver, pages): requests = [] - def callback(request): + def callback(request: Request): requests.append(request) callback_id = driver.network.add_request_handler("before_request", callback) @@ -53,6 +54,7 @@ def callback(request): assert not requests, "Requests intercepted" assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" + @pytest.mark.xfail_safari def test_continue_request(driver, pages): @@ -66,9 +68,9 @@ def callback(request: Request): @pytest.mark.xfail_safari -def test_continue_with_auth(driver, pages): +def test_continue_with_auth(driver): - callback_id = driver.network.add_auth_handler("test", "test") + callback_id = driver.network.add_auth_handler("user", "passwd") assert callback_id is not None, "Request handler not added" - pages.load("basicAuth") - assert driver.find_element(By.TAG_NAME, "h1").text == "authorized", "Authorization failed" \ No newline at end of file + driver.get("https://httpbin.org/basic-auth/user/passwd") + assert "authenticated" in driver.page_source, "Authorization failed" From 110da30b3811075a0b60f0e842f517d5ff3d5700 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 23:33:14 -0400 Subject: [PATCH 66/77] updated event_name for continue, still failing --- py/selenium/webdriver/common/bidi/network.py | 9 +-- .../webdriver/common/bidi_network_tests.py | 58 +++++++++---------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index c6c82ec52bfc5..7b1901d45e540 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -35,7 +35,8 @@ class Network: "response_completed": "network.responseCompleted", "auth_required": "network.authRequired", "fetch_error": "network.fetchError", - "continue_request": "network.continueWithRequest", + "continue_request": "network.continueRequest", + "continue_auth": "network.continueWithAuth", } PHASES = { @@ -225,12 +226,12 @@ def add_auth_handler(self, username, password): username (str): The username to authenticate with. password (str): The password to authenticate with. """ - event_name = "auth_required" + event = "auth_required" def _callback(request): self._continue_with_auth(request, username, password) - return self.add_request_handler(event_name, _callback) + return self.add_request_handler(event, _callback) def _continue_with_auth(self, request, username, password): """Continue with authentication.""" @@ -300,4 +301,4 @@ def continue_request(self, **kwargs): if self.url is not None: params["url"] = self.url - self.network.conn.execute(self.command_builder("network.continueWithRequest", params)) + self.network.conn.execute(self.command_builder("network.continueRequest", params)) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 8992e003bc588..c2cc5cef6bd9a 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -21,38 +21,38 @@ from selenium.webdriver.common.by import By -@pytest.mark.xfail_safari -def test_network_initialized(driver): - assert driver.network is not None +# @pytest.mark.xfail_safari +# def test_network_initialized(driver): +# assert driver.network is not None -@pytest.mark.xfail_safari -def test_add_intercept(driver, pages): - result = driver.network.add_intercept() - assert result is not None, "Intercept not added" +# @pytest.mark.xfail_safari +# def test_add_intercept(driver, pages): +# result = driver.network.add_intercept() +# assert result is not None, "Intercept not added" -@pytest.mark.xfail_safari -def test_remove_intercept(driver): - result = driver.network.add_intercept() - driver.network.remove_intercept(result["intercept"]) - assert driver.network.intercepts == [], "Intercept not removed" +# @pytest.mark.xfail_safari +# def test_remove_intercept(driver): +# result = driver.network.add_intercept() +# driver.network.remove_intercept(result["intercept"]) +# assert driver.network.intercepts == [], "Intercept not removed" -@pytest.mark.xfail_safari -def test_add_and_remove_request_handler(driver, pages): +# @pytest.mark.xfail_safari +# def test_add_and_remove_request_handler(driver, pages): - requests = [] +# requests = [] - def callback(request: Request): - requests.append(request) +# def callback(request: Request): +# requests.append(request) - callback_id = driver.network.add_request_handler("before_request", callback) - assert callback_id is not None, "Request handler not added" - driver.network.remove_request_handler("before_request", callback_id) - pages.load("formPage.html") - assert not requests, "Requests intercepted" - assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" +# callback_id = driver.network.add_request_handler("before_request", callback) +# assert callback_id is not None, "Request handler not added" +# driver.network.remove_request_handler("before_request", callback_id) +# pages.load("formPage.html") +# assert not requests, "Requests intercepted" +# assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" @pytest.mark.xfail_safari @@ -67,10 +67,10 @@ def callback(request: Request): assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" -@pytest.mark.xfail_safari -def test_continue_with_auth(driver): +# @pytest.mark.xfail_safari +# def test_continue_with_auth(driver): - callback_id = driver.network.add_auth_handler("user", "passwd") - assert callback_id is not None, "Request handler not added" - driver.get("https://httpbin.org/basic-auth/user/passwd") - assert "authenticated" in driver.page_source, "Authorization failed" +# callback_id = driver.network.add_auth_handler("user", "passwd") +# assert callback_id is not None, "Request handler not added" +# driver.get("https://httpbin.org/basic-auth/user/passwd") +# assert "authenticated" in driver.page_source, "Authorization failed" From 493326f275760022d81c9605f849f59fa79d499c Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 23:35:30 -0400 Subject: [PATCH 67/77] linting --- py/selenium/webdriver/common/bidi/network.py | 17 ++++++++++++++++- .../webdriver/common/bidi_network_tests.py | 1 - 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 7b1901d45e540..bb3df92846397 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -129,6 +129,10 @@ def _on_request(self, event_name, callback): event_name (str): The event to subscribe to. callback (function): The callback function to execute on event. Takes Request object as argument. + + Returns: + ------- + int : callback id """ event = NetworkEvent(event_name) @@ -225,6 +229,10 @@ def add_auth_handler(self, username, password): ---------- username (str): The username to authenticate with. password (str): The password to authenticate with. + + Returns: + ------- + int : callback id """ event = "auth_required" @@ -234,7 +242,14 @@ def _callback(request): return self.add_request_handler(event, _callback) def _continue_with_auth(self, request, username, password): - """Continue with authentication.""" + """Continue with authentication. + + Parameters: + ---------- + request (Request): The request to continue with. + username (str): The username to authenticate with. + password (str): The password to authenticate with. + """ params = {} params["request"] = request.request_id diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index c2cc5cef6bd9a..a0fe35935fddd 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -20,7 +20,6 @@ from selenium.webdriver.common.bidi.network import Request from selenium.webdriver.common.by import By - # @pytest.mark.xfail_safari # def test_network_initialized(driver): # assert driver.network is not None From 515ab5859cac5fba4a6cd810815ab076609d0e51 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 23:38:09 -0400 Subject: [PATCH 68/77] uncommented --- .../webdriver/common/bidi_network_tests.py | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index a0fe35935fddd..8992e003bc588 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -20,38 +20,39 @@ from selenium.webdriver.common.bidi.network import Request from selenium.webdriver.common.by import By -# @pytest.mark.xfail_safari -# def test_network_initialized(driver): -# assert driver.network is not None + +@pytest.mark.xfail_safari +def test_network_initialized(driver): + assert driver.network is not None -# @pytest.mark.xfail_safari -# def test_add_intercept(driver, pages): -# result = driver.network.add_intercept() -# assert result is not None, "Intercept not added" +@pytest.mark.xfail_safari +def test_add_intercept(driver, pages): + result = driver.network.add_intercept() + assert result is not None, "Intercept not added" -# @pytest.mark.xfail_safari -# def test_remove_intercept(driver): -# result = driver.network.add_intercept() -# driver.network.remove_intercept(result["intercept"]) -# assert driver.network.intercepts == [], "Intercept not removed" +@pytest.mark.xfail_safari +def test_remove_intercept(driver): + result = driver.network.add_intercept() + driver.network.remove_intercept(result["intercept"]) + assert driver.network.intercepts == [], "Intercept not removed" -# @pytest.mark.xfail_safari -# def test_add_and_remove_request_handler(driver, pages): +@pytest.mark.xfail_safari +def test_add_and_remove_request_handler(driver, pages): -# requests = [] + requests = [] -# def callback(request: Request): -# requests.append(request) + def callback(request: Request): + requests.append(request) -# callback_id = driver.network.add_request_handler("before_request", callback) -# assert callback_id is not None, "Request handler not added" -# driver.network.remove_request_handler("before_request", callback_id) -# pages.load("formPage.html") -# assert not requests, "Requests intercepted" -# assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" + callback_id = driver.network.add_request_handler("before_request", callback) + assert callback_id is not None, "Request handler not added" + driver.network.remove_request_handler("before_request", callback_id) + pages.load("formPage.html") + assert not requests, "Requests intercepted" + assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" @pytest.mark.xfail_safari @@ -66,10 +67,10 @@ def callback(request: Request): assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" -# @pytest.mark.xfail_safari -# def test_continue_with_auth(driver): +@pytest.mark.xfail_safari +def test_continue_with_auth(driver): -# callback_id = driver.network.add_auth_handler("user", "passwd") -# assert callback_id is not None, "Request handler not added" -# driver.get("https://httpbin.org/basic-auth/user/passwd") -# assert "authenticated" in driver.page_source, "Authorization failed" + callback_id = driver.network.add_auth_handler("user", "passwd") + assert callback_id is not None, "Request handler not added" + driver.get("https://httpbin.org/basic-auth/user/passwd") + assert "authenticated" in driver.page_source, "Authorization failed" From 00f76a50fbc09fbb49dc30a59895dc78a49705bc Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Tue, 25 Mar 2025 23:59:19 -0400 Subject: [PATCH 69/77] refactored continue_with_auth to proceed with ContinueWithNoAuthCredentials per BiDi spec --- py/selenium/webdriver/common/bidi/network.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index bb3df92846397..36d5fe7615dd3 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -241,7 +241,7 @@ def _callback(request): return self.add_request_handler(event, _callback) - def _continue_with_auth(self, request, username, password): + def _continue_with_auth(self, request, username=None, password=None): """Continue with authentication. Parameters: @@ -249,12 +249,21 @@ def _continue_with_auth(self, request, username, password): request (Request): The request to continue with. username (str): The username to authenticate with. password (str): The password to authenticate with. + + Notes: + ----- + If username or password is None, it attempts auth with no credentials """ params = {} params["request"] = request.request_id - params["action"] = "provideCredentials" - params["credentials"] = {"type": "password", "username": username, "password": password} + + if not username or not password: + params["action"] = "default" + + else: + params["action"] = "provideCredentials" + params["credentials"] = {"type": "password", "username": username, "password": password} self.conn.execute(self.command_builder("network.continueWithAuth", params)) From 8a85a1be145bb2ac024ad5ce7dfce8ab98ca6e9e Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Wed, 26 Mar 2025 10:36:06 -0400 Subject: [PATCH 70/77] updated continue_request, moved _continue_with_auth under Request object -- still failing 2 tests --- py/selenium/webdriver/common/bidi/network.py | 75 ++++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 36d5fe7615dd3..69e80e53093aa 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -237,36 +237,10 @@ def add_auth_handler(self, username, password): event = "auth_required" def _callback(request): - self._continue_with_auth(request, username, password) + request._continue_with_auth(username, password) return self.add_request_handler(event, _callback) - def _continue_with_auth(self, request, username=None, password=None): - """Continue with authentication. - - Parameters: - ---------- - request (Request): The request to continue with. - username (str): The username to authenticate with. - password (str): The password to authenticate with. - - Notes: - ----- - If username or password is None, it attempts auth with no credentials - """ - - params = {} - params["request"] = request.request_id - - if not username or not password: - params["action"] = "default" - - else: - params["action"] = "provideCredentials" - params["credentials"] = {"type": "password", "username": username, "password": password} - - self.conn.execute(self.command_builder("network.continueWithAuth", params)) - class Request: """Represents an intercepted network request.""" @@ -307,22 +281,47 @@ def command_builder(self, method, params): cmd = yield command return cmd - def continue_request(self, **kwargs): + def continue_request(self, body=None, method=None, headers=None, cookies=None, url=None): """Continue after intercepting this request.""" if not self.request_id: raise ValueError("Request not found.") params = {"request": self.request_id} - if "body" in kwargs: - params["body"] = kwargs["body"] - if self.method is not None: - params["method"] = self.method - if self.headers is not None: - params["headers"] = self.headers - if self.cookies is not None: - params["cookies"] = self.cookies - if self.url is not None: - params["url"] = self.url + if body is not None: + params["body"] = body + if method is not None: + params["method"] = method + if headers is not None: + params["headers"] = headers + if cookies is not None: + params["cookies"] = cookies + if url is not None: + params["url"] = url self.network.conn.execute(self.command_builder("network.continueRequest", params)) + + def _continue_with_auth(self, username=None, password=None): + """Continue with authentication. + + Parameters: + ---------- + request (Request): The request to continue with. + username (str): The username to authenticate with. + password (str): The password to authenticate with. + + Notes: + ----- + If username or password is None, it attempts auth with no credentials + """ + + params = {} + params["request"] = self.request_id + + if not username or not password: # no credentials is valid option + params["action"] = "default" + else: + params["action"] = "provideCredentials" + params["credentials"] = {"type": "password", "username": username, "password": password} + + self.network.conn.execute(self.command_builder("network.continueWithAuth", params)) From 30ffaba14f709586483584b9f742d04c3f9a7464 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:22:39 -0400 Subject: [PATCH 71/77] typo --- py/selenium/webdriver/common/bidi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 69e80e53093aa..f0c55d32c5e11 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -219,7 +219,7 @@ def remove_request_handler(self, event, callback_id): if len(self.subscriptions[event_name]) == 0: params = {} params["events"] = [event_name] - self.conn.execute(self.command_builder("session.subscribe", params)) + self.conn.execute(self.command_builder("session.unsubscribe", params)) del self.subscriptions[event_name] def add_auth_handler(self, username, password): From 901dd04d8a87bd43b2025ea82baa7c5515a952c2 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 4 Apr 2025 09:56:38 -0400 Subject: [PATCH 72/77] made changes to websocket_connection.py, added fail_request() --- py/selenium/webdriver/common/bidi/network.py | 9 +++++++++ py/selenium/webdriver/remote/websocket_connection.py | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index f0c55d32c5e11..b111839313c0e 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -280,6 +280,15 @@ def command_builder(self, method, params): command = {"method": method, "params": params} cmd = yield command return cmd + + def fail_request(self): + """Fail this request.""" + + if not self.request_id: + raise ValueError("Request not found.") + + params = {"request": self.request_id} + self.network.conn.execute(self.command_builder("network.failRequest", params)) def continue_request(self, body=None, method=None, headers=None, cookies=None, url=None): """Continue after intercepting this request.""" diff --git a/py/selenium/webdriver/remote/websocket_connection.py b/py/selenium/webdriver/remote/websocket_connection.py index 3afbba46d5e1e..34772fdb995f8 100644 --- a/py/selenium/webdriver/remote/websocket_connection.py +++ b/py/selenium/webdriver/remote/websocket_connection.py @@ -60,8 +60,9 @@ def execute(self, command): logger.debug(f"-> {data}"[: self._max_log_message_size]) self._ws.send(data) - self._wait_until(lambda: self._id in self._messages) - response = self._messages.pop(self._id) + current_id = self._id + self._wait_until(lambda: current_id in self._messages) + response = self._messages.pop(current_id) if "error" in response: raise Exception(response["error"]) @@ -131,7 +132,7 @@ def _process_message(self, message): if "method" in message: params = message["params"] for callback in self.callbacks.get(message["method"], []): - callback(params) + Thread(target=callback, args=(params,)).start() def _wait_until(self, condition): timeout = self._response_wait_timeout From b1299c82b33c51d2ef2633ab813fda28a5ac794f Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:19:29 -0400 Subject: [PATCH 73/77] Update network.py --- py/selenium/webdriver/common/bidi/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index b111839313c0e..008a0925446fa 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -280,7 +280,7 @@ def command_builder(self, method, params): command = {"method": method, "params": params} cmd = yield command return cmd - + def fail_request(self): """Fail this request.""" From 6f607043098380d8fdd267895be1ba77cbaf7b08 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 4 Apr 2025 12:05:37 -0400 Subject: [PATCH 74/77] marked add/remove_intercept() private, added clear_request_handlers() --- py/selenium/webdriver/common/bidi/network.py | 22 +++++++++++++--- .../webdriver/common/bidi_network_tests.py | 25 ++++++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 008a0925446fa..866ed5d6b526e 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -63,7 +63,7 @@ def command_builder(self, method, params): cmd = yield command return cmd - def add_intercept(self, phases=[], contexts=None, url_patterns=None): + def _add_intercept(self, phases=[], contexts=None, url_patterns=None): """Add an intercept to the network. Parameters: @@ -94,7 +94,7 @@ def add_intercept(self, phases=[], contexts=None, url_patterns=None): self.intercepts.append(result["intercept"]) return result - def remove_intercept(self, intercept=None): + def _remove_intercept(self, intercept=None): """Remove a specific intercept, or all intercepts. Parameters: @@ -183,7 +183,7 @@ def add_request_handler(self, event, callback, url_patterns=None, contexts=None) except KeyError: raise Exception(f"Event {event} not found") - result = self.add_intercept(phases=[phase_name], url_patterns=url_patterns, contexts=contexts) + result = self._add_intercept(phases=[phase_name], url_patterns=url_patterns, contexts=contexts) callback_id = self._on_request(event_name, callback) if event_name in self.subscriptions: @@ -213,7 +213,7 @@ def remove_request_handler(self, event, callback_id): net_event = NetworkEvent(event_name) self.conn.remove_callback(net_event, callback_id) - self.remove_intercept(self.callbacks[callback_id]) + self._remove_intercept(self.callbacks[callback_id]) del self.callbacks[callback_id] self.subscriptions[event_name].remove(callback_id) if len(self.subscriptions[event_name]) == 0: @@ -222,6 +222,20 @@ def remove_request_handler(self, event, callback_id): self.conn.execute(self.command_builder("session.unsubscribe", params)) del self.subscriptions[event_name] + def clear_request_handlers(self): + """Clear all request handlers from the network.""" + + for event_name in self.subscriptions: + net_event = NetworkEvent(event_name) + for callback_id in self.subscriptions[event_name]: + self.conn.remove_callback(net_event, callback_id) + self._remove_intercept(self.callbacks[callback_id]) + del self.callbacks[callback_id] + params = {} + params["events"] = [event_name] + self.conn.execute(self.command_builder("session.unsubscribe", params)) + self.subscriptions = {} + def add_auth_handler(self, username, password): """Add an authentication handler to the network. diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 8992e003bc588..2cc934fb37482 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -28,14 +28,14 @@ def test_network_initialized(driver): @pytest.mark.xfail_safari def test_add_intercept(driver, pages): - result = driver.network.add_intercept() + result = driver.network._add_intercept() assert result is not None, "Intercept not added" @pytest.mark.xfail_safari def test_remove_intercept(driver): - result = driver.network.add_intercept() - driver.network.remove_intercept(result["intercept"]) + result = driver.network._add_intercept() + driver.network._remove_intercept(result["intercept"]) assert driver.network.intercepts == [], "Intercept not removed" @@ -55,6 +55,25 @@ def callback(request: Request): assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" +@pytest.mark.xfail_safari +def test_clear_request_handlers(driver, pages): + requests = [] + + def callback(request: Request): + requests.append(request) + + callback_id_1 = driver.network.add_request_handler("before_request", callback) + assert callback_id_1 is not None, "Request handler not added" + callback_id_2 = driver.network.add_request_handler("before_request", callback) + assert callback_id_2 is not None, "Request handler not added" + + driver.network.clear_request_handlers() + + pages.load("formPage.html") + assert not requests, "Requests intercepted" + assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" + + @pytest.mark.xfail_safari def test_continue_request(driver, pages): From 5b75c6d737c574b88dc2e450a38610cbc5f70b69 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 4 Apr 2025 12:09:12 -0400 Subject: [PATCH 75/77] marked xfail for chromium bidi issues --- py/test/selenium/webdriver/common/bidi_network_tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index 2cc934fb37482..e51ccec19c10e 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -74,6 +74,8 @@ def callback(request: Request): assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" +@pytest.mark.xfail_chrome +@pytest.mark.xfail_edge @pytest.mark.xfail_safari def test_continue_request(driver, pages): @@ -86,6 +88,8 @@ def callback(request: Request): assert driver.find_element(By.NAME, "login").is_displayed(), "Request not continued" +@pytest.mark.xfail_chrome +@pytest.mark.xfail_edge @pytest.mark.xfail_safari def test_continue_with_auth(driver): From a166557b8241227a48c5a64671fecc9e59be62f4 Mon Sep 17 00:00:00 2001 From: Simon Benzer Date: Fri, 4 Apr 2025 12:57:42 -0400 Subject: [PATCH 76/77] added remove_auth_handler, all tests passing --- py/selenium/webdriver/common/bidi/network.py | 10 ++++++++++ .../selenium/webdriver/common/bidi_network_tests.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index 866ed5d6b526e..e0c45550af116 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -255,6 +255,16 @@ def _callback(request): return self.add_request_handler(event, _callback) + def remove_auth_handler(self, callback_id): + """Remove an authentication handler from the network. + + Parameters: + ---------- + callback_id (int): The callback id to remove. + """ + event = "auth_required" + self.remove_request_handler(event, callback_id) + class Request: """Represents an intercepted network request.""" diff --git a/py/test/selenium/webdriver/common/bidi_network_tests.py b/py/test/selenium/webdriver/common/bidi_network_tests.py index e51ccec19c10e..4d2ca58edc143 100644 --- a/py/test/selenium/webdriver/common/bidi_network_tests.py +++ b/py/test/selenium/webdriver/common/bidi_network_tests.py @@ -97,3 +97,13 @@ def test_continue_with_auth(driver): assert callback_id is not None, "Request handler not added" driver.get("https://httpbin.org/basic-auth/user/passwd") assert "authenticated" in driver.page_source, "Authorization failed" + + +@pytest.mark.xfail_chrome +@pytest.mark.xfail_edge +@pytest.mark.xfail_safari +def test_remove_auth_handler(driver): + callback_id = driver.network.add_auth_handler("user", "passwd") + assert callback_id is not None, "Request handler not added" + driver.network.remove_auth_handler(callback_id) + assert driver.network.intercepts == [], "Intercept not removed" From 1f1acc3b127fd78014a0042706d5b6bbbc0366c3 Mon Sep 17 00:00:00 2001 From: Simon Benzer <69980130+shbenzer@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:20:41 -0400 Subject: [PATCH 77/77] prevented editing iterator during iteration --- py/selenium/webdriver/common/bidi/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/py/selenium/webdriver/common/bidi/network.py b/py/selenium/webdriver/common/bidi/network.py index e0c45550af116..36a0f9be32677 100644 --- a/py/selenium/webdriver/common/bidi/network.py +++ b/py/selenium/webdriver/common/bidi/network.py @@ -111,7 +111,8 @@ def _remove_intercept(self, intercept=None): If intercept is None, all intercepts will be removed. """ if intercept is None: - for intercept_id in self.intercepts: # remove all intercepts + intercepts_to_remove = self.intercepts.copy() # create a copy before iterating + for intercept_id in intercepts_to_remove: # remove all intercepts self.conn.execute(self.command_builder("network.removeIntercept", {"intercept": intercept_id})) self.intercepts.remove(intercept_id) else: