From 69c84cc4dbbff986b983790d5af1997aa53789c4 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 09:39:42 +0000 Subject: [PATCH 01/10] [py] Add bidi tests for log, errors and a few integration tests --- .../webdriver/common/bidi_errors_tests.py | 206 ++++++++ .../common/bidi_integration_tests.py | 449 ++++++++++++++++++ .../webdriver/common/bidi_log_tests.py | 352 ++++++++++++++ 3 files changed, 1007 insertions(+) create mode 100644 py/test/selenium/webdriver/common/bidi_errors_tests.py create mode 100644 py/test/selenium/webdriver/common/bidi_integration_tests.py create mode 100644 py/test/selenium/webdriver/common/bidi_log_tests.py diff --git a/py/test/selenium/webdriver/common/bidi_errors_tests.py b/py/test/selenium/webdriver/common/bidi_errors_tests.py new file mode 100644 index 0000000000000..25f11fdc62492 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_errors_tests.py @@ -0,0 +1,206 @@ +# 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 selenium.common.exceptions import InvalidArgumentException +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common.by import By + + +def test_errors_module_initialized(driver): + """Test that the errors module is accessible.""" + assert driver.script is not None + + +def test_invalid_browsing_context_id(driver): + """Test that invalid browsing context ID raises an error.""" + with pytest.raises(WebDriverException): + driver.browsing_context.close("invalid-context-id") + + +def test_invalid_script_evaluate(driver): + """Test that invalid script evaluation raises an error.""" + with pytest.raises(WebDriverException): + driver.script.evaluate("invalid javascript }{", await_promise=False) + + +def test_invalid_navigation_url(driver): + """Test that invalid URL navigation raises an error.""" + with pytest.raises((WebDriverException, ValueError)): + driver.browsing_context.navigate(None) + + +def test_invalid_add_preload_script_no_function_declaration(driver): + """Test that adding preload script without proper format raises an error.""" + with pytest.raises((WebDriverException, InvalidArgumentException)): + driver.script.add_preload_script("not a valid script;}") + + +def test_remove_nonexistent_user_context(driver): + """Test that removing non-existent user context raises an error.""" + with pytest.raises(WebDriverException): + driver.browser.remove_user_context("non-existent-context-id") + + +def test_invalid_call_function_parameters(driver, pages): + """Test that call_function with invalid parameters raises an error.""" + pages.load("blank.html") + + # Try to call with invalid script + with pytest.raises(WebDriverException): + driver.script.call_function( + "invalid function }{", + [], + await_promise=False + ) + + +def test_invalid_screenshot_clipping(driver, pages): + """Test that invalid screenshot clipping region raises an error.""" + pages.load("blank.html") + context_id = driver.current_context_id + + # Try with invalid clipping region (negative dimensions) + with pytest.raises((WebDriverException, ValueError)): + driver.browsing_context.capture_screenshot( + context_id, + clip={"x": 0, "y": 0, "width": -100, "height": 100} + ) + + +def test_invalid_set_viewport_dimensions(driver, pages): + """Test that setting invalid viewport dimensions raises an error.""" + pages.load("blank.html") + context_id = driver.current_context_id + + # Try with negative dimensions + with pytest.raises((WebDriverException, ValueError)): + driver.browsing_context.set_viewport( + context_id, + viewport={"width": -1, "height": -1} + ) + + +def test_invalid_set_cookie(driver, pages): + """Test that setting invalid cookie raises an error.""" + pages.load("blank.html") + + # Try to set cookie with missing required fields + with pytest.raises((WebDriverException, TypeError, InvalidArgumentException)): + driver.storage.set_cookie(None) + + +def test_invalid_network_intercept_phase(driver): + """Test that invalid network intercept phase raises an error.""" + with pytest.raises((WebDriverException, ValueError)): + driver.network.add_intercept( + phases=["invalid_phase"], + url_patterns=[] + ) + + +def test_invalid_emulation_timezone(driver): + """Test that invalid timezone string raises an error.""" + with pytest.raises((WebDriverException, ValueError)): + driver.emulation.set_timezone_override("Invalid/Timezone") + + +def test_invalid_geolocation_coordinates(driver): + """Test that invalid geolocation coordinates raise an error.""" + with pytest.raises((WebDriverException, ValueError)): + driver.emulation.set_geolocation_override( + latitude=999, # Invalid latitude + longitude=180 + ) + + +def test_invalid_script_realm_type(driver): + """Test that invalid realm type raises an error.""" + with pytest.raises((WebDriverException, ValueError)): + driver.script.get_realms(realm_type="invalid_realm_type") + + +def test_protocol_error_with_invalid_session(driver): + """Test that operations on invalid session context raise errors.""" + # Try to use a context that doesn't exist + with pytest.raises(WebDriverException): + driver.browsing_context.navigate("https://www.example.com", context_id="nonexistent") + + +def test_error_on_locate_nodes_with_invalid_selector(driver, pages): + """Test that invalid locator strategy raises an error.""" + pages.load("blank.html") + context_id = driver.current_context_id + + # Try with invalid selector strategy + with pytest.raises((WebDriverException, ValueError)): + driver.browsing_context.locate_nodes( + locator={"type": "invalid_strategy", "value": "test"}, + context_id=context_id + ) + + +def test_error_on_perform_actions_invalid_action(driver, pages): + """Test that invalid action sequence raises an error.""" + pages.load("blank.html") + + with pytest.raises((WebDriverException, InvalidArgumentException)): + driver.input.perform_actions(actions=None) + + +def test_error_on_multiple_context_operations(driver): + """Test error handling with rapid context operations.""" + # Create a context + context1 = driver.browser.create_user_context() + assert context1 is not None + + # Try to remove it immediately after (should succeed) + driver.browser.remove_user_context(context1) + + # Try to remove again (should fail) + with pytest.raises(WebDriverException): + driver.browser.remove_user_context(context1) + + +class TestBidiErrorHandling: + """Test class for error handling in BiDi operations.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + + def test_error_on_subscribe_invalid_event(self, driver): + """Test that subscribing to invalid event type raises error.""" + with pytest.raises((WebDriverException, ValueError)): + driver.session.subscribe(events=["invalid.event.type"]) + + def test_error_on_unsubscribe_without_subscription(self, driver): + """Test that unsubscribing from non-existent subscription raises error.""" + with pytest.raises(WebDriverException): + driver.session.unsubscribe(events=["non.subscribed.event"]) + + def test_error_recovery_after_failed_navigation(self, driver): + """Test that driver can recover after failed navigation.""" + # First, try an invalid navigation + with pytest.raises(WebDriverException): + driver.browsing_context.navigate(None) + + # Driver should still be functional + driver.get("about:blank") + assert driver.find_element(By.TAG_NAME, "body") is not None diff --git a/py/test/selenium/webdriver/common/bidi_integration_tests.py b/py/test/selenium/webdriver/common/bidi_integration_tests.py new file mode 100644 index 0000000000000..5301fb6c43853 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_integration_tests.py @@ -0,0 +1,449 @@ +# 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 selenium.webdriver.common.by import By +from selenium.webdriver.common.window import WindowTypes +from selenium.webdriver.support.ui import WebDriverWait + + +class TestBidiNetworkWithCookies: + """Test integration of network and storage modules.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + + def test_cookies_sent_in_network_request(self, driver, pages): + """Test that cookies are included in network requests.""" + pages.load("blank.html") + + # Set a cookie + driver.add_cookie({"name": "test_cookie", "value": "test_value"}) + + network_data = [] + + def on_before_request(request): + network_data.append(request) + + handler_id = driver.network.add_before_request_handler(on_before_request) + + try: + # Make a request + driver.get(pages.url("blank.html")) + + # Verify the network event was captured + WebDriverWait(driver, 5).until(lambda _: network_data) + assert len(network_data) > 0 + finally: + driver.network.remove_before_request_handler(handler_id) + driver.delete_all_cookies() + + def test_cookie_modification_affects_requests(self, driver, pages): + """Test that modifying cookies affects subsequent requests.""" + pages.load("blank.html") + + # Add first cookie + driver.add_cookie({"name": "cookie1", "value": "value1"}) + + cookies_before = driver.get_cookies() + assert len(cookies_before) >= 1 + + # Add second cookie + driver.add_cookie({"name": "cookie2", "value": "value2"}) + + cookies_after = driver.get_cookies() + assert len(cookies_after) > len(cookies_before) + + # Cleanup + driver.delete_all_cookies() + + def test_network_interception_with_stored_state(self, driver, pages): + """Test network interception with stored cookies.""" + pages.load("blank.html") + + # Set up storage + driver.add_cookie({"name": "auth_token", "value": "secret123"}) + + intercepted = [] + + def intercept(request): + intercepted.append(request) + # Check if request has access to cookies + if hasattr(request, 'headers'): + pass + + handler_id = driver.network.add_before_request_handler(intercept) + + try: + driver.get(pages.url("blank.html")) + WebDriverWait(driver, 5).until(lambda _: intercepted) + finally: + driver.network.remove_before_request_handler(handler_id) + driver.delete_all_cookies() + + +class TestBidiScriptWithNavigation: + """Test integration of script and navigation modules.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + driver.delete_all_cookies() + + def test_preload_script_executes_on_page_load(self, driver, pages): + """Test that preload scripts execute on page navigation.""" + script_executions = [] + + # Add a preload script + preload_id = driver.script.add_preload_script( + "window.__test_var = 'preload_executed';" + ) + + try: + # Navigate to a new page + pages.load("blank.html") + + # Verify the preload script executed + result = driver.execute_script("return window.__test_var;") + assert result == "preload_executed" + finally: + driver.script.remove_preload_script(preload_id) + + def test_script_evaluation_after_navigation(self, driver, pages): + """Test script evaluation after page navigation.""" + # First page + pages.load("blank.html") + driver.execute_script("window.page1_loaded = true;") + + # Navigate to different page + pages.load("blank.html") + + # Previous page variable should not exist + result = driver.execute_script("return window.page1_loaded;") + assert result is None + + # New variable should work + driver.execute_script("window.page2_loaded = true;") + result = driver.execute_script("return window.page2_loaded;") + assert result is True + + def test_call_function_with_navigation(self, driver, pages): + """Test calling functions across navigations.""" + pages.load("blank.html") + + # Define a global function + driver.execute_script(""" + window.testFunction = function(x) { + return x * 2; + }; + """) + + # Call the function + result = driver.execute_script("return window.testFunction(5);") + assert result == 10 + + # Navigate + pages.load("blank.html") + + # Function should no longer exist on new page + result = driver.execute_script("return typeof window.testFunction;") + assert result == "undefined" + + +class TestBidiEmulationWithNavigation: + """Test integration of emulation and navigation modules.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + + def test_user_agent_persists_across_navigation(self, driver, pages): + """Test that user agent emulation persists across page navigations.""" + try: + pages.load("blank.html") + + # Set user agent + driver.emulation.set_user_agent_override("Custom User Agent") + + # Verify user agent + ua = driver.execute_script("return navigator.userAgent;") + assert "Custom User Agent" in ua + + # Navigate to different page + pages.load("blank.html") + + # Verify user agent still set + ua_after = driver.execute_script("return navigator.userAgent;") + assert "Custom User Agent" in ua_after + finally: + driver.emulation.set_user_agent_override() # Reset + + def test_timezone_applies_to_script(self, driver, pages): + """Test that timezone emulation affects JavaScript date operations.""" + try: + pages.load("blank.html") + + # Set timezone + driver.emulation.set_timezone_override("America/New_York") + + # Get timezone-sensitive value + offset = driver.execute_script(""" + return new Date().getTimezoneOffset(); + """) + + assert offset is not None + finally: + driver.emulation.set_timezone_override() # Reset + + +class TestBidiContextManagement: + """Test integration of context creation and management.""" + + def test_create_and_navigate_context(self, driver): + """Test creating a new context and navigating within it.""" + new_context = driver.browser.create_user_context() + + try: + assert new_context is not None + + # Navigate in the new context + driver.get("about:blank", context_id=new_context) + finally: + driver.browser.remove_user_context(new_context) + + def test_multiple_contexts_independent(self, driver, pages): + """Test that multiple contexts maintain independent state.""" + context1 = driver.browser.create_user_context() + context2 = driver.browser.create_user_context() + + try: + # Load page in context 1 + driver.get(pages.url("blank.html"), context_id=context1) + + # Load page in context 2 + driver.get(pages.url("blank.html"), context_id=context2) + + # Set cookie in context 1 + driver.add_cookie( + {"name": "context1_cookie", "value": "value1"}, + context_id=context1 + ) + + # Context 2 should not have this cookie + cookies_context2 = driver.get_cookies(context_id=context2) + cookie_names = [c.get("name") for c in cookies_context2] + assert "context1_cookie" not in cookie_names + finally: + driver.browser.remove_user_context(context1) + driver.browser.remove_user_context(context2) + + def test_context_window_operations(self, driver): + """Test window operations within contexts.""" + context = driver.browser.create_user_context() + + try: + # Create new window in context + new_window = driver.browsing_context.create( + type=WindowTypes.TAB, + context_id=context + ) + + assert new_window is not None + + # Close the window + driver.browsing_context.close(new_window) + finally: + driver.browser.remove_user_context(context) + + +class TestBidiEventHandlerManagement: + """Test integration of multiple event handlers.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + + def test_multiple_handlers_same_event(self, driver): + """Test multiple handlers for the same event type.""" + events1 = [] + events2 = [] + + handler1 = driver.browsing_context.add_context_created_handler(events1.append) + handler2 = driver.browsing_context.add_context_created_handler(events2.append) + + try: + # Create a new context + new_context = driver.browser.create_user_context() + + # Both handlers should receive the event + WebDriverWait(driver, 5).until(lambda _: len(events1) > 0 and len(events2) > 0) + assert len(events1) > 0 + assert len(events2) > 0 + + # Cleanup + driver.browser.remove_user_context(new_context) + finally: + driver.browsing_context.remove_context_created_handler(handler1) + driver.browsing_context.remove_context_created_handler(handler2) + + def test_handler_removal_prevents_events(self, driver): + """Test that removing handler prevents event delivery.""" + events1 = [] + events2 = [] + + handler1 = driver.browsing_context.add_context_created_handler(events1.append) + handler2 = driver.browsing_context.add_context_created_handler(events2.append) + + try: + # Create first context + context1 = driver.browser.create_user_context() + WebDriverWait(driver, 5).until(lambda _: len(events1) > 0 and len(events2) > 0) + + initial_events1 = len(events1) + initial_events2 = len(events2) + + # Remove first handler + driver.browsing_context.remove_context_created_handler(handler1) + + # Create second context + context2 = driver.browser.create_user_context() + WebDriverWait(driver, 5).until(lambda _: len(events2) > initial_events2) + + # First handler should not receive new events + assert len(events1) == initial_events1 + assert len(events2) > initial_events2 + + # Cleanup + driver.browser.remove_user_context(context1) + driver.browser.remove_user_context(context2) + finally: + driver.browsing_context.remove_context_created_handler(handler2) + + +class TestBidiScreenshotWithNavigation: + """Test integration of screenshot and navigation features.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + + def test_screenshot_after_navigation(self, driver, pages): + """Test taking screenshot after page navigation.""" + pages.load("blank.html") + + # Take screenshot + screenshot1 = driver.browsing_context.capture_screenshot( + driver.current_context_id + ) + assert screenshot1 is not None + + # Navigate to different page + driver.find_element(By.TAG_NAME, "body") + + # Take another screenshot + screenshot2 = driver.browsing_context.capture_screenshot( + driver.current_context_id + ) + assert screenshot2 is not None + + # Screenshots should exist (may be same or different) + assert screenshot1 is not None + assert screenshot2 is not None + + def test_screenshot_with_viewport_change(self, driver, pages): + """Test screenshot behavior with viewport changes.""" + pages.load("blank.html") + context_id = driver.current_context_id + + # Take initial screenshot + screenshot1 = driver.browsing_context.capture_screenshot(context_id) + assert screenshot1 is not None + + # Change viewport + driver.browsing_context.set_viewport( + context_id, + viewport={"width": 1024, "height": 768} + ) + + # Take screenshot with new viewport + screenshot2 = driver.browsing_context.capture_screenshot(context_id) + assert screenshot2 is not None + + +class TestBidiStorageWithContexts: + """Test storage operations across multiple contexts.""" + + def test_cookies_independent_across_contexts(self, driver, pages): + """Test that cookies are independent across user contexts.""" + context1 = driver.browser.create_user_context() + context2 = driver.browser.create_user_context() + + try: + pages.load("blank.html") + + # Add cookie to context 1 + driver.add_cookie( + {"name": "ctx1_cookie", "value": "value1"}, + context_id=context1 + ) + + # Get cookies from context 2 + cookies = driver.get_cookies(context_id=context2) + cookie_names = [c.get("name") for c in cookies] + + # Context 2 should not have context 1's cookie + assert "ctx1_cookie" not in cookie_names + finally: + driver.browser.remove_user_context(context1) + driver.browser.remove_user_context(context2) + + def test_delete_cookies_affects_only_target_context(self, driver, pages): + """Test that deleting cookies only affects the target context.""" + context1 = driver.browser.create_user_context() + context2 = driver.browser.create_user_context() + + try: + # Add same cookie name to both contexts + driver.add_cookie( + {"name": "shared_cookie", "value": "ctx1"}, + context_id=context1 + ) + driver.add_cookie( + {"name": "shared_cookie", "value": "ctx2"}, + context_id=context2 + ) + + # Delete cookie from context 1 + driver.delete_cookie("shared_cookie", context_id=context1) + + # Context 1 should not have the cookie + cookies1 = driver.get_cookies(context_id=context1) + assert not any(c.get("name") == "shared_cookie" for c in cookies1) + + # Context 2 should still have it + cookies2 = driver.get_cookies(context_id=context2) + assert any(c.get("name") == "shared_cookie" for c in cookies2) + finally: + driver.browser.remove_user_context(context1) + driver.browser.remove_user_context(context2) diff --git a/py/test/selenium/webdriver/common/bidi_log_tests.py b/py/test/selenium/webdriver/common/bidi_log_tests.py new file mode 100644 index 0000000000000..2f90259f364f0 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_log_tests.py @@ -0,0 +1,352 @@ +# 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 time + +import pytest + +from selenium.webdriver.common.bidi.log import LogLevel +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait + + +def test_log_module_initialized(driver): + """Test that the log module is initialized properly.""" + assert driver.script is not None + + +class TestBidiLogging: + """Test class for BiDi logging functionality.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("bidi/logEntryAdded.html") + + def test_console_log_message(self, driver): + """Test capturing console.log messages.""" + log_entries = [] + + def callback(log_entry): + log_entries.append(log_entry) + + handler_id = driver.script.add_console_message_handler(callback) + + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + assert log_entries[0].text == "Hello, world!" + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_console_warn_message(self, driver): + """Test capturing console.warn messages.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleWarn").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + assert "warning" in log_entries[0].text.lower() or log_entries[0].text == "Warning message" + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_console_error_message(self, driver): + """Test capturing console.error messages.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleError").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + assert "error" in log_entries[0].text.lower() or log_entries[0].text == "Error message" + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_console_debug_message(self, driver): + """Test capturing console.debug messages.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleDebug").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_console_info_message(self, driver): + """Test capturing console.info messages.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleInfo").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_multiple_console_messages(self, driver): + """Test capturing multiple console messages.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleLog").click() + driver.find_element(By.ID, "consoleWarn").click() + driver.find_element(By.ID, "consoleError").click() + + WebDriverWait(driver, 5).until(lambda _: len(log_entries) >= 3) + + assert len(log_entries) >= 3 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_add_and_remove_handler(self, driver): + """Test adding and removing log handlers.""" + log_entries1 = [] + log_entries2 = [] + + handler_id1 = driver.script.add_console_message_handler(log_entries1.append) + handler_id2 = driver.script.add_console_message_handler(log_entries2.append) + + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until(lambda _: len(log_entries1) > 0 and len(log_entries2) > 0) + + assert len(log_entries1) > 0 + assert len(log_entries2) > 0 + + # Remove first handler + driver.script.remove_console_message_handler(handler_id1) + + initial_count1 = len(log_entries1) + initial_count2 = len(log_entries2) + + # Trigger another message + driver.find_element(By.ID, "consoleWarn").click() + WebDriverWait(driver, 5).until(lambda _: len(log_entries2) > initial_count2) + + # First handler should not receive new messages + assert len(log_entries1) == initial_count1 + assert len(log_entries2) > initial_count2 + finally: + driver.script.remove_console_message_handler(handler_id2) + + def test_log_entry_text_content(self, driver): + """Test log entry contains expected text content.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert hasattr(log_entries[0], 'text') + assert len(log_entries[0].text) > 0 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_log_entry_level(self, driver): + """Test log entry has level information.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert hasattr(log_entries[0], 'level') or hasattr(log_entries[0], 'method') + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_log_entry_timestamp(self, driver): + """Test log entry contains timestamp information.""" + log_entries = [] + before_time = time.time() * 1000 + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + after_time = time.time() * 1000 + + assert hasattr(log_entries[0], 'timestamp') + timestamp = log_entries[0].timestamp + assert before_time <= timestamp <= after_time + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_exception_messages_logged(self, driver): + """Test that exception messages are logged.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + # Execute script that throws an error + driver.execute_script(""" + try { + throw new Error("Test error"); + } catch (e) { + console.error(e.message); + } + """) + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_log_handler_receives_all_levels(self, driver): + """Test that a single handler can receive all log levels.""" + log_levels = [] + + def callback(entry): + log_levels.append(entry) + + handler_id = driver.script.add_console_message_handler(callback) + + try: + driver.execute_script(""" + console.log('log'); + console.warn('warn'); + console.error('error'); + console.debug('debug'); + console.info('info'); + """) + + WebDriverWait(driver, 5).until(lambda _: len(log_levels) >= 5) + + assert len(log_levels) >= 5 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_log_with_multiple_arguments(self, driver): + """Test console.log with multiple arguments.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.execute_script("console.log('arg1', 'arg2', 'arg3');") + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + # Entry should contain all arguments + assert hasattr(log_entries[0], 'args') or hasattr(log_entries[0], 'text') + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_log_handler_context_id(self, driver): + """Test log entry contains context information.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) + + # Log entry should have context information + assert hasattr(log_entries[0], 'context') or hasattr(log_entries[0], 'source_url') + finally: + driver.script.remove_console_message_handler(handler_id) + + +class TestBidiJavaScriptErrors: + """Test class for JavaScript error logging.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + pages.load("blank.html") + + def test_syntax_error_logged(self, driver): + """Test that syntax errors are logged.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + # Execute invalid JavaScript (should trigger error logging) + try: + driver.execute_script("{{invalid}}") + except Exception: + pass + + # Give it a moment to log + time.sleep(0.5) + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_runtime_error_logged(self, driver): + """Test that runtime errors are logged.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.execute_script(""" + try { + undefined_function(); + } catch (e) { + console.error(e.message); + } + """) + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + finally: + driver.script.remove_console_message_handler(handler_id) + + def test_caught_exception_logged(self, driver): + """Test that caught exceptions can be logged explicitly.""" + log_entries = [] + + handler_id = driver.script.add_console_message_handler(log_entries.append) + + try: + driver.execute_script(""" + try { + throw new Error("Custom error"); + } catch (error) { + console.error("Caught:", error.message); + } + """) + WebDriverWait(driver, 5).until(lambda _: log_entries) + + assert len(log_entries) > 0 + finally: + driver.script.remove_console_message_handler(handler_id) From b15083753d7b4847313ad4d3b4dae37f9aa53a29 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 11:25:11 +0000 Subject: [PATCH 02/10] Add script tests --- .../webdriver/common/bidi_script_tests.py | 532 +++++++++++++++++- 1 file changed, 510 insertions(+), 22 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_script_tests.py b/py/test/selenium/webdriver/common/bidi_script_tests.py index 35f8e455573be..c19d78e1a0579 100644 --- a/py/test/selenium/webdriver/common/bidi_script_tests.py +++ b/py/test/selenium/webdriver/common/bidi_script_tests.py @@ -21,6 +21,7 @@ from selenium.webdriver.common.bidi.script import RealmType, ResultOwnership from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait +from selenium.common.exceptions import WebDriverException def has_shadow_root(node): @@ -159,7 +160,9 @@ def test_add_preload_script(driver, pages): # Check if the preload script was executed result = driver.script._evaluate( - "window.preloadExecuted", {"context": driver.current_window_handle}, await_promise=False + "window.preloadExecuted", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.result["value"] is True @@ -168,15 +171,21 @@ def test_add_preload_script_with_arguments(driver, pages): """Test adding a preload script with channel arguments.""" function_declaration = "(channelFunc) => { channelFunc('test_value'); window.preloadValue = 'received'; }" - arguments = [{"type": "channel", "value": {"channel": "test-channel", "ownership": "root"}}] + arguments = [ + {"type": "channel", "value": {"channel": "test-channel", "ownership": "root"}} + ] - script_id = driver.script._add_preload_script(function_declaration, arguments=arguments) + script_id = driver.script._add_preload_script( + function_declaration, arguments=arguments + ) assert script_id is not None pages.load("blank.html") result = driver.script._evaluate( - "window.preloadValue", {"context": driver.current_window_handle}, await_promise=False + "window.preloadValue", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.result["value"] == "received" @@ -186,13 +195,17 @@ def test_add_preload_script_with_contexts(driver, pages): function_declaration = "() => { window.contextSpecific = true; }" contexts = [driver.current_window_handle] - script_id = driver.script._add_preload_script(function_declaration, contexts=contexts) + script_id = driver.script._add_preload_script( + function_declaration, contexts=contexts + ) assert script_id is not None pages.load("blank.html") result = driver.script._evaluate( - "window.contextSpecific", {"context": driver.current_window_handle}, await_promise=False + "window.contextSpecific", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.result["value"] is True @@ -207,13 +220,17 @@ def test_add_preload_script_with_user_contexts(driver, pages): user_contexts = [user_context] - script_id = driver.script._add_preload_script(function_declaration, user_contexts=user_contexts) + script_id = driver.script._add_preload_script( + function_declaration, user_contexts=user_contexts + ) assert script_id is not None pages.load("blank.html") result = driver.script._evaluate( - "window.contextSpecific", {"context": driver.current_window_handle}, await_promise=False + "window.contextSpecific", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.result["value"] is True @@ -222,14 +239,18 @@ def test_add_preload_script_with_sandbox(driver, pages): """Test adding a preload script with sandbox.""" function_declaration = "() => { window.sandboxScript = true; }" - script_id = driver.script._add_preload_script(function_declaration, sandbox="test-sandbox") + script_id = driver.script._add_preload_script( + function_declaration, sandbox="test-sandbox" + ) assert script_id is not None pages.load("blank.html") # calling evaluate without sandbox should return undefined result = driver.script._evaluate( - "window.sandboxScript", {"context": driver.current_window_handle}, await_promise=False + "window.sandboxScript", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.result["type"] == "undefined" @@ -246,8 +267,12 @@ def test_add_preload_script_invalid_arguments(driver): """Test that providing both contexts and user_contexts raises an error.""" function_declaration = "() => {}" - with pytest.raises(ValueError, match="Cannot specify both contexts and user_contexts"): - driver.script._add_preload_script(function_declaration, contexts=["context1"], user_contexts=["user1"]) + with pytest.raises( + ValueError, match="Cannot specify both contexts and user_contexts" + ): + driver.script._add_preload_script( + function_declaration, contexts=["context1"], user_contexts=["user1"] + ) def test_remove_preload_script(driver, pages): @@ -262,7 +287,9 @@ def test_remove_preload_script(driver, pages): # The script should not have executed result = driver.script._evaluate( - "typeof window.removableScript", {"context": driver.current_window_handle}, await_promise=False + "typeof window.removableScript", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.result["value"] == "undefined" @@ -271,7 +298,9 @@ def test_evaluate_expression(driver, pages): """Test evaluating a simple expression.""" pages.load("blank.html") - result = driver.script._evaluate("1 + 2", {"context": driver.current_window_handle}, await_promise=False) + result = driver.script._evaluate( + "1 + 2", {"context": driver.current_window_handle}, await_promise=False + ) assert result.realm is not None assert result.result["type"] == "number" @@ -284,7 +313,9 @@ def test_evaluate_with_await_promise(driver, pages): pages.load("blank.html") result = driver.script._evaluate( - "Promise.resolve(42)", {"context": driver.current_window_handle}, await_promise=True + "Promise.resolve(42)", + {"context": driver.current_window_handle}, + await_promise=True, ) assert result.result["type"] == "number" @@ -296,7 +327,9 @@ def test_evaluate_with_exception(driver, pages): pages.load("blank.html") result = driver.script._evaluate( - "throw new Error('Test error')", {"context": driver.current_window_handle}, await_promise=False + "throw new Error('Test error')", + {"context": driver.current_window_handle}, + await_promise=False, ) assert result.exception_details is not None @@ -334,7 +367,11 @@ def test_evaluate_with_serialization_options(driver, pages): """Test evaluating with serialization options.""" pages.load("shadowRootPage.html") - serialization_options = {"maxDomDepth": 2, "maxObjectDepth": 2, "includeShadowTree": "all"} + serialization_options = { + "maxDomDepth": 2, + "maxObjectDepth": 2, + "includeShadowTree": "all", + } result = driver.script._evaluate( "document.body", @@ -386,7 +423,9 @@ def test_call_function_with_this(driver, pages): # First set up an object driver.script._evaluate( - "window.testObj = { value: 10 }", {"context": driver.current_window_handle}, await_promise=False + "window.testObj = { value: 10 }", + {"context": driver.current_window_handle}, + await_promise=False, ) result = driver.script._call_function( @@ -419,7 +458,11 @@ def test_call_function_with_serialization_options(driver, pages): """Test calling a function with serialization options.""" pages.load("shadowRootPage.html") - serialization_options = {"maxDomDepth": 2, "maxObjectDepth": 2, "includeShadowTree": "all"} + serialization_options = { + "maxDomDepth": 2, + "maxObjectDepth": 2, + "includeShadowTree": "all", + } result = driver.script._call_function( "() => document.body", @@ -455,7 +498,9 @@ def test_call_function_with_await_promise(driver, pages): pages.load("blank.html") result = driver.script._call_function( - "() => Promise.resolve('async result')", await_promise=True, target={"context": driver.current_window_handle} + "() => Promise.resolve('async result')", + await_promise=True, + target={"context": driver.current_window_handle}, ) assert result.result["type"] == "string" @@ -534,7 +579,10 @@ def test_disown_handles(driver, pages): # Create an object with root ownership (this will return a handle) result = driver.script._evaluate( - "({foo: 'bar'})", target={"context": driver.current_window_handle}, await_promise=False, result_ownership="root" + "({foo: 'bar'})", + target={"context": driver.current_window_handle}, + await_promise=False, + result_ownership="root", ) handle = result.result["handle"] @@ -551,7 +599,9 @@ def test_disown_handles(driver, pages): assert result_before.result["value"] == "bar" # Disown the handle - driver.script._disown(handles=[handle], target={"context": driver.current_window_handle}) + driver.script._disown( + handles=[handle], target={"context": driver.current_window_handle} + ) # Try using the disowned handle (this should fail) with pytest.raises(Exception): @@ -870,3 +920,441 @@ def test_execute_script_with_nested_objects(driver, pages): assert value_dict["userName"] == "John" assert value_dict["userAge"] == 30 assert value_dict["hobbyCount"] == 2 + + +class TestBidiScriptExecution: + """Test script execution via execute_script.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test.""" + pages.load("blank.html") + + def test_execute_script_returns_string(self, driver): + """Test executing script that returns string.""" + result = driver.execute_script("return 'hello';") + assert result == "hello" + + def test_execute_script_returns_number(self, driver): + """Test executing script that returns number.""" + result = driver.execute_script("return 42;") + assert result == 42 + + def test_execute_script_returns_boolean(self, driver): + """Test executing script that returns boolean.""" + result = driver.execute_script("return true;") + assert result is True + + def test_execute_script_returns_null(self, driver): + """Test executing script that returns null.""" + result = driver.execute_script("return null;") + assert result is None + + def test_execute_script_returns_object(self, driver): + """Test executing script that returns object.""" + result = driver.execute_script("return {x: 1, y: 2};") + assert isinstance(result, dict) + assert result["x"] == 1 + + def test_execute_script_returns_array(self, driver): + """Test executing script that returns array.""" + result = driver.execute_script("return [1, 2, 3, 4, 5];") + assert isinstance(result, list) + assert len(result) == 5 + + def test_execute_script_dom_query(self, driver, pages): + """Test executing script that queries DOM.""" + pages.load("formPage.html") + result = driver.execute_script( + "return document.querySelectorAll('input').length;" + ) + assert result > 0 + + def test_execute_script_with_arguments(self, driver): + """Test executing script with arguments.""" + result = driver.execute_script("return arguments[0] * arguments[1];", 3, 5) + assert result == 15 + + +class TestBidiScriptGlobalState: + """Test script execution with global state management.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test.""" + pages.load("blank.html") + + def test_global_state_persistence(self, driver): + """Test that global state persists across script calls.""" + driver.execute_script("window.testVar = 42;") + result = driver.execute_script("return window.testVar;") + assert result == 42 + + def test_multiple_global_variables(self, driver): + """Test managing multiple global variables.""" + driver.execute_script( + """ + window.var1 = 'first'; + window.var2 = 'second'; + window.var3 = 'third'; + """ + ) + + result = driver.execute_script( + """ + return { + v1: window.var1, + v2: window.var2, + v3: window.var3 + }; + """ + ) + + assert result["v1"] == "first" + assert result["v2"] == "second" + assert result["v3"] == "third" + + def test_function_definition_in_global_scope(self, driver): + """Test defining functions in global scope.""" + driver.execute_script( + """ + window.multiply = function(a, b) { + return a * b; + }; + """ + ) + + result = driver.execute_script("return window.multiply(3, 7);") + assert result == 21 + + def test_complex_object_in_global_scope(self, driver): + """Test storing complex objects globally.""" + driver.execute_script( + """ + window.data = { + users: [ + {name: 'Alice', age: 30}, + {name: 'Bob', age: 25} + ], + metadata: { + version: '1.0', + timestamp: Date.now() + } + }; + """ + ) + + result = driver.execute_script("return window.data.users.length;") + assert result == 2 + + +class TestBidiScriptPreloadScripts: + """Test preload script lifecycle and edge cases.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test.""" + pages.load("blank.html") + + def test_multiple_preload_scripts(self, driver, pages): + """Test adding multiple preload scripts.""" + id1 = driver.script._add_preload_script("() => { window.test1 = 'loaded'; }") + id2 = driver.script._add_preload_script("() => { window.test2 = 'loaded'; }") + + try: + pages.load("blank.html") + + result1 = driver.script._evaluate( + "window.test1", + {"context": driver.current_window_handle}, + await_promise=False + ) + result2 = driver.script._evaluate( + "window.test2", + {"context": driver.current_window_handle}, + await_promise=False + ) + + assert result1.result["value"] == "loaded" + assert result2.result["value"] == "loaded" + finally: + driver.script._remove_preload_script(script_id=id1) + driver.script._remove_preload_script(script_id=id2) + + def test_preload_script_with_function(self, driver, pages): + """Test preload script defining functions.""" + script_id = driver.script._add_preload_script( + "() => { window.customFunc = (x) => x * 2; }" + ) + + try: + pages.load("blank.html") + result = driver.script._evaluate( + "window.customFunc(5)", + {"context": driver.current_window_handle}, + await_promise=False + ) + assert result.result["value"] == 10 + finally: + driver.script._remove_preload_script(script_id=script_id) + + def test_preload_script_removal_prevents_execution(self, driver, pages): + """Test that removing preload script prevents its execution.""" + script_id = driver.script._add_preload_script("() => { window.shouldNotExist = true; }") + driver.script._remove_preload_script(script_id=script_id) + + pages.load("blank.html") + result = driver.script._evaluate( + "typeof window.shouldNotExist", + {"context": driver.current_window_handle}, + await_promise=False + ) + assert result.result["value"] == "undefined" + + def test_preload_script_with_dom_manipulation(self, driver, pages): + """Test preload script that manipulates DOM.""" + script_id = driver.script._add_preload_script( + """ + () => { + document.addEventListener('DOMContentLoaded', function() { + var div = document.createElement('div'); + div.id = 'injected-element'; + div.textContent = 'injected'; + document.body.appendChild(div); + }); + } + """ + ) + + try: + pages.load("blank.html") + element = driver.find_element(By.ID, "injected-element") + assert element is not None + assert element.text == "injected" + finally: + driver.script._remove_preload_script(script_id=script_id) + + +class TestBidiScriptContextManagement: + """Test script execution across browsing contexts.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test.""" + pages.load("blank.html") + + def test_script_executes_in_current_context(self, driver): + """Test that scripts execute in the current browsing context.""" + # Set variable in current context + driver.execute_script("window.contextVar = 'main';") + + # Verify it's accessible + result = driver.execute_script("return window.contextVar;") + assert result == "main" + + def test_multiple_navigations_maintain_context(self, driver, pages): + """Test script context changes with navigation.""" + # Load first page + pages.load("blank.html") + driver.execute_script("window.page = 'blank';") + + # Load second page - context should reset + pages.load("formPage.html") + result = driver.execute_script("return window.page;") + assert result is None + + # Set new value + driver.execute_script("window.page = 'form';") + result = driver.execute_script("return window.page;") + assert result == "form" + + def test_script_can_access_dom_elements(self, driver, pages): + """Test that scripts can access and manipulate DOM.""" + pages.load("formPage.html") + + # Find element count + result = driver.execute_script( + """ + return document.querySelectorAll('input[type="text"]').length; + """ + ) + assert result > 0 + + def test_script_context_with_console_handler(self, driver, pages): + """Test script execution with console message handler active.""" + log_entries = [] + driver.script.add_console_message_handler(log_entries.append) + + try: + pages.load("bidi/logEntryAdded.html") + driver.execute_script("console.log('test message');") + + # Give some time for handler to capture + WebDriverWait(driver, 3).until(lambda _: log_entries) + assert len(log_entries) > 0 + finally: + # Clean up handler + if log_entries: + # Handler was registered and used + pass + + def test_script_error_handler_active(self, driver, pages): + """Test script execution with error handler active.""" + errors = [] + driver.script.add_javascript_error_handler(errors.append) + + try: + pages.load("bidi/logEntryAdded.html") + # Click element that triggers JS error + driver.find_element(By.ID, "jsException").click() + + # Give time for error handler to capture + WebDriverWait(driver, 5).until(lambda _: errors) + assert len(errors) > 0 + finally: + # Handler cleanup happens automatically via fixture + pass + + +class TestBidiScriptComplexOperations: + """Test complex script operations and edge cases.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test.""" + pages.load("blank.html") + + def test_execute_script_with_timeout(self, driver): + """Test script execution within time constraints.""" + # Execute script that completes quickly + result = driver.execute_script( + """ + return new Promise((resolve) => { + setTimeout(() => resolve('completed'), 10); + }); + """ + ) + # Note: synchronous execute_script may not wait for promises + # This just tests that the method handles the call + assert result is not None + + def test_execute_script_with_dom_creation(self, driver): + """Test script that creates and manipulates DOM.""" + driver.execute_script( + """ + const div = document.createElement('div'); + div.id = 'created-element'; + div.textContent = 'Created by script'; + document.body.appendChild(div); + """ + ) + + # Verify element was created + result = driver.execute_script( + """ + const elem = document.getElementById('created-element'); + return elem ? elem.textContent : null; + """ + ) + assert result == "Created by script" + + def test_execute_script_with_nested_objects(self, driver): + """Test script that returns deeply nested objects.""" + result = driver.execute_script( + """ + return { + level1: { + level2: { + level3: { + value: 'deep' + } + } + } + }; + """ + ) + + assert result["level1"]["level2"]["level3"]["value"] == "deep" + + def test_execute_script_with_exception_handling(self, driver): + """Test script that handles exceptions internally.""" + result = driver.execute_script( + """ + try { + throw new Error('test error'); + } catch (e) { + return 'error caught: ' + e.message; + } + """ + ) + assert "error caught" in result + + +class TestBidiScriptErrorHandling: + """Test script error and logging scenarios.""" + + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test.""" + pages.load("blank.html") + + def test_script_error_handler_captures_errors(self, driver, pages): + """Test that error handler can capture script errors.""" + errors = [] + + def error_handler(entry): + errors.append(entry) + + driver.script.add_javascript_error_handler(error_handler) + + try: + pages.load("bidi/logEntryAdded.html") + driver.find_element(By.ID, "jsException").click() + + WebDriverWait(driver, 5).until(lambda _: errors) + assert len(errors) > 0 + finally: + # Handler removal happens automatically + pass + + def test_multiple_error_handlers(self, driver, pages): + """Test multiple error handlers can be registered.""" + errors1 = [] + errors2 = [] + + driver.script.add_javascript_error_handler(errors1.append) + driver.script.add_javascript_error_handler(errors2.append) + + try: + pages.load("bidi/logEntryAdded.html") + driver.find_element(By.ID, "jsException").click() + + # Both handlers should receive events when error occurs + WebDriverWait(driver, 5).until(lambda _: len(errors1) > 0) + assert len(errors1) > 0 + assert len(errors2) > 0 + finally: + # Handler cleanup happens automatically + pass + + def test_console_message_with_logging(self, driver, pages): + """Test console message handler with actual logging.""" + log_entries = [] + driver.script.add_console_message_handler(log_entries.append) + + try: + pages.load("bidi/logEntryAdded.html") + driver.find_element(By.ID, "consoleLog").click() + + WebDriverWait(driver, 5).until(lambda _: log_entries) + assert len(log_entries) > 0 + finally: + # Handler cleanup + pass + + def test_execute_script_syntax_error(self, driver): + """Test executing script with syntax errors.""" + # This should raise an exception + with pytest.raises(Exception): + driver.execute_script("{{invalid syntax}}") + From 06747cbfe93e3bf1e8678b2d1c164395bb2e2cbb Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 14:31:26 +0000 Subject: [PATCH 03/10] Correct tests --- .../webdriver/common/bidi_errors_tests.py | 214 +++----- .../common/bidi_integration_tests.py | 484 +++++------------- .../webdriver/common/bidi_log_tests.py | 298 +++-------- 3 files changed, 276 insertions(+), 720 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_errors_tests.py b/py/test/selenium/webdriver/common/bidi_errors_tests.py index 25f11fdc62492..2f30e3fdc344a 100644 --- a/py/test/selenium/webdriver/common/bidi_errors_tests.py +++ b/py/test/selenium/webdriver/common/bidi_errors_tests.py @@ -17,7 +17,6 @@ import pytest -from selenium.common.exceptions import InvalidArgumentException from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.by import By @@ -33,174 +32,123 @@ def test_invalid_browsing_context_id(driver): driver.browsing_context.close("invalid-context-id") -def test_invalid_script_evaluate(driver): - """Test that invalid script evaluation raises an error.""" - with pytest.raises(WebDriverException): - driver.script.evaluate("invalid javascript }{", await_promise=False) - - def test_invalid_navigation_url(driver): - """Test that invalid URL navigation raises an error.""" - with pytest.raises((WebDriverException, ValueError)): - driver.browsing_context.navigate(None) - - -def test_invalid_add_preload_script_no_function_declaration(driver): - """Test that adding preload script without proper format raises an error.""" - with pytest.raises((WebDriverException, InvalidArgumentException)): - driver.script.add_preload_script("not a valid script;}") - - -def test_remove_nonexistent_user_context(driver): - """Test that removing non-existent user context raises an error.""" + """Test that navigation with invalid context should fail.""" with pytest.raises(WebDriverException): - driver.browser.remove_user_context("non-existent-context-id") + # Invalid context ID should fail + driver.browsing_context.navigate("invalid-context-id", "about:blank") -def test_invalid_call_function_parameters(driver, pages): - """Test that call_function with invalid parameters raises an error.""" - pages.load("blank.html") - - # Try to call with invalid script - with pytest.raises(WebDriverException): - driver.script.call_function( - "invalid function }{", - [], - await_promise=False - ) - +def test_invalid_geolocation_coordinates(driver): + """Test that invalid geolocation coordinates raise an error.""" + from selenium.webdriver.common.bidi.emulation import GeolocationCoordinates -def test_invalid_screenshot_clipping(driver, pages): - """Test that invalid screenshot clipping region raises an error.""" - pages.load("blank.html") - context_id = driver.current_context_id - - # Try with invalid clipping region (negative dimensions) - with pytest.raises((WebDriverException, ValueError)): - driver.browsing_context.capture_screenshot( - context_id, - clip={"x": 0, "y": 0, "width": -100, "height": 100} - ) + with pytest.raises((WebDriverException, ValueError, TypeError)): + # Invalid latitude (> 90) + coords = GeolocationCoordinates(latitude=999, longitude=180, accuracy=10) + driver.emulation.set_geolocation_override(coordinates=coords) -def test_invalid_set_viewport_dimensions(driver, pages): - """Test that setting invalid viewport dimensions raises an error.""" - pages.load("blank.html") - context_id = driver.current_context_id - - # Try with negative dimensions +def test_invalid_timezone(driver): + """Test that invalid timezone string raises an error.""" with pytest.raises((WebDriverException, ValueError)): - driver.browsing_context.set_viewport( - context_id, - viewport={"width": -1, "height": -1} - ) + driver.emulation.set_timezone_override("Invalid/Timezone") def test_invalid_set_cookie(driver, pages): - """Test that setting invalid cookie raises an error.""" + """Test that setting cookie with None raises an error.""" pages.load("blank.html") - - # Try to set cookie with missing required fields - with pytest.raises((WebDriverException, TypeError, InvalidArgumentException)): + + with pytest.raises((WebDriverException, TypeError, AttributeError)): driver.storage.set_cookie(None) -def test_invalid_network_intercept_phase(driver): - """Test that invalid network intercept phase raises an error.""" - with pytest.raises((WebDriverException, ValueError)): - driver.network.add_intercept( - phases=["invalid_phase"], - url_patterns=[] - ) +def test_remove_nonexistent_context(driver): + """Test that removing non-existent context raises an error.""" + with pytest.raises(WebDriverException): + driver.browser.remove_user_context("non-existent-context-id") -def test_invalid_emulation_timezone(driver): - """Test that invalid timezone string raises an error.""" - with pytest.raises((WebDriverException, ValueError)): - driver.emulation.set_timezone_override("Invalid/Timezone") +def test_invalid_perform_actions_missing_context(driver, pages): + """Test that perform_actions without context raises an error.""" + pages.load("blank.html") + with pytest.raises(TypeError): + # Missing required 'context' parameter + driver.input.perform_actions(actions=[]) -def test_invalid_geolocation_coordinates(driver): - """Test that invalid geolocation coordinates raise an error.""" - with pytest.raises((WebDriverException, ValueError)): - driver.emulation.set_geolocation_override( - latitude=999, # Invalid latitude - longitude=180 - ) +def test_error_recovery_after_invalid_navigation(driver): + """Test that driver can recover after failed navigation.""" + # Try an invalid navigation with bad context + with pytest.raises(WebDriverException): + driver.browsing_context.navigate("invalid-context", "about:blank") + + # Driver should still be functional + driver.get("about:blank") + assert driver.find_element(By.TAG_NAME, "body") is not None -def test_invalid_script_realm_type(driver): - """Test that invalid realm type raises an error.""" - with pytest.raises((WebDriverException, ValueError)): - driver.script.get_realms(realm_type="invalid_realm_type") +def test_multiple_error_conditions(driver, pages): + """Test handling multiple error conditions in sequence.""" + pages.load("blank.html") -def test_protocol_error_with_invalid_session(driver): - """Test that operations on invalid session context raise errors.""" - # Try to use a context that doesn't exist + # First error with pytest.raises(WebDriverException): - driver.browsing_context.navigate("https://www.example.com", context_id="nonexistent") + driver.browser.remove_user_context("invalid") + # Driver should still work + assert driver.find_element(By.TAG_NAME, "body") is not None -def test_error_on_locate_nodes_with_invalid_selector(driver, pages): - """Test that invalid locator strategy raises an error.""" - pages.load("blank.html") - context_id = driver.current_context_id - - # Try with invalid selector strategy + # Second error with pytest.raises((WebDriverException, ValueError)): - driver.browsing_context.locate_nodes( - locator={"type": "invalid_strategy", "value": "test"}, - context_id=context_id - ) + driver.emulation.set_timezone_override("Invalid") - -def test_error_on_perform_actions_invalid_action(driver, pages): - """Test that invalid action sequence raises an error.""" - pages.load("blank.html") - - with pytest.raises((WebDriverException, InvalidArgumentException)): - driver.input.perform_actions(actions=None) - - -def test_error_on_multiple_context_operations(driver): - """Test error handling with rapid context operations.""" - # Create a context - context1 = driver.browser.create_user_context() - assert context1 is not None - - # Try to remove it immediately after (should succeed) - driver.browser.remove_user_context(context1) - - # Try to remove again (should fail) - with pytest.raises(WebDriverException): - driver.browser.remove_user_context(context1) + # Driver still functional + driver.get("about:blank") class TestBidiErrorHandling: """Test class for error handling in BiDi operations.""" - + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") - - def test_error_on_subscribe_invalid_event(self, driver): - """Test that subscribing to invalid event type raises error.""" - with pytest.raises((WebDriverException, ValueError)): - driver.session.subscribe(events=["invalid.event.type"]) - - def test_error_on_unsubscribe_without_subscription(self, driver): - """Test that unsubscribing from non-existent subscription raises error.""" + + def test_error_on_invalid_context_operations(self, driver): + """Test error handling with invalid context operations.""" + # Try to close non-existent context with pytest.raises(WebDriverException): - driver.session.unsubscribe(events=["non.subscribed.event"]) - - def test_error_recovery_after_failed_navigation(self, driver): - """Test that driver can recover after failed navigation.""" - # First, try an invalid navigation + driver.browsing_context.close("nonexistent") + + def test_error_recovery_sequence(self, driver): + """Test that driver recovers properly from errors.""" + # First operation fails with pytest.raises(WebDriverException): - driver.browsing_context.navigate(None) - - # Driver should still be functional + driver.browser.remove_user_context("bad-id") + + # Recovery test + element = driver.find_element(By.TAG_NAME, "body") + assert element is not None + + def test_consecutive_errors(self, driver): + """Test handling consecutive errors.""" + errors_caught = 0 + + # First error + try: + driver.browser.remove_user_context("id1") + except WebDriverException: + errors_caught += 1 + + # Second error + try: + driver.browser.remove_user_context("id2") + except WebDriverException: + errors_caught += 1 + + assert errors_caught == 2 + + # Driver should still work driver.get("about:blank") - assert driver.find_element(By.TAG_NAME, "body") is not None diff --git a/py/test/selenium/webdriver/common/bidi_integration_tests.py b/py/test/selenium/webdriver/common/bidi_integration_tests.py index 5301fb6c43853..d6e49a878e2e9 100644 --- a/py/test/selenium/webdriver/common/bidi_integration_tests.py +++ b/py/test/selenium/webdriver/common/bidi_integration_tests.py @@ -24,426 +24,214 @@ class TestBidiNetworkWithCookies: """Test integration of network and storage modules.""" - + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") - - def test_cookies_sent_in_network_request(self, driver, pages): - """Test that cookies are included in network requests.""" + + def test_cookies_interaction(self, driver, pages): + """Test that cookies work with network operations.""" pages.load("blank.html") - + # Set a cookie driver.add_cookie({"name": "test_cookie", "value": "test_value"}) - - network_data = [] - - def on_before_request(request): - network_data.append(request) - - handler_id = driver.network.add_before_request_handler(on_before_request) - - try: - # Make a request - driver.get(pages.url("blank.html")) - - # Verify the network event was captured - WebDriverWait(driver, 5).until(lambda _: network_data) - assert len(network_data) > 0 - finally: - driver.network.remove_before_request_handler(handler_id) - driver.delete_all_cookies() - - def test_cookie_modification_affects_requests(self, driver, pages): - """Test that modifying cookies affects subsequent requests.""" + + # Verify cookie is set + cookies = driver.get_cookies() + assert len(cookies) > 0 + assert any(c.get("name") == "test_cookie" for c in cookies) + + def test_cookie_modification(self, driver, pages): + """Test that modifying cookies works properly.""" pages.load("blank.html") - + # Add first cookie driver.add_cookie({"name": "cookie1", "value": "value1"}) - + cookies_before = driver.get_cookies() - assert len(cookies_before) >= 1 - + initial_count = len(cookies_before) + # Add second cookie driver.add_cookie({"name": "cookie2", "value": "value2"}) - + cookies_after = driver.get_cookies() - assert len(cookies_after) > len(cookies_before) - - # Cleanup - driver.delete_all_cookies() - - def test_network_interception_with_stored_state(self, driver, pages): - """Test network interception with stored cookies.""" - pages.load("blank.html") - - # Set up storage - driver.add_cookie({"name": "auth_token", "value": "secret123"}) - - intercepted = [] - - def intercept(request): - intercepted.append(request) - # Check if request has access to cookies - if hasattr(request, 'headers'): - pass - - handler_id = driver.network.add_before_request_handler(intercept) - - try: - driver.get(pages.url("blank.html")) - WebDriverWait(driver, 5).until(lambda _: intercepted) - finally: - driver.network.remove_before_request_handler(handler_id) - driver.delete_all_cookies() + assert len(cookies_after) > initial_count class TestBidiScriptWithNavigation: - """Test integration of script and navigation modules.""" - + """Test integration of script execution and navigation.""" + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") driver.delete_all_cookies() - - def test_preload_script_executes_on_page_load(self, driver, pages): - """Test that preload scripts execute on page navigation.""" - script_executions = [] - - # Add a preload script - preload_id = driver.script.add_preload_script( - "window.__test_var = 'preload_executed';" - ) - - try: - # Navigate to a new page - pages.load("blank.html") - - # Verify the preload script executed - result = driver.execute_script("return window.__test_var;") - assert result == "preload_executed" - finally: - driver.script.remove_preload_script(preload_id) - - def test_script_evaluation_after_navigation(self, driver, pages): - """Test script evaluation after page navigation.""" + + def test_script_execution_after_navigation(self, driver, pages): + """Test script execution after page navigation.""" # First page pages.load("blank.html") driver.execute_script("window.page1_loaded = true;") - + # Navigate to different page pages.load("blank.html") - + # Previous page variable should not exist result = driver.execute_script("return window.page1_loaded;") assert result is None - + # New variable should work driver.execute_script("window.page2_loaded = true;") result = driver.execute_script("return window.page2_loaded;") assert result is True - - def test_call_function_with_navigation(self, driver, pages): - """Test calling functions across navigations.""" - pages.load("blank.html") - - # Define a global function - driver.execute_script(""" - window.testFunction = function(x) { - return x * 2; - }; - """) - - # Call the function - result = driver.execute_script("return window.testFunction(5);") - assert result == 10 - - # Navigate + + def test_global_variable_lifecycle(self, driver, pages): + """Test global variable lifecycle across operations.""" pages.load("blank.html") - - # Function should no longer exist on new page - result = driver.execute_script("return typeof window.testFunction;") + + # Set a global variable + driver.execute_script("window.test_var = {data: 'value'};") + + # Verify it exists + result = driver.execute_script("return window.test_var.data;") + assert result == "value" + + # Navigate away + driver.get("about:blank") + + # Variable should not exist anymore + result = driver.execute_script("return typeof window.test_var;") assert result == "undefined" class TestBidiEmulationWithNavigation: - """Test integration of emulation and navigation modules.""" - + """Test integration of emulation and navigation.""" + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") - - def test_user_agent_persists_across_navigation(self, driver, pages): - """Test that user agent emulation persists across page navigations.""" - try: - pages.load("blank.html") - - # Set user agent - driver.emulation.set_user_agent_override("Custom User Agent") - - # Verify user agent - ua = driver.execute_script("return navigator.userAgent;") - assert "Custom User Agent" in ua - - # Navigate to different page - pages.load("blank.html") - - # Verify user agent still set - ua_after = driver.execute_script("return navigator.userAgent;") - assert "Custom User Agent" in ua_after - finally: - driver.emulation.set_user_agent_override() # Reset - - def test_timezone_applies_to_script(self, driver, pages): - """Test that timezone emulation affects JavaScript date operations.""" - try: - pages.load("blank.html") - - # Set timezone - driver.emulation.set_timezone_override("America/New_York") - - # Get timezone-sensitive value - offset = driver.execute_script(""" - return new Date().getTimezoneOffset(); - """) - - assert offset is not None - finally: - driver.emulation.set_timezone_override() # Reset + + def test_basic_navigation(self, driver, pages): + """Test basic navigation.""" + pages.load("blank.html") + assert driver.find_element(By.TAG_NAME, "body") is not None class TestBidiContextManagement: """Test integration of context creation and management.""" - - def test_create_and_navigate_context(self, driver): - """Test creating a new context and navigating within it.""" + + def test_create_and_close_context(self, driver): + """Test creating and closing a user context.""" new_context = driver.browser.create_user_context() - + try: assert new_context is not None - - # Navigate in the new context - driver.get("about:blank", context_id=new_context) finally: driver.browser.remove_user_context(new_context) - - def test_multiple_contexts_independent(self, driver, pages): - """Test that multiple contexts maintain independent state.""" + + def test_multiple_contexts_creation(self, driver): + """Test creating multiple contexts.""" context1 = driver.browser.create_user_context() context2 = driver.browser.create_user_context() - + try: - # Load page in context 1 - driver.get(pages.url("blank.html"), context_id=context1) - - # Load page in context 2 - driver.get(pages.url("blank.html"), context_id=context2) - - # Set cookie in context 1 - driver.add_cookie( - {"name": "context1_cookie", "value": "value1"}, - context_id=context1 - ) - - # Context 2 should not have this cookie - cookies_context2 = driver.get_cookies(context_id=context2) - cookie_names = [c.get("name") for c in cookies_context2] - assert "context1_cookie" not in cookie_names + assert context1 is not None + assert context2 is not None + assert context1 != context2 finally: driver.browser.remove_user_context(context1) driver.browser.remove_user_context(context2) - - def test_context_window_operations(self, driver): - """Test window operations within contexts.""" - context = driver.browser.create_user_context() - - try: - # Create new window in context - new_window = driver.browsing_context.create( - type=WindowTypes.TAB, - context_id=context - ) - - assert new_window is not None - - # Close the window - driver.browsing_context.close(new_window) - finally: - driver.browser.remove_user_context(context) -class TestBidiEventHandlerManagement: - """Test integration of multiple event handlers.""" - +class TestBidiEventHandlers: + """Test integration of event handlers.""" + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") - - def test_multiple_handlers_same_event(self, driver): - """Test multiple handlers for the same event type.""" - events1 = [] - events2 = [] - - handler1 = driver.browsing_context.add_context_created_handler(events1.append) - handler2 = driver.browsing_context.add_context_created_handler(events2.append) - - try: - # Create a new context - new_context = driver.browser.create_user_context() - - # Both handlers should receive the event - WebDriverWait(driver, 5).until(lambda _: len(events1) > 0 and len(events2) > 0) - assert len(events1) > 0 - assert len(events2) > 0 - - # Cleanup - driver.browser.remove_user_context(new_context) - finally: - driver.browsing_context.remove_context_created_handler(handler1) - driver.browsing_context.remove_context_created_handler(handler2) - - def test_handler_removal_prevents_events(self, driver): - """Test that removing handler prevents event delivery.""" - events1 = [] - events2 = [] - - handler1 = driver.browsing_context.add_context_created_handler(events1.append) - handler2 = driver.browsing_context.add_context_created_handler(events2.append) - + + def test_multiple_console_handlers(self, driver): + """Test multiple console message handlers.""" + messages1 = [] + messages2 = [] + + handler1 = driver.script.add_console_message_handler(messages1.append) + handler2 = driver.script.add_console_message_handler(messages2.append) + try: - # Create first context - context1 = driver.browser.create_user_context() - WebDriverWait(driver, 5).until(lambda _: len(events1) > 0 and len(events2) > 0) - - initial_events1 = len(events1) - initial_events2 = len(events2) - - # Remove first handler - driver.browsing_context.remove_context_created_handler(handler1) - - # Create second context - context2 = driver.browser.create_user_context() - WebDriverWait(driver, 5).until(lambda _: len(events2) > initial_events2) - - # First handler should not receive new events - assert len(events1) == initial_events1 - assert len(events2) > initial_events2 - - # Cleanup - driver.browser.remove_user_context(context1) - driver.browser.remove_user_context(context2) + driver.execute_script("console.log('test message');") + WebDriverWait(driver, 5).until( + lambda _: len(messages1) > 0 and len(messages2) > 0 + ) + + assert len(messages1) > 0 + assert len(messages2) > 0 finally: - driver.browsing_context.remove_context_created_handler(handler2) + driver.script.remove_console_message_handler(handler1) + driver.script.remove_console_message_handler(handler2) -class TestBidiScreenshotWithNavigation: - """Test integration of screenshot and navigation features.""" - - @pytest.fixture(autouse=True) - def setup(self, driver, pages): - """Setup for each test in this class.""" - pages.load("blank.html") - - def test_screenshot_after_navigation(self, driver, pages): - """Test taking screenshot after page navigation.""" +class TestBidiStorageOperations: + """Test storage operations.""" + + def test_cookie_operations(self, driver, pages): + """Test basic cookie operations.""" pages.load("blank.html") - - # Take screenshot - screenshot1 = driver.browsing_context.capture_screenshot( - driver.current_context_id - ) - assert screenshot1 is not None - - # Navigate to different page - driver.find_element(By.TAG_NAME, "body") - - # Take another screenshot - screenshot2 = driver.browsing_context.capture_screenshot( - driver.current_context_id - ) - assert screenshot2 is not None - - # Screenshots should exist (may be same or different) - assert screenshot1 is not None - assert screenshot2 is not None - - def test_screenshot_with_viewport_change(self, driver, pages): - """Test screenshot behavior with viewport changes.""" + + # Set cookie + driver.add_cookie({"name": "test", "value": "data"}) + + # Get cookies + cookies = driver.get_cookies() + assert any(c.get("name") == "test" for c in cookies) + + # Delete cookie + driver.delete_cookie("test") + + # Verify deletion + cookies_after = driver.get_cookies() + assert not any(c.get("name") == "test" for c in cookies_after) + + def test_cookie_attributes(self, driver, pages): + """Test cookie with various attributes.""" pages.load("blank.html") - context_id = driver.current_context_id - - # Take initial screenshot - screenshot1 = driver.browsing_context.capture_screenshot(context_id) - assert screenshot1 is not None - - # Change viewport - driver.browsing_context.set_viewport( - context_id, - viewport={"width": 1024, "height": 768} + + driver.add_cookie( + {"name": "attr_cookie", "value": "test_value", "path": "/", "secure": False} ) - - # Take screenshot with new viewport - screenshot2 = driver.browsing_context.capture_screenshot(context_id) - assert screenshot2 is not None + cookies = driver.get_cookies() + cookie = next((c for c in cookies if c.get("name") == "attr_cookie"), None) + + assert cookie is not None + assert cookie.get("value") == "test_value" + + +class TestBidiBrowsingContexts: + """Test browsing context operations.""" + + def test_create_new_window(self, driver): + """Test creating a new window context.""" + # Create new tab + new_context = driver.browsing_context.create(type=WindowTypes.TAB) -class TestBidiStorageWithContexts: - """Test storage operations across multiple contexts.""" - - def test_cookies_independent_across_contexts(self, driver, pages): - """Test that cookies are independent across user contexts.""" - context1 = driver.browser.create_user_context() - context2 = driver.browser.create_user_context() - - try: - pages.load("blank.html") - - # Add cookie to context 1 - driver.add_cookie( - {"name": "ctx1_cookie", "value": "value1"}, - context_id=context1 - ) - - # Get cookies from context 2 - cookies = driver.get_cookies(context_id=context2) - cookie_names = [c.get("name") for c in cookies] - - # Context 2 should not have context 1's cookie - assert "ctx1_cookie" not in cookie_names - finally: - driver.browser.remove_user_context(context1) - driver.browser.remove_user_context(context2) - - def test_delete_cookies_affects_only_target_context(self, driver, pages): - """Test that deleting cookies only affects the target context.""" - context1 = driver.browser.create_user_context() - context2 = driver.browser.create_user_context() - try: - # Add same cookie name to both contexts - driver.add_cookie( - {"name": "shared_cookie", "value": "ctx1"}, - context_id=context1 - ) - driver.add_cookie( - {"name": "shared_cookie", "value": "ctx2"}, - context_id=context2 - ) - - # Delete cookie from context 1 - driver.delete_cookie("shared_cookie", context_id=context1) - - # Context 1 should not have the cookie - cookies1 = driver.get_cookies(context_id=context1) - assert not any(c.get("name") == "shared_cookie" for c in cookies1) - - # Context 2 should still have it - cookies2 = driver.get_cookies(context_id=context2) - assert any(c.get("name") == "shared_cookie" for c in cookies2) + assert new_context is not None finally: - driver.browser.remove_user_context(context1) - driver.browser.remove_user_context(context2) + driver.browsing_context.close(new_context) + + def test_navigation_in_context(self, driver, pages): + """Test navigation in a specific context.""" + pages.load("blank.html") + + # Navigate using the BiDi API with the current context + driver.browsing_context.navigate( + context=driver.current_window_handle, url=pages.url("blank.html") + ) + + # Verify page loaded + element = driver.find_element(By.TAG_NAME, "body") + assert element is not None diff --git a/py/test/selenium/webdriver/common/bidi_log_tests.py b/py/test/selenium/webdriver/common/bidi_log_tests.py index 2f90259f364f0..2b660133b9bf7 100644 --- a/py/test/selenium/webdriver/common/bidi_log_tests.py +++ b/py/test/selenium/webdriver/common/bidi_log_tests.py @@ -19,7 +19,6 @@ import pytest -from selenium.webdriver.common.bidi.log import LogLevel from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait @@ -31,322 +30,143 @@ def test_log_module_initialized(driver): class TestBidiLogging: """Test class for BiDi logging functionality.""" - + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" - pages.load("bidi/logEntryAdded.html") - + pages.load("blank.html") + def test_console_log_message(self, driver): """Test capturing console.log messages.""" log_entries = [] - + def callback(log_entry): log_entries.append(log_entry) - + handler_id = driver.script.add_console_message_handler(callback) - - try: - driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - assert log_entries[0].text == "Hello, world!" - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_console_warn_message(self, driver): - """Test capturing console.warn messages.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.find_element(By.ID, "consoleWarn").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - assert "warning" in log_entries[0].text.lower() or log_entries[0].text == "Warning message" - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_console_error_message(self, driver): - """Test capturing console.error messages.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.find_element(By.ID, "consoleError").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - assert "error" in log_entries[0].text.lower() or log_entries[0].text == "Error message" - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_console_debug_message(self, driver): - """Test capturing console.debug messages.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.find_element(By.ID, "consoleDebug").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_console_info_message(self, driver): - """Test capturing console.info messages.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - + try: - driver.find_element(By.ID, "consoleInfo").click() + driver.execute_script("console.log('test message');") WebDriverWait(driver, 5).until(lambda _: log_entries) - + assert len(log_entries) > 0 finally: driver.script.remove_console_message_handler(handler_id) - - def test_multiple_console_messages(self, driver): + + def test_console_multiple_messages(self, driver): """Test capturing multiple console messages.""" log_entries = [] - + handler_id = driver.script.add_console_message_handler(log_entries.append) - + try: - driver.find_element(By.ID, "consoleLog").click() - driver.find_element(By.ID, "consoleWarn").click() - driver.find_element(By.ID, "consoleError").click() - + driver.execute_script( + """ + console.log('message 1'); + console.log('message 2'); + console.log('message 3'); + """ + ) + WebDriverWait(driver, 5).until(lambda _: len(log_entries) >= 3) - + assert len(log_entries) >= 3 finally: driver.script.remove_console_message_handler(handler_id) - + def test_add_and_remove_handler(self, driver): """Test adding and removing log handlers.""" log_entries1 = [] log_entries2 = [] - + handler_id1 = driver.script.add_console_message_handler(log_entries1.append) handler_id2 = driver.script.add_console_message_handler(log_entries2.append) - + try: - driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: len(log_entries1) > 0 and len(log_entries2) > 0) - + driver.execute_script("console.log('first message');") + WebDriverWait(driver, 5).until( + lambda _: len(log_entries1) > 0 and len(log_entries2) > 0 + ) + assert len(log_entries1) > 0 assert len(log_entries2) > 0 - + # Remove first handler driver.script.remove_console_message_handler(handler_id1) - + initial_count1 = len(log_entries1) initial_count2 = len(log_entries2) - + # Trigger another message - driver.find_element(By.ID, "consoleWarn").click() + driver.execute_script("console.log('second message');") WebDriverWait(driver, 5).until(lambda _: len(log_entries2) > initial_count2) - + # First handler should not receive new messages assert len(log_entries1) == initial_count1 assert len(log_entries2) > initial_count2 finally: driver.script.remove_console_message_handler(handler_id2) - - def test_log_entry_text_content(self, driver): - """Test log entry contains expected text content.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert hasattr(log_entries[0], 'text') - assert len(log_entries[0].text) > 0 - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_log_entry_level(self, driver): - """Test log entry has level information.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert hasattr(log_entries[0], 'level') or hasattr(log_entries[0], 'method') - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_log_entry_timestamp(self, driver): - """Test log entry contains timestamp information.""" - log_entries = [] - before_time = time.time() * 1000 - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) - after_time = time.time() * 1000 - - assert hasattr(log_entries[0], 'timestamp') - timestamp = log_entries[0].timestamp - assert before_time <= timestamp <= after_time - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_exception_messages_logged(self, driver): - """Test that exception messages are logged.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - # Execute script that throws an error - driver.execute_script(""" - try { - throw new Error("Test error"); - } catch (e) { - console.error(e.message); - } - """) - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_log_handler_receives_all_levels(self, driver): + + def test_handler_receives_all_levels(self, driver): """Test that a single handler can receive all log levels.""" log_levels = [] - + def callback(entry): log_levels.append(entry) - + handler_id = driver.script.add_console_message_handler(callback) - + try: - driver.execute_script(""" + driver.execute_script( + """ console.log('log'); console.warn('warn'); console.error('error'); console.debug('debug'); console.info('info'); - """) - + """ + ) + WebDriverWait(driver, 5).until(lambda _: len(log_levels) >= 5) - + assert len(log_levels) >= 5 finally: driver.script.remove_console_message_handler(handler_id) - + def test_log_with_multiple_arguments(self, driver): """Test console.log with multiple arguments.""" log_entries = [] - + handler_id = driver.script.add_console_message_handler(log_entries.append) - + try: driver.execute_script("console.log('arg1', 'arg2', 'arg3');") WebDriverWait(driver, 5).until(lambda _: log_entries) - + assert len(log_entries) > 0 - # Entry should contain all arguments - assert hasattr(log_entries[0], 'args') or hasattr(log_entries[0], 'text') finally: driver.script.remove_console_message_handler(handler_id) - - def test_log_handler_context_id(self, driver): - """Test log entry contains context information.""" + + def test_log_entry_attributes(self, driver): + """Test log entry has expected attributes.""" log_entries = [] - + handler_id = driver.script.add_console_message_handler(log_entries.append) - + try: - driver.find_element(By.ID, "consoleLog").click() + driver.execute_script("console.log('test');") WebDriverWait(driver, 5).until(lambda _: log_entries) - - # Log entry should have context information - assert hasattr(log_entries[0], 'context') or hasattr(log_entries[0], 'source_url') + + assert len(log_entries) > 0 + assert hasattr(log_entries[0], "text") or hasattr(log_entries[0], "args") finally: driver.script.remove_console_message_handler(handler_id) class TestBidiJavaScriptErrors: """Test class for JavaScript error logging.""" - + @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") - - def test_syntax_error_logged(self, driver): - """Test that syntax errors are logged.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - # Execute invalid JavaScript (should trigger error logging) - try: - driver.execute_script("{{invalid}}") - except Exception: - pass - - # Give it a moment to log - time.sleep(0.5) - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_runtime_error_logged(self, driver): - """Test that runtime errors are logged.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.execute_script(""" - try { - undefined_function(); - } catch (e) { - console.error(e.message); - } - """) - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - finally: - driver.script.remove_console_message_handler(handler_id) - - def test_caught_exception_logged(self, driver): - """Test that caught exceptions can be logged explicitly.""" - log_entries = [] - - handler_id = driver.script.add_console_message_handler(log_entries.append) - - try: - driver.execute_script(""" - try { - throw new Error("Custom error"); - } catch (error) { - console.error("Caught:", error.message); - } - """) - WebDriverWait(driver, 5).until(lambda _: log_entries) - - assert len(log_entries) > 0 - finally: - driver.script.remove_console_message_handler(handler_id) From ed37820ce6ac7c8da83f49a9e1f48e0dd870d2d7 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 14:33:01 +0000 Subject: [PATCH 04/10] fix lint --- py/test/selenium/webdriver/common/bidi_log_tests.py | 2 -- py/test/selenium/webdriver/common/bidi_script_tests.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_log_tests.py b/py/test/selenium/webdriver/common/bidi_log_tests.py index 2b660133b9bf7..d52e6b3dd06a4 100644 --- a/py/test/selenium/webdriver/common/bidi_log_tests.py +++ b/py/test/selenium/webdriver/common/bidi_log_tests.py @@ -15,11 +15,9 @@ # specific language governing permissions and limitations # under the License. -import time import pytest -from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait diff --git a/py/test/selenium/webdriver/common/bidi_script_tests.py b/py/test/selenium/webdriver/common/bidi_script_tests.py index c19d78e1a0579..194a1c9909897 100644 --- a/py/test/selenium/webdriver/common/bidi_script_tests.py +++ b/py/test/selenium/webdriver/common/bidi_script_tests.py @@ -17,11 +17,11 @@ import pytest +from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.bidi.log import LogLevel from selenium.webdriver.common.bidi.script import RealmType, ResultOwnership from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait -from selenium.common.exceptions import WebDriverException def has_shadow_root(node): @@ -864,7 +864,6 @@ def test_execute_script_with_exception(driver, pages): """Test executing script that throws an exception.""" pages.load("blank.html") - from selenium.common.exceptions import WebDriverException with pytest.raises(WebDriverException) as exc_info: driver.script.execute( From 7f9081923d911f28eeccc655e7a00ba6d4e441fe Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 18:22:49 +0000 Subject: [PATCH 05/10] Extend input tests --- .../webdriver/common/bidi_input_tests.py | 468 ++++++++++++++++++ 1 file changed, 468 insertions(+) diff --git a/py/test/selenium/webdriver/common/bidi_input_tests.py b/py/test/selenium/webdriver/common/bidi_input_tests.py index ecbe0bddd4f73..ae8df4b251e1f 100644 --- a/py/test/selenium/webdriver/common/bidi_input_tests.py +++ b/py/test/selenium/webdriver/common/bidi_input_tests.py @@ -27,6 +27,7 @@ KeyDownAction, KeySourceActions, KeyUpAction, + NoneSourceActions, Origin, PauseAction, PointerCommonProperties, @@ -413,3 +414,470 @@ def file_dialog_handler(file_dialog_info): # Wait to ensure no events are captured time.sleep(1) assert len(file_dialog_events) == 0 + + +# Edge Cases and Additional Tests + + +def test_perform_actions_with_none_source(driver, pages): + """Test performing NoneSourceActions (pause only).""" + pages.load("single_text_input.html") + + # Create none actions (pause only - no actual input) + none_actions = NoneSourceActions( + id="none_id", + actions=[ + PauseAction(duration=100), + PauseAction(duration=50), + ], + ) + + # Should execute without error + driver.input.perform_actions(driver.current_window_handle, [none_actions]) + + # Verify input field is still empty + input_element = driver.find_element(By.ID, "textInput") + assert input_element.get_attribute("value") == "" + + +def test_perform_actions_rapid_key_sequence(driver, pages): + """Test rapid key input sequence without pause between keys.""" + pages.load("single_text_input.html") + + input_element = driver.find_element(By.ID, "textInput") + + # Create rapid key sequence + key_actions = KeySourceActions( + id="keyboard", + actions=[ + KeyDownAction(value="a"), + KeyUpAction(value="a"), + KeyDownAction(value="b"), + KeyUpAction(value="b"), + KeyDownAction(value="c"), + KeyUpAction(value="c"), + KeyDownAction(value="d"), + KeyUpAction(value="d"), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [key_actions]) + + WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "abcd") + assert input_element.get_attribute("value") == "abcd" + + +def test_perform_actions_multiple_pointer_buttons(driver, pages): + """Test pointer actions with different button values.""" + pages.load("javascriptPage.html") + + button = driver.find_element(By.ID, "clickField") + location = button.location + size = button.size + x = location["x"] + size["width"] // 2 + y = location["y"] + size["height"] // 2 + + # Test with button 0 (left click) + pointer_actions_left = PointerSourceActions( + id="mouse_left", + parameters=PointerParameters(pointer_type=PointerType.MOUSE), + actions=[ + PointerMoveAction(x=x, y=y), + PointerDownAction(button=0), + PointerUpAction(button=0), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [pointer_actions_left]) + + WebDriverWait(driver, 5).until(lambda d: button.get_attribute("value") == "Clicked") + assert button.get_attribute("value") == "Clicked" + + +def test_perform_actions_pointer_touch_type(driver, pages): + """Test pointer actions with touch pointer type.""" + pages.load("javascriptPage.html") + + button = driver.find_element(By.ID, "clickField") + location = button.location + size = button.size + x = location["x"] + size["width"] // 2 + y = location["y"] + size["height"] // 2 + + # Create touch actions + touch_actions = PointerSourceActions( + id="touch", + parameters=PointerParameters(pointer_type=PointerType.TOUCH), + actions=[ + PointerMoveAction(x=x, y=y), + PointerDownAction(button=0), + PointerUpAction(button=0), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [touch_actions]) + + # Touch should work similar to mouse click + WebDriverWait(driver, 5).until(lambda d: button.get_attribute("value") == "Clicked") + assert button.get_attribute("value") == "Clicked" + + +def test_perform_actions_pointer_pen_type(driver, pages): + """Test pointer actions with pen pointer type.""" + pages.load("javascriptPage.html") + + button = driver.find_element(By.ID, "clickField") + location = button.location + size = button.size + x = location["x"] + size["width"] // 2 + y = location["y"] + size["height"] // 2 + + # Create pen actions + pen_actions = PointerSourceActions( + id="pen", + parameters=PointerParameters(pointer_type=PointerType.PEN), + actions=[ + PointerMoveAction(x=x, y=y), + PointerDownAction(button=0), + PointerUpAction(button=0), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [pen_actions]) + + WebDriverWait(driver, 5).until(lambda d: button.get_attribute("value") == "Clicked") + assert button.get_attribute("value") == "Clicked" + + +def test_perform_actions_pointer_move_with_duration(driver, pages): + """Test pointer move action with duration parameter.""" + pages.load("javascriptPage.html") + + button = driver.find_element(By.ID, "clickField") + location = button.location + size = button.size + x = location["x"] + size["width"] // 2 + y = location["y"] + size["height"] // 2 + + # Start point (off the button) + start_x = x - 100 + start_y = y - 100 + + # Create pointer actions with duration on move + pointer_actions = PointerSourceActions( + id="mouse", + parameters=PointerParameters(pointer_type=PointerType.MOUSE), + actions=[ + PointerMoveAction(x=start_x, y=start_y), + PointerMoveAction(x=x, y=y, duration=500), # Slow move + PointerDownAction(button=0), + PointerUpAction(button=0), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [pointer_actions]) + + WebDriverWait(driver, 5).until(lambda d: button.get_attribute("value") == "Clicked") + assert button.get_attribute("value") == "Clicked" + + +def test_wheel_scroll_negative_delta(driver, pages): + """Test wheel scroll with negative delta values (up/left).""" + pages.load("scroll3.html") + + # First scroll down + wheel_actions_down = WheelSourceActions( + id="wheel_down", + actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT)], + ) + + driver.input.perform_actions(driver.current_window_handle, [wheel_actions_down]) + + scroll_y_down = driver.execute_script("return window.pageYOffset;") + assert scroll_y_down > 0 + + # Then scroll back up (negative delta) + wheel_actions_up = WheelSourceActions( + id="wheel_up", + actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=-50, origin=Origin.VIEWPORT)], + ) + + driver.input.perform_actions(driver.current_window_handle, [wheel_actions_up]) + + scroll_y_up = driver.execute_script("return window.pageYOffset;") + assert scroll_y_up < scroll_y_down + + +def test_wheel_scroll_with_duration(driver, pages): + """Test wheel scroll action with duration parameter.""" + pages.load("scroll3.html") + + wheel_actions = WheelSourceActions( + id="wheel", + actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, duration=500, origin=Origin.VIEWPORT)], + ) + + driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) + + scroll_y = driver.execute_script("return window.pageYOffset;") + assert scroll_y == 100 + + +def test_wheel_scroll_horizontal(driver, pages): + """Test wheel scroll with horizontal movement.""" + pages.load("scroll3.html") + + # Scroll horizontally + wheel_actions = WheelSourceActions( + id="wheel", + actions=[WheelScrollAction(x=100, y=100, delta_x=50, delta_y=0, origin=Origin.VIEWPORT)], + ) + + driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) + + # Check horizontal scroll occurred + scroll_x = driver.execute_script("return window.pageXOffset;") + assert scroll_x >= 0 + + +def test_key_input_special_characters(driver, pages): + """Test keyboard input with special characters.""" + pages.load("single_text_input.html") + + input_element = driver.find_element(By.ID, "textInput") + + # Create keyboard actions for special characters + key_actions = KeySourceActions( + id="keyboard", + actions=[ + KeyDownAction(value="!"), + KeyUpAction(value="!"), + KeyDownAction(value="@"), + KeyUpAction(value="@"), + KeyDownAction(value="#"), + KeyUpAction(value="#"), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [key_actions]) + + WebDriverWait(driver, 5).until(lambda d: "!" in input_element.get_attribute("value")) + + +def test_set_files_empty_file_list(driver, pages): + """Test setting an empty file list on a file input element.""" + pages.load("formPage.html") + + upload_element = driver.find_element(By.ID, "upload") + + # Get element reference for BiDi + element_id = upload_element.id + element_ref = {"sharedId": element_id} + + # Set empty file list + driver.input.set_files(driver.current_window_handle, element_ref, []) + + # Value should be empty + value = upload_element.get_attribute("value") + assert value == "" + + +def test_set_files_with_absolute_path(driver): + """Test setting a file using absolute file path.""" + driver.get("data:text/html,") + + upload_element = driver.find_element(By.ID, "upload") + + # Create a temporary file + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as temp_file: + temp_file.write("test file content") + temp_file_path = temp_file.name + + try: + # Get element reference + element_id = upload_element.id + element_ref = {"sharedId": element_id} + + # Set file using absolute path + driver.input.set_files(driver.current_window_handle, element_ref, [temp_file_path]) + + value = upload_element.get_attribute("value") + assert os.path.basename(temp_file_path) in value + + finally: + if os.path.exists(temp_file_path): + os.unlink(temp_file_path) + + +def test_release_actions_clears_pointer_state(driver, pages): + """Test that release_actions properly clears pointer state.""" + pages.load("javascriptPage.html") + + button = driver.find_element(By.ID, "clickField") + location = button.location + size = button.size + x = location["x"] + size["width"] // 2 + y = location["y"] + size["height"] // 2 + + # Press pointer button but don't release + pointer_actions = PointerSourceActions( + id="mouse", + parameters=PointerParameters(pointer_type=PointerType.MOUSE), + actions=[ + PointerMoveAction(x=x, y=y), + PointerDownAction(button=0), + # Not releasing button + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [pointer_actions]) + + # Release all actions + driver.input.release_actions(driver.current_window_handle) + + # Now move and try clicking again - should work normally + pointer_actions2 = PointerSourceActions( + id="mouse", + parameters=PointerParameters(pointer_type=PointerType.MOUSE), + actions=[ + PointerMoveAction(x=x, y=y), + PointerDownAction(button=0), + PointerUpAction(button=0), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [pointer_actions2]) + + WebDriverWait(driver, 5).until(lambda d: button.get_attribute("value") == "Clicked") + assert button.get_attribute("value") == "Clicked" + + +def test_multiple_file_dialog_handlers(driver): + """Test registering multiple file dialog handlers.""" + handlers_triggered = [] + + def handler_1(file_dialog_info): + handlers_triggered.append(1) + + def handler_2(file_dialog_info): + handlers_triggered.append(2) + + # Register two handlers + handler_id_1 = driver.input.add_file_dialog_handler(handler_1) + handler_id_2 = driver.input.add_file_dialog_handler(handler_2) + + assert handler_id_1 is not None + assert handler_id_2 is not None + assert handler_id_1 != handler_id_2 + + # Clean up + driver.input.remove_file_dialog_handler(handler_id_1) + driver.input.remove_file_dialog_handler(handler_id_2) + + +def test_pointer_common_properties_pressure_values(driver, pages): + """Test pointer actions with various pressure values.""" + pages.load("javascriptPage.html") + + button = driver.find_element(By.ID, "clickField") + location = button.location + size = button.size + x = location["x"] + size["width"] // 2 + y = location["y"] + size["height"] // 2 + + # Test with different pressure values + properties = PointerCommonProperties( + width=2, + height=2, + pressure=0.75, # High pressure + tangential_pressure=0.25, + twist=90, + altitude_angle=0.7, + azimuth_angle=1.5, + ) + + pointer_actions = PointerSourceActions( + id="mouse", + parameters=PointerParameters(pointer_type=PointerType.MOUSE), + actions=[ + PointerMoveAction(x=x, y=y, properties=properties), + PointerDownAction(button=0, properties=properties), + PointerUpAction(button=0), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [pointer_actions]) + + WebDriverWait(driver, 5).until(lambda d: button.get_attribute("value") == "Clicked") + assert button.get_attribute("value") == "Clicked" + + +def test_combined_keyboard_and_wheel_actions(driver, pages): + """Test combining keyboard and wheel scroll actions.""" + pages.load("scroll3.html") + + # Combine keyboard and wheel actions + key_actions = KeySourceActions( + id="keyboard", + actions=[PauseAction(duration=0)], # Sync with wheel + ) + + wheel_actions = WheelSourceActions( + id="wheel", + actions=[ + PauseAction(duration=0), # Sync with keyboard + WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [key_actions, wheel_actions]) + + scroll_y = driver.execute_script("return window.pageYOffset;") + assert scroll_y == 100 + + +def test_key_input_with_value_attribute(driver, pages): + """Test KeyDownAction and KeyUpAction use value attribute correctly.""" + pages.load("single_text_input.html") + + input_element = driver.find_element(By.ID, "textInput") + + # Use explicit value attribute in actions + key_actions = KeySourceActions( + id="keyboard", + actions=[ + KeyDownAction(value="x"), + KeyUpAction(value="x"), + KeyDownAction(value="y"), + KeyUpAction(value="y"), + KeyDownAction(value="z"), + KeyUpAction(value="z"), + ], + ) + + driver.input.perform_actions(driver.current_window_handle, [key_actions]) + + WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "xyz") + assert input_element.get_attribute("value") == "xyz" + + +def test_wheel_scroll_with_element_origin(driver, pages): + """Test wheel scroll with element origin instead of viewport.""" + pages.load("scroll3.html") + + # Get a reference to a scrollable element (body) + body_element = driver.find_element(By.TAG_NAME, "body") + element_id = body_element.id + element_ref = {"sharedId": element_id} + element_origin = ElementOrigin(element_ref) + + # Scroll with element origin + wheel_actions = WheelSourceActions( + id="wheel", + actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=element_origin)], + ) + + driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) + + scroll_y = driver.execute_script("return window.pageYOffset;") + assert scroll_y >= 0 From 32d627734b8843c27c6e3d4cf5481ab7c30347a1 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 18:29:47 +0000 Subject: [PATCH 06/10] Add more storage tests --- .../webdriver/common/bidi_storage_tests.py | 366 ++++++++++++++++++ 1 file changed, 366 insertions(+) diff --git a/py/test/selenium/webdriver/common/bidi_storage_tests.py b/py/test/selenium/webdriver/common/bidi_storage_tests.py index 01fb375f7c39a..807e4d62c403c 100644 --- a/py/test/selenium/webdriver/common/bidi_storage_tests.py +++ b/py/test/selenium/webdriver/common/bidi_storage_tests.py @@ -353,3 +353,369 @@ def test_add_cookies_with_different_paths(self, driver, pages, webserver): driver.get(pages.url("formPage.html")) assert_cookie_is_not_present_with_name(driver, "fish") + + def test_delete_cookies_by_name_filter(self, driver, pages, webserver): + """Test deleting cookies with specific name filter.""" + assert_no_cookies_are_present(driver) + + key1 = generate_unique_key() + key2 = generate_unique_key() + key3 = generate_unique_key() + + driver.add_cookie({"name": key1, "value": "value1"}) + driver.add_cookie({"name": key2, "value": "value2"}) + driver.add_cookie({"name": key3, "value": "value3"}) + + # Delete only key1 + driver.storage.delete_cookies(filter=CookieFilter(name=key1)) + + # Verify + assert_cookie_is_not_present_with_name(driver, key1) + assert_cookie_is_present_with_name(driver, key2) + assert_cookie_is_present_with_name(driver, key3) + + def test_delete_cookies_multiple_filters(self, driver, pages, webserver): + """Test deleting cookies with multiple filter criteria.""" + assert_no_cookies_are_present(driver) + + key1 = "http_only_delete_test" + key2 = "normal_delete_test" + value = BytesValue(BytesValue.TYPE_STRING, "test_value") + + cookie1 = PartialCookie(key1, value, webserver.host, http_only=True) + cookie2 = PartialCookie(key2, value, webserver.host, http_only=False) + + driver.storage.set_cookie(cookie=cookie1) + driver.storage.set_cookie(cookie=cookie2) + + # Delete only http_only cookies + driver.storage.delete_cookies(filter=CookieFilter(name=key1, http_only=True)) + + # Verify + assert_cookie_is_not_present_with_name(driver, key1) + assert_cookie_is_present_with_name(driver, key2) + + def test_delete_cookies_empty_filter(self, driver, pages, webserver): + """Test deleting with empty filter deletes all cookies.""" + assert_no_cookies_are_present(driver) + + # Add multiple cookies + for i in range(3): + driver.add_cookie({"name": f"cookie_{i}", "value": f"value_{i}"}) + + assert_some_cookies_are_present(driver) + + # Delete with empty filter + driver.storage.delete_cookies(filter=CookieFilter()) + + # Verify all deleted + assert_no_cookies_are_present(driver) + + def test_set_cookie_with_http_only_attribute(self, driver, pages, webserver): + """Test setting a cookie with http_only attribute.""" + assert_no_cookies_are_present(driver) + + key = "http_only_cookie" + value = BytesValue(BytesValue.TYPE_STRING, "protected") + + cookie = PartialCookie(key, value, webserver.host, http_only=True) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key, http_only=True) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].http_only is True + + def test_set_cookie_with_secure_attribute(self, driver, pages, webserver): + """Test setting a cookie with secure attribute.""" + assert_no_cookies_are_present(driver) + + key = "secure_cookie" + value = BytesValue(BytesValue.TYPE_STRING, "encrypted") + + cookie = PartialCookie(key, value, webserver.host, secure=True) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key, secure=True) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].secure is True + + def test_set_cookie_with_same_site_strict(self, driver, pages, webserver): + """Test setting a cookie with SameSite=Strict.""" + assert_no_cookies_are_present(driver) + + key = "samesite_strict" + value = BytesValue(BytesValue.TYPE_STRING, "strict") + + cookie = PartialCookie(key, value, webserver.host, same_site=SameSite.STRICT) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key, same_site=SameSite.STRICT) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].same_site == SameSite.STRICT + + def test_set_cookie_with_same_site_lax(self, driver, pages, webserver): + """Test setting a cookie with SameSite=Lax.""" + assert_no_cookies_are_present(driver) + + key = "samesite_lax" + value = BytesValue(BytesValue.TYPE_STRING, "lax") + + cookie = PartialCookie(key, value, webserver.host, same_site=SameSite.LAX) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key, same_site=SameSite.LAX) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].same_site == SameSite.LAX + + def test_set_cookie_with_same_site_none(self, driver, pages, webserver): + """Test setting a cookie with SameSite=None (requires Secure).""" + assert_no_cookies_are_present(driver) + + key = "samesite_none" + value = BytesValue(BytesValue.TYPE_STRING, "none") + + # SameSite=None typically requires secure=True + cookie = PartialCookie(key, value, webserver.host, same_site=SameSite.NONE, secure=True) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key, same_site=SameSite.NONE) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].same_site == SameSite.NONE + + def test_set_cookie_with_path_and_domain(self, driver, pages, webserver): + """Test setting a cookie with specific path and domain.""" + assert_no_cookies_are_present(driver) + + key = "path_domain_cookie" + value = BytesValue(BytesValue.TYPE_STRING, "scoped") + path = "/simpleTest.html" + + cookie = PartialCookie(key, value, webserver.host, path=path) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key, path=path) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].path == path + assert result.cookies[0].domain == webserver.host + + def test_set_cookie_with_future_expiry(self, driver, pages, webserver): + """Test setting a cookie with a future expiry date.""" + assert_no_cookies_are_present(driver) + + key = "future_expiry_cookie" + value = BytesValue(BytesValue.TYPE_STRING, "future") + + # Set expiry to 1 hour from now + future_expiry = int(time.time() + 3600) + + cookie = PartialCookie(key, value, webserver.host, expiry=future_expiry) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].expiry == future_expiry + + def test_set_cookie_with_string_value(self, driver, pages, webserver): + """Test setting a cookie with string value (standard format).""" + assert_no_cookies_are_present(driver) + + key = "string_value_cookie" + value = BytesValue(BytesValue.TYPE_STRING, "hello") + + cookie = PartialCookie(key, value, webserver.host) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify + cookie_filter = CookieFilter(name=key) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].value.value == "hello" + + def test_get_cookies_filter_by_domain(self, driver, pages, webserver): + """Test getting cookies filtered by domain.""" + assert_no_cookies_are_present(driver) + + key = generate_unique_key() + value = BytesValue(BytesValue.TYPE_STRING, "domain_test") + + cookie = PartialCookie(key, value, webserver.host) + driver.storage.set_cookie(cookie=cookie) + + # Filter by domain + cookie_filter = CookieFilter(domain=webserver.host) + result = driver.storage.get_cookies(filter=cookie_filter) + + # Should find the cookie + cookie_names = [c.name for c in result.cookies] + assert key in cookie_names + + def test_get_cookies_filter_by_path(self, driver, pages, webserver): + """Test getting cookies filtered by path.""" + assert_no_cookies_are_present(driver) + + key1 = generate_unique_key() + key2 = generate_unique_key() + value = BytesValue(BytesValue.TYPE_STRING, "path_test") + + # Cookie with specific path + cookie1 = PartialCookie(key1, value, webserver.host, path="/simpleTest.html") + # Cookie with root path + cookie2 = PartialCookie(key2, value, webserver.host, path="/") + + driver.storage.set_cookie(cookie=cookie1) + driver.storage.set_cookie(cookie=cookie2) + + # Filter by specific path + cookie_filter = CookieFilter(path="/simpleTest.html") + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert all(c.path == "/simpleTest.html" for c in result.cookies) + + def test_multiple_cookies_same_name_different_paths(self, driver, pages, webserver): + """Test setting multiple cookies with same name but different paths.""" + assert_no_cookies_are_present(driver) + + key = "multi_path_cookie" + value = BytesValue(BytesValue.TYPE_STRING, "test") + + # Create cookies with same name but different paths + cookie1 = PartialCookie(key, value, webserver.host, path="/") + cookie2 = PartialCookie(key, value, webserver.host, path="/simpleTest.html") + + driver.storage.set_cookie(cookie=cookie1) + driver.storage.set_cookie(cookie=cookie2) + + # Both should exist + cookie_filter = CookieFilter(name=key) + result = driver.storage.get_cookies(filter=cookie_filter) + + # Should find at least 2 cookies with this name (different paths) + assert len(result.cookies) >= 2 + + def test_delete_cookie_by_path(self, driver, pages, webserver): + """Test deleting cookies filtered by path.""" + assert_no_cookies_are_present(driver) + + key1 = generate_unique_key() + key2 = generate_unique_key() + value = BytesValue(BytesValue.TYPE_STRING, "delete_test") + + cookie1 = PartialCookie(key1, value, webserver.host, path="/simpleTest.html") + cookie2 = PartialCookie(key2, value, webserver.host, path="/") + + driver.storage.set_cookie(cookie=cookie1) + driver.storage.set_cookie(cookie=cookie2) + + # Delete only cookies with specific path + driver.storage.delete_cookies(filter=CookieFilter(path="/simpleTest.html")) + + # Verify path-specific cookie is deleted, root path cookie remains + result = driver.storage.get_cookies(filter=CookieFilter()) + cookie_names = [c.name for c in result.cookies] + + assert key1 not in cookie_names or all(c.path != "/simpleTest.html" for c in result.cookies if c.name == key1) + + def test_cookie_expiry_timestamp(self, driver, pages, webserver): + """Test that cookie expiry is stored correctly as timestamp.""" + assert_no_cookies_are_present(driver) + + key = "expiry_test" + value = BytesValue(BytesValue.TYPE_STRING, "expires") + + # Set expiry to specific time + expiry_time = int(time.time() + 7200) # 2 hours from now + + cookie = PartialCookie(key, value, webserver.host, expiry=expiry_time) + + driver.storage.set_cookie(cookie=cookie) + + # Get and verify + cookie_filter = CookieFilter(name=key) + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + assert result.cookies[0].expiry == expiry_time + + def test_cookie_combined_attributes(self, driver, pages, webserver): + """Test setting and getting cookie with multiple attributes combined.""" + assert_no_cookies_are_present(driver) + + key = "combined_attrs" + value = BytesValue(BytesValue.TYPE_STRING, "all_features") + path = "/simpleTest.html" + expiry = int(time.time() + 3600) + + cookie = PartialCookie( + key, + value, + webserver.host, + path=path, + http_only=True, + secure=True, + same_site=SameSite.LAX, + expiry=expiry, + ) + + # Test + driver.storage.set_cookie(cookie=cookie) + + # Verify with matching filter + cookie_filter = CookieFilter( + name=key, + path=path, + http_only=True, + secure=True, + same_site=SameSite.LAX, + expiry=expiry, + ) + + result = driver.storage.get_cookies(filter=cookie_filter) + + assert len(result.cookies) > 0 + cookie_result = result.cookies[0] + assert cookie_result.name == key + assert cookie_result.value.value == value.value + assert cookie_result.path == path + assert cookie_result.http_only is True + assert cookie_result.secure is True + assert cookie_result.same_site == SameSite.LAX + assert cookie_result.expiry == expiry From 9e848e5a0e1f784d4f67fe1010939c7ac60a7921 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 18:30:31 +0000 Subject: [PATCH 07/10] Add more storage tests --- .../webdriver/common/bidi_input_tests.py | 110 ++++++++++++++---- .../webdriver/common/bidi_storage_tests.py | 65 ++++++++--- 2 files changed, 139 insertions(+), 36 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_input_tests.py b/py/test/selenium/webdriver/common/bidi_input_tests.py index ae8df4b251e1f..9929a01117924 100644 --- a/py/test/selenium/webdriver/common/bidi_input_tests.py +++ b/py/test/selenium/webdriver/common/bidi_input_tests.py @@ -74,7 +74,9 @@ def test_basic_key_input(driver, pages): driver.input.perform_actions(driver.current_window_handle, [key_actions]) - WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "hello") + WebDriverWait(driver, 5).until( + lambda d: input_element.get_attribute("value") == "hello" + ) assert input_element.get_attribute("value") == "hello" @@ -98,7 +100,9 @@ def test_key_input_with_pause(driver, pages): driver.input.perform_actions(driver.current_window_handle, [key_actions]) - WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "ab") + WebDriverWait(driver, 5).until( + lambda d: input_element.get_attribute("value") == "ab" + ) assert input_element.get_attribute("value") == "ab" @@ -171,7 +175,13 @@ def test_pointer_with_common_properties(driver, pages): # Create pointer properties properties = PointerCommonProperties( - width=2, height=2, pressure=0.5, tangential_pressure=0.0, twist=45, altitude_angle=0.5, azimuth_angle=1.0 + width=2, + height=2, + pressure=0.5, + tangential_pressure=0.0, + twist=45, + altitude_angle=0.5, + azimuth_angle=1.0, ) pointer_actions = PointerSourceActions( @@ -197,7 +207,12 @@ def test_wheel_scroll(driver, pages): # Scroll down wheel_actions = WheelSourceActions( - id="wheel", actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT)] + id="wheel", + actions=[ + WheelScrollAction( + x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT + ) + ], ) driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) @@ -248,9 +263,13 @@ def test_combined_input_actions(driver, pages): ], ) - driver.input.perform_actions(driver.current_window_handle, [pointer_actions, key_actions]) + driver.input.perform_actions( + driver.current_window_handle, [pointer_actions, key_actions] + ) - WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "test") + WebDriverWait(driver, 5).until( + lambda d: input_element.get_attribute("value") == "test" + ) assert input_element.get_attribute("value") == "test" @@ -262,7 +281,9 @@ def test_set_files(driver, pages): assert upload_element.get_attribute("value") == "" # Create a temporary file - with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as temp_file: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".txt", delete=False + ) as temp_file: temp_file.write("test content") temp_file_path = temp_file.name @@ -272,7 +293,9 @@ def test_set_files(driver, pages): element_ref = {"sharedId": element_id} # Set files using BiDi - driver.input.set_files(driver.current_window_handle, element_ref, [temp_file_path]) + driver.input.set_files( + driver.current_window_handle, element_ref, [temp_file_path] + ) # Verify file was set value = upload_element.get_attribute("value") @@ -347,7 +370,9 @@ def test_release_actions(driver, pages): driver.input.perform_actions(driver.current_window_handle, [key_actions2]) # Should be able to type normally - WebDriverWait(driver, 5).until(lambda d: "b" in input_element.get_attribute("value")) + WebDriverWait(driver, 5).until( + lambda d: "b" in input_element.get_attribute("value") + ) @pytest.mark.parametrize("multiple", [True, False]) @@ -363,7 +388,9 @@ def file_dialog_handler(file_dialog_info): handler_id = driver.input.add_file_dialog_handler(file_dialog_handler) assert handler_id is not None - driver.get(f"data:text/html,") + driver.get( + f"data:text/html," + ) # Use script.evaluate to trigger the file dialog with user activation driver.script._evaluate( @@ -463,7 +490,9 @@ def test_perform_actions_rapid_key_sequence(driver, pages): driver.input.perform_actions(driver.current_window_handle, [key_actions]) - WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "abcd") + WebDriverWait(driver, 5).until( + lambda d: input_element.get_attribute("value") == "abcd" + ) assert input_element.get_attribute("value") == "abcd" @@ -588,7 +617,11 @@ def test_wheel_scroll_negative_delta(driver, pages): # First scroll down wheel_actions_down = WheelSourceActions( id="wheel_down", - actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT)], + actions=[ + WheelScrollAction( + x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT + ) + ], ) driver.input.perform_actions(driver.current_window_handle, [wheel_actions_down]) @@ -599,7 +632,11 @@ def test_wheel_scroll_negative_delta(driver, pages): # Then scroll back up (negative delta) wheel_actions_up = WheelSourceActions( id="wheel_up", - actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=-50, origin=Origin.VIEWPORT)], + actions=[ + WheelScrollAction( + x=100, y=100, delta_x=0, delta_y=-50, origin=Origin.VIEWPORT + ) + ], ) driver.input.perform_actions(driver.current_window_handle, [wheel_actions_up]) @@ -614,7 +651,16 @@ def test_wheel_scroll_with_duration(driver, pages): wheel_actions = WheelSourceActions( id="wheel", - actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, duration=500, origin=Origin.VIEWPORT)], + actions=[ + WheelScrollAction( + x=100, + y=100, + delta_x=0, + delta_y=100, + duration=500, + origin=Origin.VIEWPORT, + ) + ], ) driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) @@ -630,7 +676,11 @@ def test_wheel_scroll_horizontal(driver, pages): # Scroll horizontally wheel_actions = WheelSourceActions( id="wheel", - actions=[WheelScrollAction(x=100, y=100, delta_x=50, delta_y=0, origin=Origin.VIEWPORT)], + actions=[ + WheelScrollAction( + x=100, y=100, delta_x=50, delta_y=0, origin=Origin.VIEWPORT + ) + ], ) driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) @@ -661,7 +711,9 @@ def test_key_input_special_characters(driver, pages): driver.input.perform_actions(driver.current_window_handle, [key_actions]) - WebDriverWait(driver, 5).until(lambda d: "!" in input_element.get_attribute("value")) + WebDriverWait(driver, 5).until( + lambda d: "!" in input_element.get_attribute("value") + ) def test_set_files_empty_file_list(driver, pages): @@ -689,7 +741,9 @@ def test_set_files_with_absolute_path(driver): upload_element = driver.find_element(By.ID, "upload") # Create a temporary file - with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as temp_file: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".txt", delete=False + ) as temp_file: temp_file.write("test file content") temp_file_path = temp_file.name @@ -699,7 +753,9 @@ def test_set_files_with_absolute_path(driver): element_ref = {"sharedId": element_id} # Set file using absolute path - driver.input.set_files(driver.current_window_handle, element_ref, [temp_file_path]) + driver.input.set_files( + driver.current_window_handle, element_ref, [temp_file_path] + ) value = upload_element.get_attribute("value") assert os.path.basename(temp_file_path) in value @@ -826,11 +882,15 @@ def test_combined_keyboard_and_wheel_actions(driver, pages): id="wheel", actions=[ PauseAction(duration=0), # Sync with keyboard - WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT), + WheelScrollAction( + x=100, y=100, delta_x=0, delta_y=100, origin=Origin.VIEWPORT + ), ], ) - driver.input.perform_actions(driver.current_window_handle, [key_actions, wheel_actions]) + driver.input.perform_actions( + driver.current_window_handle, [key_actions, wheel_actions] + ) scroll_y = driver.execute_script("return window.pageYOffset;") assert scroll_y == 100 @@ -857,7 +917,9 @@ def test_key_input_with_value_attribute(driver, pages): driver.input.perform_actions(driver.current_window_handle, [key_actions]) - WebDriverWait(driver, 5).until(lambda d: input_element.get_attribute("value") == "xyz") + WebDriverWait(driver, 5).until( + lambda d: input_element.get_attribute("value") == "xyz" + ) assert input_element.get_attribute("value") == "xyz" @@ -874,7 +936,11 @@ def test_wheel_scroll_with_element_origin(driver, pages): # Scroll with element origin wheel_actions = WheelSourceActions( id="wheel", - actions=[WheelScrollAction(x=100, y=100, delta_x=0, delta_y=100, origin=element_origin)], + actions=[ + WheelScrollAction( + x=100, y=100, delta_x=0, delta_y=100, origin=element_origin + ) + ], ) driver.input.perform_actions(driver.current_window_handle, [wheel_actions]) diff --git a/py/test/selenium/webdriver/common/bidi_storage_tests.py b/py/test/selenium/webdriver/common/bidi_storage_tests.py index 807e4d62c403c..86d219b83a03a 100644 --- a/py/test/selenium/webdriver/common/bidi_storage_tests.py +++ b/py/test/selenium/webdriver/common/bidi_storage_tests.py @@ -98,7 +98,9 @@ def test_get_cookie_by_name(self, driver, pages, webserver): driver.add_cookie({"name": key, "value": value}) # Test - cookie_filter = CookieFilter(name=key, value=BytesValue(BytesValue.TYPE_STRING, "set")) + cookie_filter = CookieFilter( + name=key, value=BytesValue(BytesValue.TYPE_STRING, "set") + ) result = driver.storage.get_cookies(filter=cookie_filter) @@ -120,14 +122,18 @@ def test_get_cookie_in_default_user_context(self, driver, pages, webserver): driver.add_cookie({"name": key, "value": value}) # Test - cookie_filter = CookieFilter(name=key, value=BytesValue(BytesValue.TYPE_STRING, "set")) + cookie_filter = CookieFilter( + name=key, value=BytesValue(BytesValue.TYPE_STRING, "set") + ) driver.switch_to.new_window(WindowTypes.WINDOW) descriptor = BrowsingContextPartitionDescriptor(driver.current_window_handle) params = cookie_filter - result_after_switching_context = driver.storage.get_cookies(filter=params, partition=descriptor) + result_after_switching_context = driver.storage.get_cookies( + filter=params, partition=descriptor + ) assert len(result_after_switching_context.cookies) > 0 assert result_after_switching_context.cookies[0].value.value == value @@ -158,15 +164,21 @@ def test_get_cookie_in_a_user_context(self, driver, pages, webserver): descriptor = StorageKeyPartitionDescriptor(user_context=user_context) - parameters = PartialCookie(key, BytesValue(BytesValue.TYPE_STRING, value), webserver.host) + parameters = PartialCookie( + key, BytesValue(BytesValue.TYPE_STRING, value), webserver.host + ) driver.storage.set_cookie(cookie=parameters, partition=descriptor) # Test - cookie_filter = CookieFilter(name=key, value=BytesValue(BytesValue.TYPE_STRING, "set")) + cookie_filter = CookieFilter( + name=key, value=BytesValue(BytesValue.TYPE_STRING, "set") + ) # Create a new window with the user context - new_window = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context) + new_window = driver.browsing_context.create( + type=WindowTypes.TAB, user_context=user_context + ) driver.switch_to.window(new_window) @@ -181,9 +193,13 @@ def test_get_cookie_in_a_user_context(self, driver, pages, webserver): driver.switch_to.window(window_handle) - browsing_context_partition_descriptor = BrowsingContextPartitionDescriptor(window_handle) + browsing_context_partition_descriptor = BrowsingContextPartitionDescriptor( + window_handle + ) - result1 = driver.storage.get_cookies(filter=cookie_filter, partition=browsing_context_partition_descriptor) + result1 = driver.storage.get_cookies( + filter=cookie_filter, partition=browsing_context_partition_descriptor + ) assert len(result1.cookies) == 0 @@ -198,7 +214,9 @@ def test_add_cookie(self, driver, pages, webserver): key = generate_unique_key() value = "foo" - parameters = PartialCookie(key, BytesValue(BytesValue.TYPE_STRING, value), webserver.host) + parameters = PartialCookie( + key, BytesValue(BytesValue.TYPE_STRING, value), webserver.host + ) assert_cookie_is_not_present_with_name(driver, key) # Test @@ -223,7 +241,14 @@ def test_add_and_get_cookie(self, driver, pages, webserver): path = "/simpleTest.html" cookie = PartialCookie( - "fish", value, domain, path=path, http_only=True, secure=False, same_site=SameSite.LAX, expiry=expiry + "fish", + value, + domain, + path=path, + http_only=True, + secure=False, + same_site=SameSite.LAX, + expiry=expiry, ) # Test @@ -336,10 +361,18 @@ def test_add_cookies_with_different_paths(self, driver, pages, webserver): assert_no_cookies_are_present(driver) cookie1 = PartialCookie( - "fish", BytesValue(BytesValue.TYPE_STRING, "cod"), webserver.host, path="/simpleTest.html" + "fish", + BytesValue(BytesValue.TYPE_STRING, "cod"), + webserver.host, + path="/simpleTest.html", ) - cookie2 = PartialCookie("planet", BytesValue(BytesValue.TYPE_STRING, "earth"), webserver.host, path="/") + cookie2 = PartialCookie( + "planet", + BytesValue(BytesValue.TYPE_STRING, "earth"), + webserver.host, + path="/", + ) # Test driver.storage.set_cookie(cookie=cookie1) @@ -495,7 +528,9 @@ def test_set_cookie_with_same_site_none(self, driver, pages, webserver): value = BytesValue(BytesValue.TYPE_STRING, "none") # SameSite=None typically requires secure=True - cookie = PartialCookie(key, value, webserver.host, same_site=SameSite.NONE, secure=True) + cookie = PartialCookie( + key, value, webserver.host, same_site=SameSite.NONE, secure=True + ) # Test driver.storage.set_cookie(cookie=cookie) @@ -652,7 +687,9 @@ def test_delete_cookie_by_path(self, driver, pages, webserver): result = driver.storage.get_cookies(filter=CookieFilter()) cookie_names = [c.name for c in result.cookies] - assert key1 not in cookie_names or all(c.path != "/simpleTest.html" for c in result.cookies if c.name == key1) + assert key1 not in cookie_names or all( + c.path != "/simpleTest.html" for c in result.cookies if c.name == key1 + ) def test_cookie_expiry_timestamp(self, driver, pages, webserver): """Test that cookie expiry is stored correctly as timestamp.""" From e6f35ba2d0e29dd8cbf28bf136db0f466d6ea53a Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Mon, 9 Mar 2026 18:37:26 +0000 Subject: [PATCH 08/10] Extend webextension tests --- .../common/bidi_webextension_tests.py | 307 +++++++++++++++++- 1 file changed, 306 insertions(+), 1 deletion(-) diff --git a/py/test/selenium/webdriver/common/bidi_webextension_tests.py b/py/test/selenium/webdriver/common/bidi_webextension_tests.py index 7bea9f71e7e16..93c6c5a1d5528 100644 --- a/py/test/selenium/webdriver/common/bidi_webextension_tests.py +++ b/py/test/selenium/webdriver/common/bidi_webextension_tests.py @@ -179,4 +179,309 @@ def test_install_with_extension_id_uninstall(self, chromium_driver): ext_info = chromium_driver.webextension.install(path=path) extension_id = ext_info.get("extension") # Uninstall using the extension ID - uninstall_extension_and_verify_extension_uninstalled(chromium_driver, extension_id) + uninstall_extension_and_verify_extension_uninstalled( + chromium_driver, extension_id + ) + + +# Additional edge case tests for better WPT coverage + + +class TestFirefoxWebExtensionEdgeCases: + """Firefox WebExtension edge case tests.""" + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_uninstall_extension_by_id_string(self, driver, pages): + """Test uninstalling extension using extension ID as string.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = install_extension(driver, path=path) + extension_id_string = ext_info.get("extension") + + # Uninstall using ID string directly + driver.webextension.uninstall(extension_id_string) + + # Verify uninstall was successful + driver.browsing_context.reload(driver.current_window_handle) + assert len(driver.find_elements(By.ID, "webextensions-selenium-example")) == 0 + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_uninstall_extension_by_result_dict(self, driver, pages): + """Test uninstalling extension using result dictionary from install.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = install_extension(driver, path=path) + + # Uninstall using result dict + driver.webextension.uninstall(ext_info) + + # Verify uninstall was successful + driver.browsing_context.reload(driver.current_window_handle) + assert len(driver.find_elements(By.ID, "webextensions-selenium-example")) == 0 + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_install_returns_extension_id(self, driver, pages): + """Test that install returns proper extension ID in result.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = install_extension(driver, path=path) + + # Verify result structure + assert "extension" in ext_info + assert isinstance(ext_info.get("extension"), str) + assert len(ext_info.get("extension", "")) > 0 + assert ext_info.get("extension") == EXTENSION_ID + + # Cleanup + driver.webextension.uninstall(ext_info) + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_extension_content_script_injection(self, driver, pages): + """Test that extension content scripts are properly injected.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = install_extension(driver, path=path) + + # Load page and verify content script injection + pages.load("blank.html") + + # Element should be injected by extension + injected_element = WebDriverWait(driver, timeout=5).until( + lambda dr: dr.find_element(By.ID, "webextensions-selenium-example") + ) + + assert injected_element is not None + assert ( + "Content injected by webextensions-selenium-example" + in injected_element.text + ) + + # Cleanup + driver.webextension.uninstall(ext_info) + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_uninstall_removes_content_scripts(self, driver, pages): + """Test that uninstalling extension removes content scripts.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = install_extension(driver, path=path) + + # Verify injection works + pages.load("blank.html") + WebDriverWait(driver, timeout=5).until( + lambda dr: dr.find_element(By.ID, "webextensions-selenium-example") + ) + + # Uninstall + driver.webextension.uninstall(ext_info) + + # Reload page and verify injection is gone + driver.browsing_context.reload(driver.current_window_handle) + assert len(driver.find_elements(By.ID, "webextensions-selenium-example")) == 0 + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_install_from_archive_returns_extension_id(self, driver, pages): + """Test that archive install returns proper extension ID.""" + archive_path = os.path.join(EXTENSIONS, EXTENSION_ARCHIVE_PATH) + ext_info = install_extension(driver, archive_path=archive_path) + + # Verify result structure + assert "extension" in ext_info + assert isinstance(ext_info.get("extension"), str) + assert len(ext_info.get("extension", "")) > 0 + + # Cleanup + driver.webextension.uninstall(ext_info) + + @pytest.mark.xfail_chrome + @pytest.mark.xfail_edge + def test_multiple_installations_and_uninstalls(self, driver, pages): + """Test installing and uninstalling extension multiple times.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + + # Install/uninstall cycle 1 + ext_info_1 = install_extension(driver, path=path) + verify_extension_injection(driver, pages) + driver.webextension.uninstall(ext_info_1) + driver.browsing_context.reload(driver.current_window_handle) + assert len(driver.find_elements(By.ID, "webextensions-selenium-example")) == 0 + + # Install/uninstall cycle 2 + ext_info_2 = install_extension(driver, path=path) + verify_extension_injection(driver, pages) + driver.webextension.uninstall(ext_info_2) + driver.browsing_context.reload(driver.current_window_handle) + assert len(driver.find_elements(By.ID, "webextensions-selenium-example")) == 0 + + +class TestChromiumWebExtensionEdgeCases: + """Chrome/Edge WebExtension edge case tests.""" + + @pytest.mark.xfail_firefox + @pytest.fixture + def pages_chromium(self, webserver, chromium_driver): + class Pages: + def load(self, name): + chromium_driver.get(webserver.where_is(name, localhost=False)) + + return Pages() + + @pytest.mark.xfail_firefox + @pytest.fixture + def chromium_driver(self, chromium_options, request): + """Create a Chrome/Edge driver with webextension support enabled.""" + driver_option = request.config.option.drivers[0].lower() + + if driver_option == "chrome": + browser_class = webdriver.Chrome + browser_service = webdriver.ChromeService + elif driver_option == "edge": + browser_class = webdriver.Edge + browser_service = webdriver.EdgeService + + temp_dir = tempfile.mkdtemp(prefix="chromium-profile-") + + chromium_options.enable_bidi = True + chromium_options.enable_webextensions = True + chromium_options.add_argument(f"--user-data-dir={temp_dir}") + chromium_options.add_argument("--no-sandbox") + chromium_options.add_argument("--disable-dev-shm-usage") + + binary = request.config.option.binary + if binary: + chromium_options.binary_location = binary + + executable = request.config.option.executable + if executable: + service = browser_service(executable_path=executable) + else: + service = browser_service() + + chromium_driver = browser_class(options=chromium_options, service=service) + + yield chromium_driver + chromium_driver.quit() + + # delete the temp directory + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + + @pytest.mark.xfail_firefox + def test_uninstall_extension_by_id_string(self, chromium_driver, pages_chromium): + """Test uninstalling extension using extension ID as string.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = chromium_driver.webextension.install(path=path) + extension_id_string = ext_info.get("extension") + + # Uninstall using ID string directly + chromium_driver.webextension.uninstall(extension_id_string) + + # Verify uninstall was successful + chromium_driver.browsing_context.reload(chromium_driver.current_window_handle) + assert ( + len(chromium_driver.find_elements(By.ID, "webextensions-selenium-example")) + == 0 + ) + + @pytest.mark.xfail_firefox + def test_uninstall_extension_by_result_dict(self, chromium_driver, pages_chromium): + """Test uninstalling extension using result dictionary from install.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = chromium_driver.webextension.install(path=path) + + # Uninstall using result dict + chromium_driver.webextension.uninstall(ext_info) + + # Verify uninstall was successful + chromium_driver.browsing_context.reload(chromium_driver.current_window_handle) + assert ( + len(chromium_driver.find_elements(By.ID, "webextensions-selenium-example")) + == 0 + ) + + @pytest.mark.xfail_firefox + def test_install_returns_extension_id(self, chromium_driver, pages_chromium): + """Test that install returns proper extension ID in result.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = chromium_driver.webextension.install(path=path) + + # Verify result structure + assert "extension" in ext_info + assert isinstance(ext_info.get("extension"), str) + assert len(ext_info.get("extension", "")) > 0 + + # Cleanup + chromium_driver.webextension.uninstall(ext_info) + + @pytest.mark.xfail_firefox + def test_extension_content_script_injection(self, chromium_driver, pages_chromium): + """Test that extension content scripts are properly injected.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = chromium_driver.webextension.install(path=path) + + # Load page and verify content script injection + pages_chromium.load("blank.html") + + # Element should be injected by extension + injected_element = WebDriverWait(chromium_driver, timeout=5).until( + lambda dr: dr.find_element(By.ID, "webextensions-selenium-example") + ) + + assert injected_element is not None + assert ( + "Content injected by webextensions-selenium-example" + in injected_element.text + ) + + # Cleanup + chromium_driver.webextension.uninstall(ext_info) + + @pytest.mark.xfail_firefox + def test_uninstall_removes_content_scripts(self, chromium_driver, pages_chromium): + """Test that uninstalling extension removes content scripts.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + ext_info = chromium_driver.webextension.install(path=path) + + # Verify injection works + pages_chromium.load("blank.html") + WebDriverWait(chromium_driver, timeout=5).until( + lambda dr: dr.find_element(By.ID, "webextensions-selenium-example") + ) + + # Uninstall + chromium_driver.webextension.uninstall(ext_info) + + # Reload page and verify injection is gone + chromium_driver.browsing_context.reload(chromium_driver.current_window_handle) + assert ( + len(chromium_driver.find_elements(By.ID, "webextensions-selenium-example")) + == 0 + ) + + @pytest.mark.xfail_firefox + def test_multiple_installations_and_uninstalls( + self, chromium_driver, pages_chromium + ): + """Test installing and uninstalling extension multiple times.""" + path = os.path.join(EXTENSIONS, EXTENSION_PATH) + + # Install/uninstall cycle 1 + ext_info_1 = chromium_driver.webextension.install(path=path) + verify_extension_injection(chromium_driver, pages_chromium) + chromium_driver.webextension.uninstall(ext_info_1) + chromium_driver.browsing_context.reload(chromium_driver.current_window_handle) + assert ( + len(chromium_driver.find_elements(By.ID, "webextensions-selenium-example")) + == 0 + ) + + # Install/uninstall cycle 2 + ext_info_2 = chromium_driver.webextension.install(path=path) + verify_extension_injection(chromium_driver, pages_chromium) + chromium_driver.webextension.uninstall(ext_info_2) + chromium_driver.browsing_context.reload(chromium_driver.current_window_handle) + assert ( + len(chromium_driver.find_elements(By.ID, "webextensions-selenium-example")) + == 0 + ) From 87af1899f83e0d6afde3da2096d13ae54e14e04b Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Tue, 10 Mar 2026 12:10:09 +0000 Subject: [PATCH 09/10] fixing comments --- .../webdriver/common/bidi_errors_tests.py | 5 - .../common/bidi_integration_tests.py | 29 +++ .../webdriver/common/bidi_log_tests.py | 9 - .../webdriver/common/bidi_script_tests.py | 171 ++++++++++-------- 4 files changed, 125 insertions(+), 89 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_errors_tests.py b/py/test/selenium/webdriver/common/bidi_errors_tests.py index 2f30e3fdc344a..2d826b280ca0b 100644 --- a/py/test/selenium/webdriver/common/bidi_errors_tests.py +++ b/py/test/selenium/webdriver/common/bidi_errors_tests.py @@ -21,11 +21,6 @@ from selenium.webdriver.common.by import By -def test_errors_module_initialized(driver): - """Test that the errors module is accessible.""" - assert driver.script is not None - - def test_invalid_browsing_context_id(driver): """Test that invalid browsing context ID raises an error.""" with pytest.raises(WebDriverException): diff --git a/py/test/selenium/webdriver/common/bidi_integration_tests.py b/py/test/selenium/webdriver/common/bidi_integration_tests.py index d6e49a878e2e9..85323f49b3ccf 100644 --- a/py/test/selenium/webdriver/common/bidi_integration_tests.py +++ b/py/test/selenium/webdriver/common/bidi_integration_tests.py @@ -29,6 +29,9 @@ class TestBidiNetworkWithCookies: def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") + yield + # Cleanup: delete all cookies to prevent bleed-through + driver.delete_all_cookies() def test_cookies_interaction(self, driver, pages): """Test that cookies work with network operations.""" @@ -65,7 +68,10 @@ class TestBidiScriptWithNavigation: @pytest.fixture(autouse=True) def setup(self, driver, pages): """Setup for each test in this class.""" + driver.delete_all_cookies() pages.load("blank.html") + yield + # Cleanup: delete all cookies to prevent bleed-through driver.delete_all_cookies() def test_script_execution_after_navigation(self, driver, pages): @@ -112,6 +118,9 @@ class TestBidiEmulationWithNavigation: def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") + yield + # Cleanup: delete all cookies to prevent bleed-through + driver.delete_all_cookies() def test_basic_navigation(self, driver, pages): """Test basic navigation.""" @@ -152,6 +161,9 @@ class TestBidiEventHandlers: def setup(self, driver, pages): """Setup for each test in this class.""" pages.load("blank.html") + yield + # Cleanup: delete all cookies to prevent bleed-through + driver.delete_all_cookies() def test_multiple_console_handlers(self, driver): """Test multiple console message handlers.""" @@ -177,6 +189,15 @@ def test_multiple_console_handlers(self, driver): class TestBidiStorageOperations: """Test storage operations.""" + @pytest.fixture(autouse=True) + def setup(self, driver, pages): + """Setup for each test in this class.""" + driver.delete_all_cookies() + pages.load("blank.html") + yield + # Cleanup: delete all cookies to prevent bleed-through + driver.delete_all_cookies() + def test_cookie_operations(self, driver, pages): """Test basic cookie operations.""" pages.load("blank.html") @@ -213,6 +234,14 @@ def test_cookie_attributes(self, driver, pages): class TestBidiBrowsingContexts: """Test browsing context operations.""" + @pytest.fixture(autouse=True) + def setup(self, driver): + """Setup for each test in this class.""" + driver.delete_all_cookies() + yield + # Cleanup: delete all cookies to prevent bleed-through + driver.delete_all_cookies() + def test_create_new_window(self, driver): """Test creating a new window context.""" # Create new tab diff --git a/py/test/selenium/webdriver/common/bidi_log_tests.py b/py/test/selenium/webdriver/common/bidi_log_tests.py index d52e6b3dd06a4..fbbd3a8166b2d 100644 --- a/py/test/selenium/webdriver/common/bidi_log_tests.py +++ b/py/test/selenium/webdriver/common/bidi_log_tests.py @@ -159,12 +159,3 @@ def test_log_entry_attributes(self, driver): assert hasattr(log_entries[0], "text") or hasattr(log_entries[0], "args") finally: driver.script.remove_console_message_handler(handler_id) - - -class TestBidiJavaScriptErrors: - """Test class for JavaScript error logging.""" - - @pytest.fixture(autouse=True) - def setup(self, driver, pages): - """Setup for each test in this class.""" - pages.load("blank.html") diff --git a/py/test/selenium/webdriver/common/bidi_script_tests.py b/py/test/selenium/webdriver/common/bidi_script_tests.py index 194a1c9909897..dfc91dc92ba1e 100644 --- a/py/test/selenium/webdriver/common/bidi_script_tests.py +++ b/py/test/selenium/webdriver/common/bidi_script_tests.py @@ -42,18 +42,21 @@ def test_logs_console_messages(driver, pages): pages.load("bidi/logEntryAdded.html") log_entries = [] - driver.script.add_console_message_handler(log_entries.append) + handler_id = driver.script.add_console_message_handler(log_entries.append) - driver.find_element(By.ID, "jsException").click() - driver.find_element(By.ID, "consoleLog").click() + try: + driver.find_element(By.ID, "jsException").click() + driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) + WebDriverWait(driver, 5).until(lambda _: log_entries) - log_entry = log_entries[0] - assert log_entry.level == LogLevel.INFO - assert log_entry.method == "log" - assert log_entry.text == "Hello, world!" - assert log_entry.type_ == "console" + log_entry = log_entries[0] + assert log_entry.level == LogLevel.INFO + assert log_entry.method == "log" + assert log_entry.text == "Hello, world!" + assert log_entry.type_ == "console" + finally: + driver.script.remove_console_message_handler(handler_id) def test_logs_console_errors(driver, pages): @@ -64,34 +67,41 @@ def log_error(entry): if entry.level == LogLevel.ERROR: log_entries.append(entry) - driver.script.add_console_message_handler(log_error) + handler_id = driver.script.add_console_message_handler(log_error) - driver.find_element(By.ID, "consoleLog").click() - driver.find_element(By.ID, "consoleError").click() + try: + driver.find_element(By.ID, "consoleLog").click() + driver.find_element(By.ID, "consoleError").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) + WebDriverWait(driver, 5).until(lambda _: log_entries) - assert len(log_entries) == 1 + assert len(log_entries) == 1 - log_entry = log_entries[0] - assert log_entry.level == LogLevel.ERROR - assert log_entry.method == "error" - assert log_entry.text == "I am console error" - assert log_entry.type_ == "console" + log_entry = log_entries[0] + assert log_entry.level == LogLevel.ERROR + assert log_entry.method == "error" + assert log_entry.text == "I am console error" + assert log_entry.type_ == "console" + finally: + driver.script.remove_console_message_handler(handler_id) def test_logs_multiple_console_messages(driver, pages): pages.load("bidi/logEntryAdded.html") log_entries = [] - driver.script.add_console_message_handler(log_entries.append) - driver.script.add_console_message_handler(log_entries.append) + handler_id1 = driver.script.add_console_message_handler(log_entries.append) + handler_id2 = driver.script.add_console_message_handler(log_entries.append) - driver.find_element(By.ID, "jsException").click() - driver.find_element(By.ID, "consoleLog").click() + try: + driver.find_element(By.ID, "jsException").click() + driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: len(log_entries) > 1) - assert len(log_entries) == 2 + WebDriverWait(driver, 5).until(lambda _: len(log_entries) > 1) + assert len(log_entries) == 2 + finally: + driver.script.remove_console_message_handler(handler_id1) + driver.script.remove_console_message_handler(handler_id2) def test_removes_console_message_handler(driver, pages): @@ -100,32 +110,41 @@ def test_removes_console_message_handler(driver, pages): log_entries1 = [] log_entries2 = [] - id = driver.script.add_console_message_handler(log_entries1.append) - driver.script.add_console_message_handler(log_entries2.append) + id1 = driver.script.add_console_message_handler(log_entries1.append) + id2 = driver.script.add_console_message_handler(log_entries2.append) - driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: len(log_entries1) and len(log_entries2)) + try: + driver.find_element(By.ID, "consoleLog").click() + WebDriverWait(driver, 5).until( + lambda _: len(log_entries1) and len(log_entries2) + ) - driver.script.remove_console_message_handler(id) - driver.find_element(By.ID, "consoleLog").click() + driver.script.remove_console_message_handler(id1) + driver.find_element(By.ID, "consoleLog").click() - WebDriverWait(driver, 5).until(lambda _: len(log_entries2) == 2) - assert len(log_entries1) == 1 + WebDriverWait(driver, 5).until(lambda _: len(log_entries2) == 2) + assert len(log_entries1) == 1 + finally: + driver.script.remove_console_message_handler(id1) + driver.script.remove_console_message_handler(id2) def test_javascript_error_messages(driver, pages): pages.load("bidi/logEntryAdded.html") log_entries = [] - driver.script.add_javascript_error_handler(log_entries.append) + handler_id = driver.script.add_javascript_error_handler(log_entries.append) - driver.find_element(By.ID, "jsException").click() - WebDriverWait(driver, 5).until(lambda _: log_entries) + try: + driver.find_element(By.ID, "jsException").click() + WebDriverWait(driver, 5).until(lambda _: log_entries) - log_entry = log_entries[0] - assert log_entry.text == "Error: Not working" - assert log_entry.level == LogLevel.ERROR - assert log_entry.type_ == "javascript" + log_entry = log_entries[0] + assert log_entry.text == "Error: Not working" + assert log_entry.level == LogLevel.ERROR + assert log_entry.type_ == "javascript" + finally: + driver.script.remove_javascript_error_handler(handler_id) def test_removes_javascript_message_handler(driver, pages): @@ -134,17 +153,23 @@ def test_removes_javascript_message_handler(driver, pages): log_entries1 = [] log_entries2 = [] - id = driver.script.add_javascript_error_handler(log_entries1.append) - driver.script.add_javascript_error_handler(log_entries2.append) + id1 = driver.script.add_javascript_error_handler(log_entries1.append) + id2 = driver.script.add_javascript_error_handler(log_entries2.append) - driver.find_element(By.ID, "jsException").click() - WebDriverWait(driver, 5).until(lambda _: len(log_entries1) and len(log_entries2)) + try: + driver.find_element(By.ID, "jsException").click() + WebDriverWait(driver, 5).until( + lambda _: len(log_entries1) and len(log_entries2) + ) - driver.script.remove_javascript_error_handler(id) - driver.find_element(By.ID, "jsException").click() + driver.script.remove_javascript_error_handler(id1) + driver.find_element(By.ID, "jsException").click() - WebDriverWait(driver, 5).until(lambda _: len(log_entries2) == 2) - assert len(log_entries1) == 1 + WebDriverWait(driver, 5).until(lambda _: len(log_entries2) == 2) + assert len(log_entries1) == 1 + finally: + driver.script.remove_javascript_error_handler(id1) + driver.script.remove_javascript_error_handler(id2) def test_add_preload_script(driver, pages): @@ -864,7 +889,6 @@ def test_execute_script_with_exception(driver, pages): """Test executing script that throws an exception.""" pages.load("blank.html") - with pytest.raises(WebDriverException) as exc_info: driver.script.execute( """() => { @@ -1066,12 +1090,12 @@ def test_multiple_preload_scripts(self, driver, pages): result1 = driver.script._evaluate( "window.test1", {"context": driver.current_window_handle}, - await_promise=False + await_promise=False, ) result2 = driver.script._evaluate( "window.test2", {"context": driver.current_window_handle}, - await_promise=False + await_promise=False, ) assert result1.result["value"] == "loaded" @@ -1091,7 +1115,7 @@ def test_preload_script_with_function(self, driver, pages): result = driver.script._evaluate( "window.customFunc(5)", {"context": driver.current_window_handle}, - await_promise=False + await_promise=False, ) assert result.result["value"] == 10 finally: @@ -1099,14 +1123,16 @@ def test_preload_script_with_function(self, driver, pages): def test_preload_script_removal_prevents_execution(self, driver, pages): """Test that removing preload script prevents its execution.""" - script_id = driver.script._add_preload_script("() => { window.shouldNotExist = true; }") + script_id = driver.script._add_preload_script( + "() => { window.shouldNotExist = true; }" + ) driver.script._remove_preload_script(script_id=script_id) pages.load("blank.html") result = driver.script._evaluate( "typeof window.shouldNotExist", {"context": driver.current_window_handle}, - await_promise=False + await_promise=False, ) assert result.result["value"] == "undefined" @@ -1182,7 +1208,7 @@ def test_script_can_access_dom_elements(self, driver, pages): def test_script_context_with_console_handler(self, driver, pages): """Test script execution with console message handler active.""" log_entries = [] - driver.script.add_console_message_handler(log_entries.append) + handler_id = driver.script.add_console_message_handler(log_entries.append) try: pages.load("bidi/logEntryAdded.html") @@ -1192,27 +1218,23 @@ def test_script_context_with_console_handler(self, driver, pages): WebDriverWait(driver, 3).until(lambda _: log_entries) assert len(log_entries) > 0 finally: - # Clean up handler - if log_entries: - # Handler was registered and used - pass + driver.script.remove_console_message_handler(handler_id) def test_script_error_handler_active(self, driver, pages): """Test script execution with error handler active.""" errors = [] - driver.script.add_javascript_error_handler(errors.append) + handler_id = driver.script.add_javascript_error_handler(errors.append) try: pages.load("bidi/logEntryAdded.html") # Click element that triggers JS error driver.find_element(By.ID, "jsException").click() - + # Give time for error handler to capture WebDriverWait(driver, 5).until(lambda _: errors) assert len(errors) > 0 finally: - # Handler cleanup happens automatically via fixture - pass + driver.script.remove_javascript_error_handler(handler_id) class TestBidiScriptComplexOperations: @@ -1304,7 +1326,7 @@ def test_script_error_handler_captures_errors(self, driver, pages): def error_handler(entry): errors.append(entry) - driver.script.add_javascript_error_handler(error_handler) + handler_id = driver.script.add_javascript_error_handler(error_handler) try: pages.load("bidi/logEntryAdded.html") @@ -1313,33 +1335,34 @@ def error_handler(entry): WebDriverWait(driver, 5).until(lambda _: errors) assert len(errors) > 0 finally: - # Handler removal happens automatically - pass + driver.script.remove_javascript_error_handler(handler_id) def test_multiple_error_handlers(self, driver, pages): """Test multiple error handlers can be registered.""" errors1 = [] errors2 = [] - driver.script.add_javascript_error_handler(errors1.append) - driver.script.add_javascript_error_handler(errors2.append) + handler_id1 = driver.script.add_javascript_error_handler(errors1.append) + handler_id2 = driver.script.add_javascript_error_handler(errors2.append) try: pages.load("bidi/logEntryAdded.html") driver.find_element(By.ID, "jsException").click() # Both handlers should receive events when error occurs - WebDriverWait(driver, 5).until(lambda _: len(errors1) > 0) + WebDriverWait(driver, 5).until( + lambda _: len(errors1) > 0 and len(errors2) > 0 + ) assert len(errors1) > 0 assert len(errors2) > 0 finally: - # Handler cleanup happens automatically - pass + driver.script.remove_javascript_error_handler(handler_id1) + driver.script.remove_javascript_error_handler(handler_id2) def test_console_message_with_logging(self, driver, pages): """Test console message handler with actual logging.""" log_entries = [] - driver.script.add_console_message_handler(log_entries.append) + handler_id = driver.script.add_console_message_handler(log_entries.append) try: pages.load("bidi/logEntryAdded.html") @@ -1348,12 +1371,10 @@ def test_console_message_with_logging(self, driver, pages): WebDriverWait(driver, 5).until(lambda _: log_entries) assert len(log_entries) > 0 finally: - # Handler cleanup - pass + driver.script.remove_console_message_handler(handler_id) def test_execute_script_syntax_error(self, driver): """Test executing script with syntax errors.""" # This should raise an exception with pytest.raises(Exception): driver.execute_script("{{invalid syntax}}") - From 75fe4325eefec617f752cd59cd6681a4e98b3984 Mon Sep 17 00:00:00 2001 From: AutomatedTester Date: Tue, 10 Mar 2026 12:14:32 +0000 Subject: [PATCH 10/10] fix comments --- .../webdriver/common/bidi_script_tests.py | 30 +++++++++++-------- .../webdriver/common/bidi_storage_tests.py | 22 ++++++++------ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_script_tests.py b/py/test/selenium/webdriver/common/bidi_script_tests.py index dfc91dc92ba1e..5d5fc6ef9b780 100644 --- a/py/test/selenium/webdriver/common/bidi_script_tests.py +++ b/py/test/selenium/webdriver/common/bidi_script_tests.py @@ -238,26 +238,32 @@ def test_add_preload_script_with_contexts(driver, pages): def test_add_preload_script_with_user_contexts(driver, pages): """Test adding a preload script with user contexts.""" function_declaration = "() => { window.contextSpecific = true; }" + original_handle = driver.current_window_handle user_context = driver.browser.create_user_context() context1 = driver.browsing_context.create(type="window", user_context=user_context) driver.switch_to.window(context1) - user_contexts = [user_context] + try: + user_contexts = [user_context] - script_id = driver.script._add_preload_script( - function_declaration, user_contexts=user_contexts - ) - assert script_id is not None + script_id = driver.script._add_preload_script( + function_declaration, user_contexts=user_contexts + ) + assert script_id is not None - pages.load("blank.html") + pages.load("blank.html") - result = driver.script._evaluate( - "window.contextSpecific", - {"context": driver.current_window_handle}, - await_promise=False, - ) - assert result.result["value"] is True + result = driver.script._evaluate( + "window.contextSpecific", + {"context": driver.current_window_handle}, + await_promise=False, + ) + assert result.result["value"] is True + finally: + driver.switch_to.window(original_handle) + driver.browsing_context.close(context1) + driver.browser.remove_user_context(user_context) def test_add_preload_script_with_sandbox(driver, pages): diff --git a/py/test/selenium/webdriver/common/bidi_storage_tests.py b/py/test/selenium/webdriver/common/bidi_storage_tests.py index 86d219b83a03a..157df78dd3cd9 100644 --- a/py/test/selenium/webdriver/common/bidi_storage_tests.py +++ b/py/test/selenium/webdriver/common/bidi_storage_tests.py @@ -411,22 +411,26 @@ def test_delete_cookies_multiple_filters(self, driver, pages, webserver): """Test deleting cookies with multiple filter criteria.""" assert_no_cookies_are_present(driver) - key1 = "http_only_delete_test" - key2 = "normal_delete_test" + key = "multi_filter_delete_test" value = BytesValue(BytesValue.TYPE_STRING, "test_value") - cookie1 = PartialCookie(key1, value, webserver.host, http_only=True) - cookie2 = PartialCookie(key2, value, webserver.host, http_only=False) + # Create two cookies with same name but different http_only attributes + # This ensures the http_only filter actually affects which cookies are deleted + cookie1 = PartialCookie(key, value, webserver.host, http_only=True) + cookie2 = PartialCookie(key, value, webserver.host, http_only=False) driver.storage.set_cookie(cookie=cookie1) driver.storage.set_cookie(cookie=cookie2) - # Delete only http_only cookies - driver.storage.delete_cookies(filter=CookieFilter(name=key1, http_only=True)) + # Delete only http_only cookies - the http_only filter should actually matter here + driver.storage.delete_cookies(filter=CookieFilter(name=key, http_only=True)) - # Verify - assert_cookie_is_not_present_with_name(driver, key1) - assert_cookie_is_present_with_name(driver, key2) + # Verify - only the http_only=True cookie should be deleted + result = driver.storage.get_cookies(filter=CookieFilter(name=key)) + + # Should have one cookie remaining (the http_only=False one) + assert len(result.cookies) == 1 + assert result.cookies[0].http_only is False def test_delete_cookies_empty_filter(self, driver, pages, webserver): """Test deleting with empty filter deletes all cookies."""