From 4eeef6bdee0f3ef74c545ca0c7d45e1b9d27d1a0 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 29 May 2025 23:09:16 +0530 Subject: [PATCH 1/6] implement bidi emulation module --- .../webdriver/common/bidi/emulation.py | 170 ++++++++++++++++++ py/selenium/webdriver/remote/webdriver.py | 24 +++ 2 files changed, 194 insertions(+) create mode 100644 py/selenium/webdriver/common/bidi/emulation.py diff --git a/py/selenium/webdriver/common/bidi/emulation.py b/py/selenium/webdriver/common/bidi/emulation.py new file mode 100644 index 0000000000000..93b62b40760da --- /dev/null +++ b/py/selenium/webdriver/common/bidi/emulation.py @@ -0,0 +1,170 @@ +# 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. + +from typing import Dict, List, Optional + +from selenium.webdriver.common.bidi.common import command_builder + + +class GeolocationCoordinates: + """Represents geolocation coordinates.""" + + def __init__( + self, + latitude: float, + longitude: float, + accuracy: float = 1.0, + altitude: Optional[float] = None, + altitude_accuracy: Optional[float] = None, + heading: Optional[float] = None, + speed: Optional[float] = None, + ): + """Initialize GeolocationCoordinates. + + Parameters: + ----------- + latitude: Latitude coordinate (-90.0 to 90.0). + longitude: Longitude coordinate (-180.0 to 180.0). + accuracy: Accuracy in meters (>= 0.0), defaults to 1.0. + altitude: Altitude in meters or None, defaults to None. + altitude_accuracy: Altitude accuracy in meters (>= 0.0) or None, defaults to None. + heading: Heading in degrees (0.0 to 360.0) or None, defaults to None. + speed: Speed in meters per second (>= 0.0) or None, defaults to None. + + Raises: + ------ + ValueError: If coordinates are out of valid range or if altitude_accuracy is provided without altitude. + """ + if not (-90.0 <= latitude <= 90.0): + raise ValueError("Latitude must be between -90.0 and 90.0") + if not (-180.0 <= longitude <= 180.0): + raise ValueError("Longitude must be between -180.0 and 180.0") + if accuracy < 0.0: + raise ValueError("Accuracy must be >= 0.0") + if altitude_accuracy is not None and altitude is None: + raise ValueError("altitude_accuracy cannot be set without altitude") + if altitude_accuracy is not None and altitude_accuracy < 0.0: + raise ValueError("Altitude accuracy must be >= 0.0") + if heading is not None and not (0.0 <= heading < 360.0): + raise ValueError("Heading must be between 0.0 and 360.0") + if speed is not None and speed < 0.0: + raise ValueError("Speed must be >= 0.0") + + self.latitude = latitude + self.longitude = longitude + self.accuracy = accuracy + self.altitude = altitude + self.altitude_accuracy = altitude_accuracy + self.heading = heading + self.speed = speed + + def to_dict(self) -> Dict: + result = { + "latitude": self.latitude, + "longitude": self.longitude, + "accuracy": self.accuracy, + } + + if self.altitude is not None: + result["altitude"] = self.altitude + else: + result["altitude"] = None + + if self.altitude_accuracy is not None: + result["altitudeAccuracy"] = self.altitude_accuracy + else: + result["altitudeAccuracy"] = None + + if self.heading is not None: + result["heading"] = self.heading + else: + result["heading"] = None + + if self.speed is not None: + result["speed"] = self.speed + else: + result["speed"] = None + + return result + + +class GeolocationPositionError: + """Represents a geolocation position error.""" + + TYPE_POSITION_UNAVAILABLE = "positionUnavailable" + + def __init__(self, type: str = TYPE_POSITION_UNAVAILABLE): + if type != self.TYPE_POSITION_UNAVAILABLE: + raise ValueError(f'type must be "{self.TYPE_POSITION_UNAVAILABLE}"') + self.type = type + + def to_dict(self) -> Dict: + return {"type": self.type} + + +class Emulation: + """ + BiDi implementation of the emulation module. + """ + + def __init__(self, conn): + self.conn = conn + + def set_geolocation_override( + self, + coordinates: Optional[GeolocationCoordinates] = None, + error: Optional[GeolocationPositionError] = None, + contexts: Optional[List[str]] = None, + user_contexts: Optional[List[str]] = None, + ) -> None: + """Set geolocation override for the given contexts or user contexts. + + Parameters: + ----------- + coordinates: Geolocation coordinates to emulate, or None. + error: Geolocation error to emulate, or None. + contexts: List of browsing context IDs to apply the override to. + user_contexts: List of user context IDs to apply the override to. + + Raises: + ------ + ValueError: If both coordinates and error are provided, or if both contexts + and user_contexts are provided, or if neither contexts nor + user_contexts are provided. + """ + if coordinates is not None and error is not None: + raise ValueError("Cannot specify both coordinates and error") + + if contexts is not None and user_contexts is not None: + raise ValueError("Cannot specify both contexts and userContexts") + + if contexts is None and user_contexts is None: + raise ValueError("Must specify either contexts or userContexts") + + params = {} + + if coordinates is not None: + params["coordinates"] = coordinates.to_dict() + elif error is not None: + params["error"] = error.to_dict() + + if contexts is not None: + params["contexts"] = contexts + elif user_contexts is not None: + params["userContexts"] = user_contexts + + self.conn.execute(command_builder("emulation.setGeolocationOverride", params)) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 149f12d8fe1a0..22c2590f3981b 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -41,6 +41,7 @@ ) from selenium.webdriver.common.bidi.browser import Browser from selenium.webdriver.common.bidi.browsing_context import BrowsingContext +from selenium.webdriver.common.bidi.emulation import Emulation from selenium.webdriver.common.bidi.network import Network from selenium.webdriver.common.bidi.script import Script from selenium.webdriver.common.bidi.session import Session @@ -265,6 +266,7 @@ def __init__( self._browsing_context = None self._storage = None self._webextension = None + self._emulation = None def __repr__(self): return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>' @@ -1361,6 +1363,28 @@ def webextension(self): return self._webextension + @property + def emulation(self): + """Returns an emulation module object for BiDi emulation commands. + + Returns: + -------- + Emulation: an object containing access to BiDi emulation commands. + + Examples: + --------- + >>> from selenium.webdriver.common.bidi.emulation import GeolocationCoordinates + >>> coordinates = GeolocationCoordinates(37.7749, -122.4194) + >>> driver.emulation.set_geolocation_override(coordinates=coordinates, contexts=[context_id]) + """ + if not self._websocket_connection: + self._start_bidi() + + if self._emulation is None: + self._emulation = Emulation(self._websocket_connection) + + return self._emulation + def _get_cdp_details(self): import json From caf71365544a4463dacf3f77b625493be0d57955 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 29 May 2025 23:09:27 +0530 Subject: [PATCH 2/6] add tests --- .../webdriver/common/bidi_emulation_tests.py | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 py/test/selenium/webdriver/common/bidi_emulation_tests.py diff --git a/py/test/selenium/webdriver/common/bidi_emulation_tests.py b/py/test/selenium/webdriver/common/bidi_emulation_tests.py new file mode 100644 index 0000000000000..0469fd566a225 --- /dev/null +++ b/py/test/selenium/webdriver/common/bidi_emulation_tests.py @@ -0,0 +1,218 @@ +# 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.bidi.emulation import Emulation, GeolocationCoordinates +from selenium.webdriver.common.window import WindowTypes + + +def get_browser_geolocation(driver): + # TODO: use the permissions module to grant geolocation permission when its implemented + # currently it needs to be granted manually in the browser GUI window, so CI will fail + + return driver.execute_async_script(""" + const callback = arguments[arguments.length - 1]; + navigator.geolocation.getCurrentPosition( + position => { + const coords = position.coords; + callback({ + latitude: coords.latitude, + longitude: coords.longitude, + accuracy: coords.accuracy, + altitude: coords.altitude, + altitudeAccuracy: coords.altitudeAccuracy, + heading: coords.heading, + speed: coords.speed, + timestamp: position.timestamp + }); + }, + error => { + callback({ error: error.message }); + } + ); + """) + + +def test_emulation_initialized(driver): + """Test that the emulation module is initialized properly.""" + assert driver.emulation is not None + assert isinstance(driver.emulation, Emulation) + + +@pytest.mark.xfail_edge +def test_set_geolocation_override_with_coordinates_in_context(driver, pages): + """Test setting geolocation override with coordinates.""" + context_id = driver.current_window_handle + pages.load("blank.html") + coords = GeolocationCoordinates(45.5, -122.4194, accuracy=10.0) + + driver.emulation.set_geolocation_override(coordinates=coords, contexts=[context_id]) + + result = get_browser_geolocation(driver) + + assert "error" not in result, f"Geolocation error: {result.get('error')}" + assert abs(result["latitude"] - coords.latitude) < 0.0001, f"Latitude mismatch: {result['latitude']}" + assert abs(result["longitude"] - coords.longitude) < 0.0001, f"Longitude mismatch: {result['longitude']}" + assert abs(result["accuracy"] - coords.accuracy) < 1.0, f"Accuracy mismatch: {result['accuracy']}" + + +@pytest.mark.xfail_edge +def test_set_geolocation_override_with_coordinates_in_user_context(driver, pages): + """Test setting geolocation override with coordinates in a user context.""" + # Create a user context + user_context = driver.browser.create_user_context() + + context_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context) + + driver.switch_to.window(context_id) + pages.load("blank.html") + + coords = GeolocationCoordinates(45.5, -122.4194, accuracy=10.0) + + driver.emulation.set_geolocation_override(coordinates=coords, user_contexts=[user_context]) + + result = get_browser_geolocation(driver) + + assert "error" not in result, f"Geolocation error: {result.get('error')}" + assert abs(result["latitude"] - coords.latitude) < 0.0001, f"Latitude mismatch: {result['latitude']}" + assert abs(result["longitude"] - coords.longitude) < 0.0001, f"Longitude mismatch: {result['longitude']}" + assert abs(result["accuracy"] - coords.accuracy) < 1.0, f"Accuracy mismatch: {result['accuracy']}" + + driver.browsing_context.close(context_id) + driver.browser.remove_user_context(user_context) + + +@pytest.mark.xfail_edge +def test_set_geolocation_override_all_coords(driver, pages): + """Test setting geolocation override with coordinates.""" + context_id = driver.current_window_handle + pages.load("blank.html") + coords = GeolocationCoordinates( + 45.5, -122.4194, accuracy=10.0, altitude=100.2, altitude_accuracy=5.0, heading=183.2, speed=10.0 + ) + + driver.emulation.set_geolocation_override(coordinates=coords, contexts=[context_id]) + + result = get_browser_geolocation(driver) + + assert "error" not in result, f"Geolocation error: {result.get('error')}" + assert abs(result["latitude"] - coords.latitude) < 0.0001, f"Latitude mismatch: {result['latitude']}" + assert abs(result["longitude"] - coords.longitude) < 0.0001, f"Longitude mismatch: {result['longitude']}" + assert abs(result["accuracy"] - coords.accuracy) < 1.0, f"Accuracy mismatch: {result['accuracy']}" + assert abs(result["altitude"] - coords.altitude) < 0.0001, f"Altitude mismatch: {result['altitude']}" + assert abs(result["altitudeAccuracy"] - coords.altitude_accuracy) < 0.1, ( + f"Altitude accuracy mismatch: {result['altitudeAccuracy']}" + ) + assert abs(result["heading"] - coords.heading) < 0.1, f"Heading mismatch: {result['heading']}" + assert abs(result["speed"] - coords.speed) < 0.1, f"Speed mismatch: {result['speed']}" + + driver.browsing_context.close(context_id) + + +@pytest.mark.xfail_edge +def test_set_geolocation_override_with_multiple_contexts(driver, pages): + """Test setting geolocation override with multiple browsing contexts.""" + # Create two browsing contexts + context1_id = driver.browsing_context.create(type=WindowTypes.TAB) + context2_id = driver.browsing_context.create(type=WindowTypes.TAB) + + coords = GeolocationCoordinates(45.5, -122.4194, accuracy=10.0) + + driver.emulation.set_geolocation_override(coordinates=coords, contexts=[context1_id, context2_id]) + + # Test first context + driver.switch_to.window(context1_id) + pages.load("blank.html") + result1 = get_browser_geolocation(driver) + + assert "error" not in result1, f"Geolocation error in context1: {result1.get('error')}" + assert abs(result1["latitude"] - coords.latitude) < 0.0001, f"Context1 latitude mismatch: {result1['latitude']}" + assert abs(result1["longitude"] - coords.longitude) < 0.0001, f"Context1 longitude mismatch: {result1['longitude']}" + assert abs(result1["accuracy"] - coords.accuracy) < 1.0, f"Context1 accuracy mismatch: {result1['accuracy']}" + + # Test second context + driver.switch_to.window(context2_id) + pages.load("blank.html") + result2 = get_browser_geolocation(driver) + + assert "error" not in result2, f"Geolocation error in context2: {result2.get('error')}" + assert abs(result2["latitude"] - coords.latitude) < 0.0001, f"Context2 latitude mismatch: {result2['latitude']}" + assert abs(result2["longitude"] - coords.longitude) < 0.0001, f"Context2 longitude mismatch: {result2['longitude']}" + assert abs(result2["accuracy"] - coords.accuracy) < 1.0, f"Context2 accuracy mismatch: {result2['accuracy']}" + + driver.browsing_context.close(context1_id) + driver.browsing_context.close(context2_id) + + +@pytest.mark.xfail_edge +def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): + """Test setting geolocation override with multiple user contexts.""" + # Create two user contexts + user_context1 = driver.browser.create_user_context() + user_context2 = driver.browser.create_user_context() + + context1_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context1) + context2_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context2) + + coords = GeolocationCoordinates(45.5, -122.4194, accuracy=10.0) + + driver.emulation.set_geolocation_override(coordinates=coords, user_contexts=[user_context1, user_context2]) + + # Test first user context + driver.switch_to.window(context1_id) + pages.load("blank.html") + result1 = get_browser_geolocation(driver) + + assert "error" not in result1, f"Geolocation error in user_context1: {result1.get('error')}" + assert abs(result1["latitude"] - coords.latitude) < 0.0001, ( + f"User context1 latitude mismatch: {result1['latitude']}" + ) + assert abs(result1["longitude"] - coords.longitude) < 0.0001, ( + f"User context1 longitude mismatch: {result1['longitude']}" + ) + assert abs(result1["accuracy"] - coords.accuracy) < 1.0, f"User context1 accuracy mismatch: {result1['accuracy']}" + + # Test second user context + driver.switch_to.window(context2_id) + pages.load("blank.html") + result2 = get_browser_geolocation(driver) + + assert "error" not in result2, f"Geolocation error in user_context2: {result2.get('error')}" + assert abs(result2["latitude"] - coords.latitude) < 0.0001, ( + f"User context2 latitude mismatch: {result2['latitude']}" + ) + assert abs(result2["longitude"] - coords.longitude) < 0.0001, ( + f"User context2 longitude mismatch: {result2['longitude']}" + ) + assert abs(result2["accuracy"] - coords.accuracy) < 1.0, f"User context2 accuracy mismatch: {result2['accuracy']}" + + driver.browsing_context.close(context1_id) + driver.browsing_context.close(context2_id) + driver.browser.remove_user_context(user_context1) + driver.browser.remove_user_context(user_context2) + + +# the error param returns "invalid argument: Invalid input in "coordinates" error, might be bug from the spec/browser +# @pytest.mark.xfail_edge +# def test_set_geolocation_override_with_error(driver): +# """Test setting geolocation override with error.""" +# context_id = driver.current_window_handle +# +# error = GeolocationPositionError() +# +# driver.emulation.set_geolocation_override(error=error, contexts=[context_id]) From 297ef0b360acaeef3b46ddaac1b8789f2fb6eb22 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Fri, 30 May 2025 13:02:22 +0530 Subject: [PATCH 3/6] edge supports emulation from version 137 --- py/test/selenium/webdriver/common/bidi_emulation_tests.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_emulation_tests.py b/py/test/selenium/webdriver/common/bidi_emulation_tests.py index 0469fd566a225..020e99654d3a0 100644 --- a/py/test/selenium/webdriver/common/bidi_emulation_tests.py +++ b/py/test/selenium/webdriver/common/bidi_emulation_tests.py @@ -54,7 +54,6 @@ def test_emulation_initialized(driver): assert isinstance(driver.emulation, Emulation) -@pytest.mark.xfail_edge def test_set_geolocation_override_with_coordinates_in_context(driver, pages): """Test setting geolocation override with coordinates.""" context_id = driver.current_window_handle @@ -71,7 +70,6 @@ def test_set_geolocation_override_with_coordinates_in_context(driver, pages): assert abs(result["accuracy"] - coords.accuracy) < 1.0, f"Accuracy mismatch: {result['accuracy']}" -@pytest.mark.xfail_edge def test_set_geolocation_override_with_coordinates_in_user_context(driver, pages): """Test setting geolocation override with coordinates in a user context.""" # Create a user context @@ -97,7 +95,6 @@ def test_set_geolocation_override_with_coordinates_in_user_context(driver, pages driver.browser.remove_user_context(user_context) -@pytest.mark.xfail_edge def test_set_geolocation_override_all_coords(driver, pages): """Test setting geolocation override with coordinates.""" context_id = driver.current_window_handle @@ -124,7 +121,6 @@ def test_set_geolocation_override_all_coords(driver, pages): driver.browsing_context.close(context_id) -@pytest.mark.xfail_edge def test_set_geolocation_override_with_multiple_contexts(driver, pages): """Test setting geolocation override with multiple browsing contexts.""" # Create two browsing contexts @@ -159,7 +155,6 @@ def test_set_geolocation_override_with_multiple_contexts(driver, pages): driver.browsing_context.close(context2_id) -@pytest.mark.xfail_edge def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): """Test setting geolocation override with multiple user contexts.""" # Create two user contexts From d9ab207b618b45917cdef19629cbc936a844c557 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Wed, 4 Jun 2025 21:20:23 +0530 Subject: [PATCH 4/6] set geolocation permission --- .../webdriver/common/bidi_emulation_tests.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_emulation_tests.py b/py/test/selenium/webdriver/common/bidi_emulation_tests.py index 020e99654d3a0..6d0193a3c4ed4 100644 --- a/py/test/selenium/webdriver/common/bidi_emulation_tests.py +++ b/py/test/selenium/webdriver/common/bidi_emulation_tests.py @@ -15,15 +15,15 @@ # specific language governing permissions and limitations # under the License. -import pytest - from selenium.webdriver.common.bidi.emulation import Emulation, GeolocationCoordinates +from selenium.webdriver.common.bidi.permissions import PermissionState from selenium.webdriver.common.window import WindowTypes -def get_browser_geolocation(driver): - # TODO: use the permissions module to grant geolocation permission when its implemented - # currently it needs to be granted manually in the browser GUI window, so CI will fail +def get_browser_geolocation(driver, user_context=None): + + origin = driver.execute_script("return window.location.origin;") + driver.permissions.set_permission("geolocation", PermissionState.GRANTED, origin, user_context=user_context) return driver.execute_async_script(""" const callback = arguments[arguments.length - 1]; @@ -84,7 +84,7 @@ def test_set_geolocation_override_with_coordinates_in_user_context(driver, pages driver.emulation.set_geolocation_override(coordinates=coords, user_contexts=[user_context]) - result = get_browser_geolocation(driver) + result = get_browser_geolocation(driver, user_context=user_context) assert "error" not in result, f"Geolocation error: {result.get('error')}" assert abs(result["latitude"] - coords.latitude) < 0.0001, f"Latitude mismatch: {result['latitude']}" @@ -171,7 +171,7 @@ def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): # Test first user context driver.switch_to.window(context1_id) pages.load("blank.html") - result1 = get_browser_geolocation(driver) + result1 = get_browser_geolocation(driver, user_context=user_context1) assert "error" not in result1, f"Geolocation error in user_context1: {result1.get('error')}" assert abs(result1["latitude"] - coords.latitude) < 0.0001, ( @@ -185,7 +185,7 @@ def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): # Test second user context driver.switch_to.window(context2_id) pages.load("blank.html") - result2 = get_browser_geolocation(driver) + result2 = get_browser_geolocation(driver, user_context=user_context2) assert "error" not in result2, f"Geolocation error in user_context2: {result2.get('error')}" assert abs(result2["latitude"] - coords.latitude) < 0.0001, ( @@ -202,8 +202,8 @@ def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): driver.browser.remove_user_context(user_context2) -# the error param returns "invalid argument: Invalid input in "coordinates" error, might be bug from the spec/browser -# @pytest.mark.xfail_edge +# the error param returns "invalid argument: Invalid input in "coordinates" error, Chrome 138 fixes this +# @pytest.mark.xfail_firefox # def test_set_geolocation_override_with_error(driver): # """Test setting geolocation override with error.""" # context_id = driver.current_window_handle @@ -211,3 +211,5 @@ def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): # error = GeolocationPositionError() # # driver.emulation.set_geolocation_override(error=error, contexts=[context_id]) +# +# # assert "error" after inspecting the get_browser_geolocation script's response From e75e23eb06556a9acb559760e4955092909bbac0 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Wed, 4 Jun 2025 21:37:39 +0530 Subject: [PATCH 5/6] resolve mypy type errors --- py/selenium/webdriver/common/bidi/emulation.py | 18 +++++------------- .../webdriver/common/bidi_emulation_tests.py | 1 - 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/py/selenium/webdriver/common/bidi/emulation.py b/py/selenium/webdriver/common/bidi/emulation.py index 93b62b40760da..45c91f5ec0b2b 100644 --- a/py/selenium/webdriver/common/bidi/emulation.py +++ b/py/selenium/webdriver/common/bidi/emulation.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -from typing import Dict, List, Optional +from typing import Any, Dict, List, Optional, Union from selenium.webdriver.common.bidi.common import command_builder @@ -72,8 +72,8 @@ def __init__( self.heading = heading self.speed = speed - def to_dict(self) -> Dict: - result = { + def to_dict(self) -> Dict[str, Union[float, None]]: + result: Dict[str, Union[float, None]] = { "latitude": self.latitude, "longitude": self.longitude, "accuracy": self.accuracy, @@ -81,23 +81,15 @@ def to_dict(self) -> Dict: if self.altitude is not None: result["altitude"] = self.altitude - else: - result["altitude"] = None if self.altitude_accuracy is not None: result["altitudeAccuracy"] = self.altitude_accuracy - else: - result["altitudeAccuracy"] = None if self.heading is not None: result["heading"] = self.heading - else: - result["heading"] = None if self.speed is not None: result["speed"] = self.speed - else: - result["speed"] = None return result @@ -112,7 +104,7 @@ def __init__(self, type: str = TYPE_POSITION_UNAVAILABLE): raise ValueError(f'type must be "{self.TYPE_POSITION_UNAVAILABLE}"') self.type = type - def to_dict(self) -> Dict: + def to_dict(self) -> Dict[str, str]: return {"type": self.type} @@ -155,7 +147,7 @@ def set_geolocation_override( if contexts is None and user_contexts is None: raise ValueError("Must specify either contexts or userContexts") - params = {} + params: Dict[str, Any] = {} if coordinates is not None: params["coordinates"] = coordinates.to_dict() diff --git a/py/test/selenium/webdriver/common/bidi_emulation_tests.py b/py/test/selenium/webdriver/common/bidi_emulation_tests.py index 6d0193a3c4ed4..dc7a704131412 100644 --- a/py/test/selenium/webdriver/common/bidi_emulation_tests.py +++ b/py/test/selenium/webdriver/common/bidi_emulation_tests.py @@ -21,7 +21,6 @@ def get_browser_geolocation(driver, user_context=None): - origin = driver.execute_script("return window.location.origin;") driver.permissions.set_permission("geolocation", PermissionState.GRANTED, origin, user_context=user_context) From a6c1bd2a05b43772685a7884cdd32ab809472388 Mon Sep 17 00:00:00 2001 From: Navin Chandra Date: Thu, 26 Jun 2025 17:09:09 +0530 Subject: [PATCH 6/6] add test for `error` param --- .../webdriver/common/bidi_emulation_tests.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/py/test/selenium/webdriver/common/bidi_emulation_tests.py b/py/test/selenium/webdriver/common/bidi_emulation_tests.py index dc7a704131412..05e2343e8d432 100644 --- a/py/test/selenium/webdriver/common/bidi_emulation_tests.py +++ b/py/test/selenium/webdriver/common/bidi_emulation_tests.py @@ -14,8 +14,9 @@ # 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.bidi.emulation import Emulation, GeolocationCoordinates +from selenium.webdriver.common.bidi.emulation import Emulation, GeolocationCoordinates, GeolocationPositionError from selenium.webdriver.common.bidi.permissions import PermissionState from selenium.webdriver.common.window import WindowTypes @@ -201,14 +202,16 @@ def test_set_geolocation_override_with_multiple_user_contexts(driver, pages): driver.browser.remove_user_context(user_context2) -# the error param returns "invalid argument: Invalid input in "coordinates" error, Chrome 138 fixes this -# @pytest.mark.xfail_firefox -# def test_set_geolocation_override_with_error(driver): -# """Test setting geolocation override with error.""" -# context_id = driver.current_window_handle -# -# error = GeolocationPositionError() -# -# driver.emulation.set_geolocation_override(error=error, contexts=[context_id]) -# -# # assert "error" after inspecting the get_browser_geolocation script's response +@pytest.mark.xfail_firefox +@pytest.mark.xfail_edge +def test_set_geolocation_override_with_error(driver, pages): + """Test setting geolocation override with error.""" + context_id = driver.current_window_handle + pages.load("blank.html") + + error = GeolocationPositionError() + + driver.emulation.set_geolocation_override(error=error, contexts=[context_id]) + + result = get_browser_geolocation(driver) + assert "error" in result, f"Expected geolocation error, got: {result}"