From 7caac8716d1333199ad51dd9a8e47242004b7341 Mon Sep 17 00:00:00 2001 From: Shaurya Bisht <87357655+ShauryaDusht@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:49:58 +0530 Subject: [PATCH 1/3] Fix : RelativeBy Annotations --- py/selenium/webdriver/remote/shadowroot.py | 9 ++- py/selenium/webdriver/remote/webdriver.py | 37 ++--------- py/selenium/webdriver/remote/webelement.py | 11 +++- .../webdriver/support/expected_conditions.py | 28 ++++---- .../test_expected_conditions_relative_by.py | 57 ++++++++++++++++ .../webdriver/test_relative_by_annotations.py | 65 +++++++++++++++++++ 6 files changed, 159 insertions(+), 48 deletions(-) create mode 100644 py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py create mode 100644 py/test/unit/selenium/webdriver/test_relative_by_annotations.py diff --git a/py/selenium/webdriver/remote/shadowroot.py b/py/selenium/webdriver/remote/shadowroot.py index 2d81f17e01426..e3603797e838c 100644 --- a/py/selenium/webdriver/remote/shadowroot.py +++ b/py/selenium/webdriver/remote/shadowroot.py @@ -16,8 +16,11 @@ # under the License. from hashlib import md5 as md5_hash +from typing import Union, TYPE_CHECKING +if TYPE_CHECKING: + from selenium.webdriver.support.relative_locator import RelativeBy -from ..common.by import By +from ..common.by import By, ByType from .command import Command @@ -43,7 +46,7 @@ def __repr__(self) -> str: def id(self) -> str: return self._id - def find_element(self, by: str = By.ID, value: str = None): + def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value: str = None): """Find an element inside a shadow root given a By strategy and locator. @@ -82,7 +85,7 @@ def find_element(self, by: str = By.ID, value: str = None): return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"] - def find_elements(self, by: str = By.ID, value: str = None): + def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value: str = None): """Find elements inside a shadow root given a By strategy and locator. Parameters: diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 07627a488a10e..8ad8a182c8a99 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -30,7 +30,10 @@ from base64 import b64decode, urlsafe_b64encode from contextlib import asynccontextmanager, contextmanager from importlib import import_module -from typing import Any, Optional, Union +from typing import Any, Optional, Union, TYPE_CHECKING + +if TYPE_CHECKING: + from selenium.webdriver.support.relative_locator import RelativeBy from selenium.common.exceptions import ( InvalidArgumentException, @@ -42,12 +45,11 @@ from selenium.webdriver.common.bidi.browser import Browser from selenium.webdriver.common.bidi.browsing_context import BrowsingContext from selenium.webdriver.common.bidi.network import Network -from selenium.webdriver.common.bidi.permissions import Permissions from selenium.webdriver.common.bidi.script import Script from selenium.webdriver.common.bidi.session import Session from selenium.webdriver.common.bidi.storage import Storage from selenium.webdriver.common.bidi.webextension import WebExtension -from selenium.webdriver.common.by import By +from selenium.webdriver.common.by import By, ByType from selenium.webdriver.common.options import ArgOptions, BaseOptions from selenium.webdriver.common.print_page_options import PrintOptions from selenium.webdriver.common.timeouts import Timeouts @@ -56,8 +58,6 @@ VirtualAuthenticatorOptions, required_virtual_authenticator, ) -from selenium.webdriver.support.relative_locator import RelativeBy - from ..common.fedcm.dialog import Dialog from .bidi_connection import BidiConnection from .client_config import ClientConfig @@ -266,7 +266,6 @@ def __init__( self._browsing_context = None self._storage = None self._webextension = None - self._permissions = None def __repr__(self): return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}")>' @@ -879,7 +878,7 @@ def timeouts(self, timeouts) -> None: """ _ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())["value"] - def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement: + def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[str] = None) -> WebElement: """Find an element given a By strategy and locator. Parameters: @@ -915,7 +914,7 @@ def find_element(self, by=By.ID, value: Optional[str] = None) -> WebElement: return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"] - def find_elements(self, by=By.ID, value: Optional[str] = None) -> list[WebElement]: + def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[str] = None) -> list[WebElement]: """Find elements given a By strategy and locator. Parameters: @@ -1341,28 +1340,6 @@ def storage(self): return self._storage - @property - def permissions(self): - """Returns a permissions module object for BiDi permissions commands. - - Returns: - -------- - Permissions: an object containing access to BiDi permissions commands. - - Examples: - --------- - >>> from selenium.webdriver.common.bidi.permissions import PermissionDescriptor, PermissionState - >>> descriptor = PermissionDescriptor("geolocation") - >>> driver.permissions.set_permission(descriptor, PermissionState.GRANTED, "https://example.com") - """ - if not self._websocket_connection: - self._start_bidi() - - if self._permissions is None: - self._permissions = Permissions(self._websocket_connection) - - return self._permissions - @property def webextension(self): """Returns a webextension module object for BiDi webextension commands. diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index 0e5754d82b314..37043653c7a98 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -24,9 +24,14 @@ from base64 import b64decode, encodebytes from hashlib import md5 as md5_hash from io import BytesIO +from typing import Union, TYPE_CHECKING + +if TYPE_CHECKING: + from selenium.webdriver.support.relative_locator import RelativeBy + from selenium.common.exceptions import JavascriptException, WebDriverException -from selenium.webdriver.common.by import By +from selenium.webdriver.common.by import By, ByType from selenium.webdriver.common.utils import keys_to_typing from .command import Command @@ -572,7 +577,7 @@ def _execute(self, command, params=None): params["id"] = self._id return self._parent.execute(command, params) - def find_element(self, by=By.ID, value=None) -> WebElement: + def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value=None) -> WebElement: """Find an element given a By strategy and locator. Parameters: @@ -601,7 +606,7 @@ def find_element(self, by=By.ID, value=None) -> WebElement: by, value = self._parent.locator_converter.convert(by, value) return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"] - def find_elements(self, by=By.ID, value=None) -> list[WebElement]: + def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value=None) -> list[WebElement]: """Find elements given a By strategy and locator. Parameters: diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index 06a5f36d9e7c6..bd4fdf4f7d6c4 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -17,7 +17,10 @@ import re from collections.abc import Iterable -from typing import Any, Callable, Literal, TypeVar, Union +from typing import Any, Callable, Literal, TypeVar, Union, Tuple + +from selenium.webdriver.common.by import ByType +from selenium.webdriver.support.relative_locator import RelativeBy from selenium.common.exceptions import ( NoAlertPresentException, @@ -38,6 +41,7 @@ T = TypeVar("T") WebDriverOrWebElement = Union[WebDriver, WebElement] +LocatorType = Union[Tuple[ByType, str], Tuple[RelativeBy, None]] def title_is(title: str) -> Callable[[WebDriver], bool]: @@ -79,7 +83,7 @@ def _predicate(driver: WebDriver): return _predicate -def presence_of_element_located(locator: tuple[str, str]) -> Callable[[WebDriverOrWebElement], WebElement]: +def presence_of_element_located(locator: LocatorType) -> Callable[[WebDriverOrWebElement], WebElement]: """An expectation for checking that an element is present on the DOM of a page. This does not necessarily mean that the element is visible. @@ -189,7 +193,7 @@ def _predicate(driver: WebDriver): def visibility_of_element_located( - locator: tuple[str, str], + locator: LocatorType, ) -> Callable[[WebDriverOrWebElement], Union[Literal[False], WebElement]]: """An expectation for checking that an element is present on the DOM of a page and visible. Visibility means that the element is not only displayed @@ -272,7 +276,7 @@ def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[L return element if element.is_displayed() == visibility else False -def presence_of_all_elements_located(locator: tuple[str, str]) -> Callable[[WebDriverOrWebElement], list[WebElement]]: +def presence_of_all_elements_located(locator: LocatorType) -> Callable[[WebDriverOrWebElement], list[WebElement]]: """An expectation for checking that there is at least one element present on a web page. @@ -299,7 +303,7 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def visibility_of_any_elements_located(locator: tuple[str, str]) -> Callable[[WebDriverOrWebElement], list[WebElement]]: +def visibility_of_any_elements_located(locator: LocatorType) -> Callable[[WebDriverOrWebElement], list[WebElement]]: """An expectation for checking that there is at least one element visible on a web page. @@ -327,7 +331,7 @@ def _predicate(driver: WebDriverOrWebElement): def visibility_of_all_elements_located( - locator: tuple[str, str], + locator: LocatorType, ) -> Callable[[WebDriverOrWebElement], Union[list[WebElement], Literal[False]]]: """An expectation for checking that all elements are present on the DOM of a page and visible. Visibility means that the elements are not only @@ -363,7 +367,7 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def text_to_be_present_in_element(locator: tuple[str, str], text_: str) -> Callable[[WebDriverOrWebElement], bool]: +def text_to_be_present_in_element(locator: LocatorType, text_: str) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for checking if the given text is present in the specified element. @@ -399,7 +403,7 @@ def _predicate(driver: WebDriverOrWebElement): def text_to_be_present_in_element_value( - locator: tuple[str, str], text_: str + locator: LocatorType, text_: str ) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for checking if the given text is present in the element's value. @@ -436,7 +440,7 @@ def _predicate(driver: WebDriverOrWebElement): def text_to_be_present_in_element_attribute( - locator: tuple[str, str], attribute_: str, text_: str + locator: LocatorType, attribute_: str, text_: str ) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for checking if the given text is present in the element's attribute. @@ -687,7 +691,7 @@ def _predicate(_): return _predicate -def element_located_to_be_selected(locator: tuple[str, str]) -> Callable[[WebDriverOrWebElement], bool]: +def element_located_to_be_selected(locator: LocatorType) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for the element to be located is selected. Parameters: @@ -743,7 +747,7 @@ def _predicate(_): def element_located_selection_state_to_be( - locator: tuple[str, str], is_selected: bool + locator: LocatorType, is_selected: bool ) -> Callable[[WebDriverOrWebElement], bool]: """An expectation to locate an element and check if the selection state specified is in that state. @@ -858,7 +862,7 @@ def _predicate(driver: WebDriver): return _predicate -def element_attribute_to_include(locator: tuple[str, str], attribute_: str) -> Callable[[WebDriverOrWebElement], bool]: +def element_attribute_to_include(locator: LocatorType, attribute_: str) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for checking if the given attribute is included in the specified element. diff --git a/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py b/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py new file mode 100644 index 0000000000000..9a6dd2eabdfde --- /dev/null +++ b/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py @@ -0,0 +1,57 @@ +import pytest +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.relative_locator import locate_with +from selenium.webdriver.common.by import By +from unittest.mock import Mock + + +class TestExpectedConditionsRelativeBy: + """Test that expected conditions accept RelativeBy in type annotations""" + + def test_presence_of_element_located_accepts_relative_by(self): + """Test presence_of_element_located accepts RelativeBy""" + relative_by = locate_with(By.TAG_NAME, "div").above({By.ID: "footer"}) + condition = EC.presence_of_element_located(relative_by) + assert condition is not None + + def test_visibility_of_element_located_accepts_relative_by(self): + """Test visibility_of_element_located accepts RelativeBy""" + relative_by = locate_with(By.TAG_NAME, "button").near({By.CLASS_NAME: "submit"}) + condition = EC.visibility_of_element_located(relative_by) + assert condition is not None + + def test_presence_of_all_elements_located_accepts_relative_by(self): + """Test presence_of_all_elements_located accepts RelativeBy""" + relative_by = locate_with(By.CSS_SELECTOR, ".item").below({By.ID: "header"}) + condition = EC.presence_of_all_elements_located(relative_by) + assert condition is not None + + def test_visibility_of_any_elements_located_accepts_relative_by(self): + """Test visibility_of_any_elements_located accepts RelativeBy""" + relative_by = locate_with(By.TAG_NAME, "span").to_left_of({By.ID: "sidebar"}) + condition = EC.visibility_of_any_elements_located(relative_by) + assert condition is not None + + def test_text_to_be_present_in_element_accepts_relative_by(self): + """Test text_to_be_present_in_element accepts RelativeBy""" + relative_by = locate_with(By.TAG_NAME, "p").above({By.CLASS_NAME: "footer"}) + condition = EC.text_to_be_present_in_element(relative_by, "Hello") + assert condition is not None + + def test_element_to_be_clickable_accepts_relative_by(self): + """Test element_to_be_clickable accepts RelativeBy""" + relative_by = locate_with(By.TAG_NAME, "button").near({By.ID: "form"}) + condition = EC.element_to_be_clickable(relative_by) + assert condition is not None + + def test_invisibility_of_element_located_accepts_relative_by(self): + """Test invisibility_of_element_located accepts RelativeBy""" + relative_by = locate_with(By.CSS_SELECTOR, ".loading").above({By.ID: "content"}) + condition = EC.invisibility_of_element_located(relative_by) + assert condition is not None + + def test_element_located_to_be_selected_accepts_relative_by(self): + """Test element_located_to_be_selected accepts RelativeBy""" + relative_by = locate_with(By.TAG_NAME, "input").near({By.ID: "terms-label"}) + condition = EC.element_located_to_be_selected(relative_by) + assert condition is not None \ No newline at end of file diff --git a/py/test/unit/selenium/webdriver/test_relative_by_annotations.py b/py/test/unit/selenium/webdriver/test_relative_by_annotations.py new file mode 100644 index 0000000000000..0e149e7b86d18 --- /dev/null +++ b/py/test/unit/selenium/webdriver/test_relative_by_annotations.py @@ -0,0 +1,65 @@ +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.support.relative_locator import RelativeBy, locate_with +from selenium.webdriver.remote.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.remote.shadowroot import ShadowRoot +from unittest.mock import Mock, MagicMock + + +class TestRelativeByAnnotations: + """Test that RelativeBy is properly accepted in type annotations""" + + def test_webdriver_find_element_accepts_relative_by(self): + """Test WebDriver.find_element accepts RelativeBy""" + driver = Mock(spec=WebDriver) + relative_by = locate_with(By.TAG_NAME, "div").above({By.ID: "footer"}) + + # This should not raise type checking errors + driver.find_element(by=relative_by) + driver.find_element(relative_by) + + def test_webdriver_find_elements_accepts_relative_by(self): + """Test WebDriver.find_elements accepts RelativeBy""" + driver = Mock(spec=WebDriver) + relative_by = locate_with(By.TAG_NAME, "div").below({By.ID: "header"}) + + # This should not raise type checking errors + driver.find_elements(by=relative_by) + driver.find_elements(relative_by) + + def test_webelement_find_element_accepts_relative_by(self): + """Test WebElement.find_element accepts RelativeBy""" + element = Mock(spec=WebElement) + relative_by = locate_with(By.TAG_NAME, "span").near({By.CLASS_NAME: "button"}) + + # This should not raise type checking errors + element.find_element(by=relative_by) + element.find_element(relative_by) + + def test_webelement_find_elements_accepts_relative_by(self): + """Test WebElement.find_elements accepts RelativeBy""" + element = Mock(spec=WebElement) + relative_by = locate_with(By.TAG_NAME, "input").to_left_of({By.ID: "submit"}) + + # This should not raise type checking errors + element.find_elements(by=relative_by) + element.find_elements(relative_by) + + def test_shadowroot_find_element_accepts_relative_by(self): + """Test ShadowRoot.find_element accepts RelativeBy""" + shadow_root = Mock(spec=ShadowRoot) + relative_by = locate_with(By.TAG_NAME, "button").to_right_of({By.ID: "cancel"}) + + # This should not raise type checking errors + shadow_root.find_element(by=relative_by) + shadow_root.find_element(relative_by) + + def test_shadowroot_find_elements_accepts_relative_by(self): + """Test ShadowRoot.find_elements accepts RelativeBy""" + shadow_root = Mock(spec=ShadowRoot) + relative_by = locate_with(By.CSS_SELECTOR, ".item").above({By.ID: "footer"}) + + # This should not raise type checking errors + shadow_root.find_elements(by=relative_by) + shadow_root.find_elements(relative_by) \ No newline at end of file From 520247086393e6fa283e75c41a7e63a590f641e9 Mon Sep 17 00:00:00 2001 From: Shaurya Bisht <87357655+ShauryaDusht@users.noreply.github.com> Date: Sun, 8 Jun 2025 16:58:52 +0530 Subject: [PATCH 2/3] Added deleted part --- py/selenium/webdriver/remote/webdriver.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 8ad8a182c8a99..47fb68c534b07 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -1340,6 +1340,26 @@ def storage(self): return self._storage + @property + def permissions(self): + """Returns a permissions module object for BiDi permissions commands. + Returns: + -------- + Permissions: an object containing access to BiDi permissions commands. + Examples: + --------- + >>> from selenium.webdriver.common.bidi.permissions import PermissionDescriptor, PermissionState + >>> descriptor = PermissionDescriptor("geolocation") + >>> driver.permissions.set_permission(descriptor, PermissionState.GRANTED, "https://example.com") + """ + if not self._websocket_connection: + self._start_bidi() + + if self._permissions is None: + self._permissions = Permissions(self._websocket_connection) + + return self._permissions + @property def webextension(self): """Returns a webextension module object for BiDi webextension commands. From cbbff82fe211b157b60d55c784c6524060cbf9da Mon Sep 17 00:00:00 2001 From: Shaurya Bisht <87357655+ShauryaDusht@users.noreply.github.com> Date: Sun, 8 Jun 2025 19:31:08 +0530 Subject: [PATCH 3/3] Formatting --- py/selenium/webdriver/remote/shadowroot.py | 11 +- py/selenium/webdriver/remote/webdriver.py | 213 +++++++++++++----- py/selenium/webdriver/remote/webelement.py | 46 ++-- .../webdriver/support/expected_conditions.py | 65 ++++-- .../remote/test_relative_by_annotations.py | 66 ++++++ .../test_expected_conditions_relative_by.py | 23 +- .../webdriver/test_relative_by_annotations.py | 33 +-- 7 files changed, 333 insertions(+), 124 deletions(-) create mode 100644 py/test/unit/selenium/webdriver/remote/test_relative_by_annotations.py diff --git a/py/selenium/webdriver/remote/shadowroot.py b/py/selenium/webdriver/remote/shadowroot.py index e3603797e838c..78ef88be52232 100644 --- a/py/selenium/webdriver/remote/shadowroot.py +++ b/py/selenium/webdriver/remote/shadowroot.py @@ -16,7 +16,8 @@ # under the License. from hashlib import md5 as md5_hash -from typing import Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Union + if TYPE_CHECKING: from selenium.webdriver.support.relative_locator import RelativeBy @@ -83,7 +84,9 @@ def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value: str = Non by = By.CSS_SELECTOR value = f'[name="{value}"]' - return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"] + return self._execute( + Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value} + )["value"] def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value: str = None): """Find elements inside a shadow root given a By strategy and locator. @@ -121,7 +124,9 @@ def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value: str = No by = By.CSS_SELECTOR value = f'[name="{value}"]' - return self._execute(Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"] + return self._execute( + Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value} + )["value"] # Private Methods def _execute(self, command, params=None): diff --git a/py/selenium/webdriver/remote/webdriver.py b/py/selenium/webdriver/remote/webdriver.py index 47fb68c534b07..ce4f463a9a2e6 100644 --- a/py/selenium/webdriver/remote/webdriver.py +++ b/py/selenium/webdriver/remote/webdriver.py @@ -30,18 +30,16 @@ from base64 import b64decode, urlsafe_b64encode from contextlib import asynccontextmanager, contextmanager from importlib import import_module -from typing import Any, Optional, Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional, Union if TYPE_CHECKING: from selenium.webdriver.support.relative_locator import RelativeBy -from selenium.common.exceptions import ( - InvalidArgumentException, - JavascriptException, - NoSuchCookieException, - NoSuchElementException, - WebDriverException, -) +from selenium.common.exceptions import (InvalidArgumentException, + JavascriptException, + NoSuchCookieException, + NoSuchElementException, + WebDriverException) from selenium.webdriver.common.bidi.browser import Browser from selenium.webdriver.common.bidi.browsing_context import BrowsingContext from selenium.webdriver.common.bidi.network import Network @@ -54,10 +52,8 @@ from selenium.webdriver.common.print_page_options import PrintOptions from selenium.webdriver.common.timeouts import Timeouts from selenium.webdriver.common.virtual_authenticator import ( - Credential, - VirtualAuthenticatorOptions, - required_virtual_authenticator, -) + Credential, VirtualAuthenticatorOptions, required_virtual_authenticator) + from ..common.fedcm.dialog import Dialog from .bidi_connection import BidiConnection from .client_config import ClientConfig @@ -113,16 +109,29 @@ def get_remote_connection( client_config: Optional[ClientConfig] = None, ) -> RemoteConnection: if isinstance(command_executor, str): - client_config = client_config or ClientConfig(remote_server_addr=command_executor) + client_config = client_config or ClientConfig( + remote_server_addr=command_executor + ) client_config.remote_server_addr = command_executor command_executor = RemoteConnection(client_config=client_config) - from selenium.webdriver.chrome.remote_connection import ChromeRemoteConnection + from selenium.webdriver.chrome.remote_connection import \ + ChromeRemoteConnection from selenium.webdriver.edge.remote_connection import EdgeRemoteConnection - from selenium.webdriver.firefox.remote_connection import FirefoxRemoteConnection - from selenium.webdriver.safari.remote_connection import SafariRemoteConnection - - candidates = [ChromeRemoteConnection, EdgeRemoteConnection, SafariRemoteConnection, FirefoxRemoteConnection] - handler = next((c for c in candidates if c.browser_name == capabilities.get("browserName")), RemoteConnection) + from selenium.webdriver.firefox.remote_connection import \ + FirefoxRemoteConnection + from selenium.webdriver.safari.remote_connection import \ + SafariRemoteConnection + + candidates = [ + ChromeRemoteConnection, + EdgeRemoteConnection, + SafariRemoteConnection, + FirefoxRemoteConnection, + ] + handler = next( + (c for c in candidates if c.browser_name == capabilities.get("browserName")), + RemoteConnection, + ) return handler( remote_server_addr=command_executor, @@ -383,9 +392,13 @@ def create_web_element(self, element_id: str) -> WebElement: def _unwrap_value(self, value): if isinstance(value, dict): if "element-6066-11e4-a52e-4f735466cecf" in value: - return self.create_web_element(value["element-6066-11e4-a52e-4f735466cecf"]) + return self.create_web_element( + value["element-6066-11e4-a52e-4f735466cecf"] + ) if "shadow-6066-11e4-a52e-4f735466cecf" in value: - return self._shadowroot_cls(self, value["shadow-6066-11e4-a52e-4f735466cecf"]) + return self._shadowroot_cls( + self, value["shadow-6066-11e4-a52e-4f735466cecf"] + ) for key, val in value.items(): value[key] = self._unwrap_value(val) return value @@ -418,9 +431,13 @@ def execute_cdp_cmd(self, cmd: str, cmd_args: dict): >>> driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": requestId}) """ - return self.execute("executeCdpCommand", {"cmd": cmd, "params": cmd_args})["value"] + return self.execute("executeCdpCommand", {"cmd": cmd, "params": cmd_args})[ + "value" + ] - def execute(self, driver_command: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]: + def execute( + self, driver_command: str, params: Optional[dict[str, Any]] = None + ) -> dict[str, Any]: """Sends a command to be executed by a command.CommandExecutor. Parameters: @@ -505,7 +522,9 @@ def unpin(self, script_key: ScriptKey) -> None: try: self.pinned_scripts.pop(script_key.id) except KeyError: - raise KeyError(f"No script with key: {script_key} existed in {self.pinned_scripts}") from None + raise KeyError( + f"No script with key: {script_key} existed in {self.pinned_scripts}" + ) from None def get_pinned_scripts(self) -> list[str]: """Return a list of all pinned scripts. @@ -542,7 +561,9 @@ def execute_script(self, script: str, *args): converted_args = list(args) command = Command.W3C_EXECUTE_SCRIPT - return self.execute(command, {"script": script, "args": converted_args})["value"] + return self.execute(command, {"script": script, "args": converted_args})[ + "value" + ] def execute_async_script(self, script: str, *args): """Asynchronously Executes JavaScript in the current window/frame. @@ -564,7 +585,9 @@ def execute_async_script(self, script: str, *args): converted_args = list(args) command = Command.W3C_EXECUTE_SCRIPT_ASYNC - return self.execute(command, {"script": script, "args": converted_args})["value"] + return self.execute(command, {"script": script, "args": converted_args})[ + "value" + ] @property def current_url(self) -> str: @@ -809,7 +832,9 @@ def implicitly_wait(self, time_to_wait: float) -> None: -------- >>> driver.implicitly_wait(30) """ - self.execute(Command.SET_TIMEOUTS, {"implicit": int(float(time_to_wait) * 1000)}) + self.execute( + Command.SET_TIMEOUTS, {"implicit": int(float(time_to_wait) * 1000)} + ) def set_script_timeout(self, time_to_wait: float) -> None: """Set the amount of time that the script should wait during an @@ -840,9 +865,14 @@ def set_page_load_timeout(self, time_to_wait: float) -> None: >>> driver.set_page_load_timeout(30) """ try: - self.execute(Command.SET_TIMEOUTS, {"pageLoad": int(float(time_to_wait) * 1000)}) + self.execute( + Command.SET_TIMEOUTS, {"pageLoad": int(float(time_to_wait) * 1000)} + ) except WebDriverException: - self.execute(Command.SET_TIMEOUTS, {"ms": float(time_to_wait) * 1000, "type": "page load"}) + self.execute( + Command.SET_TIMEOUTS, + {"ms": float(time_to_wait) * 1000, "type": "page load"}, + ) @property def timeouts(self) -> Timeouts: @@ -878,7 +908,9 @@ def timeouts(self, timeouts) -> None: """ _ = self.execute(Command.SET_TIMEOUTS, timeouts._to_json())["value"] - def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[str] = None) -> WebElement: + def find_element( + self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[str] = None + ) -> WebElement: """Find an element given a By strategy and locator. Parameters: @@ -909,12 +941,18 @@ def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[ if isinstance(by, RelativeBy): elements = self.find_elements(by=by, value=value) if not elements: - raise NoSuchElementException(f"Cannot locate relative element with: {by.root}") + raise NoSuchElementException( + f"Cannot locate relative element with: {by.root}" + ) return elements[0] - return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"] + return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})[ + "value" + ] - def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[str] = None) -> list[WebElement]: + def find_elements( + self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional[str] = None + ) -> list[WebElement]: """Find elements given a By strategy and locator. Parameters: @@ -945,12 +983,17 @@ def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value: Optional if isinstance(by, RelativeBy): _pkg = ".".join(__name__.split(".")[:-1]) raw_function = pkgutil.get_data(_pkg, "findElements.js").decode("utf8") - find_element_js = f"/* findElements */return ({raw_function}).apply(null, arguments);" + find_element_js = ( + f"/* findElements */return ({raw_function}).apply(null, arguments);" + ) return self.execute_script(find_element_js, by.to_dict()) # Return empty list if driver returns null # See https://github.com/SeleniumHQ/selenium/issues/4555 - return self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] or [] + return ( + self.execute(Command.FIND_ELEMENTS, {"using": by, "value": value})["value"] + or [] + ) @property def capabilities(self) -> dict: @@ -1063,7 +1106,9 @@ def get_window_size(self, windowHandle: str = "current") -> dict: return {k: size[k] for k in ("width", "height")} - def set_window_position(self, x: float, y: float, windowHandle: str = "current") -> dict: + def set_window_position( + self, x: float, y: float, windowHandle: str = "current" + ) -> dict: """Sets the x,y position of the current window. (window.moveTo) Parameters: @@ -1097,7 +1142,10 @@ def get_window_position(self, windowHandle="current") -> dict: def _check_if_window_handle_is_current(self, windowHandle: str) -> None: """Warns if the window handle is not equal to `current`.""" if windowHandle != "current": - warnings.warn("Only 'current' window is supported for W3C compatible browsers.", stacklevel=2) + warnings.warn( + "Only 'current' window is supported for W3C compatible browsers.", + stacklevel=2, + ) def get_window_rect(self) -> dict: """Gets the x, y coordinates of the window as well as height and width @@ -1125,7 +1173,9 @@ def set_window_rect(self, x=None, y=None, width=None, height=None) -> dict: if (x is None and y is None) and (not height and not width): raise InvalidArgumentException("x and y or height and width need values") - return self.execute(Command.SET_WINDOW_RECT, {"x": x, "y": y, "width": width, "height": height})["value"] + return self.execute( + Command.SET_WINDOW_RECT, {"x": x, "y": y, "width": width, "height": height} + )["value"] @property def file_detector(self) -> FileDetector: @@ -1178,7 +1228,9 @@ def orientation(self, value) -> None: if value.upper() in allowed_values: self.execute(Command.SET_SCREEN_ORIENTATION, {"orientation": value}) else: - raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") + raise WebDriverException( + "You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'" + ) def start_devtools(self): global devtools @@ -1196,15 +1248,21 @@ def start_devtools(self): version, ws_url = self._get_cdp_details() if not ws_url: - raise WebDriverException("Unable to find url to connect to from capabilities") + raise WebDriverException( + "Unable to find url to connect to from capabilities" + ) devtools = cdp.import_devtools(version) if self.caps["browserName"].lower() == "firefox": - raise RuntimeError("CDP support for Firefox has been removed. Please switch to WebDriver BiDi.") + raise RuntimeError( + "CDP support for Firefox has been removed. Please switch to WebDriver BiDi." + ) self._websocket_connection = WebSocketConnection(ws_url) targets = self._websocket_connection.execute(devtools.target.get_targets()) target_id = targets[0].target_id - session = self._websocket_connection.execute(devtools.target.attach_to_target(target_id, True)) + session = self._websocket_connection.execute( + devtools.target.attach_to_target(target_id, True) + ) self._websocket_connection.session_id = session return devtools, self._websocket_connection @@ -1219,7 +1277,9 @@ async def bidi_connection(self): version, ws_url = self._get_cdp_details() if not ws_url: - raise WebDriverException("Unable to find url to connect to from capabilities") + raise WebDriverException( + "Unable to find url to connect to from capabilities" + ) devtools = cdp.import_devtools(version) async with cdp.open_cdp(ws_url) as conn: @@ -1242,7 +1302,9 @@ def _start_bidi(self): if self.caps.get("webSocketUrl"): ws_url = self.caps.get("webSocketUrl") else: - raise WebDriverException("Unable to find url to connect to from capabilities") + raise WebDriverException( + "Unable to find url to connect to from capabilities" + ) self._websocket_connection = WebSocketConnection(ws_url) @@ -1340,7 +1402,7 @@ def storage(self): return self._storage - @property + @property def permissions(self): """Returns a permissions module object for BiDi permissions commands. Returns: @@ -1389,7 +1451,9 @@ def _get_cdp_details(self): http = urllib3.PoolManager() if self.caps.get("browserName") == "chrome": - debugger_address = self.caps.get("goog:chromeOptions").get("debuggerAddress") + debugger_address = self.caps.get("goog:chromeOptions").get( + "debuggerAddress" + ) elif self.caps.get("browserName") == "MicrosoftEdge": debugger_address = self.caps.get("ms:edgeOptions").get("debuggerAddress") @@ -1415,7 +1479,9 @@ def add_virtual_authenticator(self, options: VirtualAuthenticatorOptions) -> Non >>> options = VirtualAuthenticatorOptions(protocol="u2f", transport="usb", device_id="myDevice123") >>> driver.add_virtual_authenticator(options) """ - self._authenticator_id = self.execute(Command.ADD_VIRTUAL_AUTHENTICATOR, options.to_dict())["value"] + self._authenticator_id = self.execute( + Command.ADD_VIRTUAL_AUTHENTICATOR, options.to_dict() + )["value"] @property def virtual_authenticator_id(self) -> str: @@ -1438,7 +1504,10 @@ def remove_virtual_authenticator(self) -> None: -------- >>> driver.remove_virtual_authenticator() """ - self.execute(Command.REMOVE_VIRTUAL_AUTHENTICATOR, {"authenticatorId": self._authenticator_id}) + self.execute( + Command.REMOVE_VIRTUAL_AUTHENTICATOR, + {"authenticatorId": self._authenticator_id}, + ) self._authenticator_id = None @required_virtual_authenticator @@ -1451,7 +1520,10 @@ def add_credential(self, credential: Credential) -> None: >>> credential = Credential(id="user@example.com", password="aPassword") >>> driver.add_credential(credential) """ - self.execute(Command.ADD_CREDENTIAL, {**credential.to_dict(), "authenticatorId": self._authenticator_id}) + self.execute( + Command.ADD_CREDENTIAL, + {**credential.to_dict(), "authenticatorId": self._authenticator_id}, + ) @required_virtual_authenticator def get_credentials(self) -> list[Credential]: @@ -1461,8 +1533,12 @@ def get_credentials(self) -> list[Credential]: -------- >>> credentials = driver.get_credentials() """ - credential_data = self.execute(Command.GET_CREDENTIALS, {"authenticatorId": self._authenticator_id}) - return [Credential.from_dict(credential) for credential in credential_data["value"]] + credential_data = self.execute( + Command.GET_CREDENTIALS, {"authenticatorId": self._authenticator_id} + ) + return [ + Credential.from_dict(credential) for credential in credential_data["value"] + ] @required_virtual_authenticator def remove_credential(self, credential_id: Union[str, bytearray]) -> None: @@ -1478,7 +1554,8 @@ def remove_credential(self, credential_id: Union[str, bytearray]) -> None: credential_id = urlsafe_b64encode(credential_id).decode() self.execute( - Command.REMOVE_CREDENTIAL, {"credentialId": credential_id, "authenticatorId": self._authenticator_id} + Command.REMOVE_CREDENTIAL, + {"credentialId": credential_id, "authenticatorId": self._authenticator_id}, ) @required_virtual_authenticator @@ -1489,7 +1566,9 @@ def remove_all_credentials(self) -> None: -------- >>> driver.remove_all_credentials() """ - self.execute(Command.REMOVE_ALL_CREDENTIALS, {"authenticatorId": self._authenticator_id}) + self.execute( + Command.REMOVE_ALL_CREDENTIALS, {"authenticatorId": self._authenticator_id} + ) @required_virtual_authenticator def set_user_verified(self, verified: bool) -> None: @@ -1504,7 +1583,10 @@ def set_user_verified(self, verified: bool) -> None: -------- >>> driver.set_user_verified(True) """ - self.execute(Command.SET_USER_VERIFIED, {"authenticatorId": self._authenticator_id, "isUserVerified": verified}) + self.execute( + Command.SET_USER_VERIFIED, + {"authenticatorId": self._authenticator_id, "isUserVerified": verified}, + ) def get_downloadable_files(self) -> list: """Retrieves the downloadable files as a list of file names. @@ -1514,7 +1596,9 @@ def get_downloadable_files(self) -> list: >>> files = driver.get_downloadable_files() """ if "se:downloadsEnabled" not in self.capabilities: - raise WebDriverException("You must enable downloads in order to work with downloadable files.") + raise WebDriverException( + "You must enable downloads in order to work with downloadable files." + ) return self.execute(Command.GET_DOWNLOADABLE_FILES)["value"]["names"] @@ -1535,12 +1619,16 @@ def download_file(self, file_name: str, target_directory: str) -> None: >>> driver.download_file("example.zip", "/path/to/directory") """ if "se:downloadsEnabled" not in self.capabilities: - raise WebDriverException("You must enable downloads in order to work with downloadable files.") + raise WebDriverException( + "You must enable downloads in order to work with downloadable files." + ) if not os.path.exists(target_directory): os.makedirs(target_directory) - contents = self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"]["contents"] + contents = self.execute(Command.DOWNLOAD_FILE, {"name": file_name})["value"][ + "contents" + ] with tempfile.TemporaryDirectory() as tmp_dir: zip_file = os.path.join(tmp_dir, file_name + ".zip") @@ -1558,7 +1646,9 @@ def delete_downloadable_files(self) -> None: >>> driver.delete_downloadable_files() """ if "se:downloadsEnabled" not in self.capabilities: - raise WebDriverException("You must enable downloads in order to work with downloadable files.") + raise WebDriverException( + "You must enable downloads in order to work with downloadable files." + ) self.execute(Command.DELETE_DOWNLOADABLE_FILES) @@ -1653,5 +1743,10 @@ def _check_fedcm(): except NoAlertPresentException: return None - wait = WebDriverWait(self, timeout, poll_frequency=poll_frequency, ignored_exceptions=ignored_exceptions) + wait = WebDriverWait( + self, + timeout, + poll_frequency=poll_frequency, + ignored_exceptions=ignored_exceptions, + ) return wait.until(lambda _: _check_fedcm()) diff --git a/py/selenium/webdriver/remote/webelement.py b/py/selenium/webdriver/remote/webelement.py index 37043653c7a98..90e9a01f33616 100644 --- a/py/selenium/webdriver/remote/webelement.py +++ b/py/selenium/webdriver/remote/webelement.py @@ -24,12 +24,11 @@ from base64 import b64decode, encodebytes from hashlib import md5 as md5_hash from io import BytesIO -from typing import Union, TYPE_CHECKING +from typing import TYPE_CHECKING, Union if TYPE_CHECKING: from selenium.webdriver.support.relative_locator import RelativeBy - from selenium.common.exceptions import JavascriptException, WebDriverException from selenium.webdriver.common.by import By, ByType from selenium.webdriver.common.utils import keys_to_typing @@ -147,7 +146,9 @@ def submit(self) -> None: try: self._parent.execute_script(script, self) except JavascriptException as exc: - raise WebDriverException("To submit an element, it must be nested inside a form element") from exc + raise WebDriverException( + "To submit an element, it must be nested inside a form element" + ) from exc def clear(self) -> None: """Clears the text if it's a text entry element. @@ -179,7 +180,9 @@ def get_property(self, name) -> str | bool | WebElement | dict: return self._execute(Command.GET_ELEMENT_PROPERTY, {"name": name})["value"] except WebDriverException: # if we hit an end point that doesn't understand getElementProperty lets fake it - return self.parent.execute_script("return arguments[0][arguments[1]]", self, name) + return self.parent.execute_script( + "return arguments[0][arguments[1]]", self, name + ) def get_dom_attribute(self, name) -> str: """Gets the given attribute of the element. Unlike @@ -235,7 +238,9 @@ def get_attribute(self, name) -> str | None: if getAttribute_js is None: _load_js() attribute_value = self.parent.execute_script( - f"/* getAttribute */return ({getAttribute_js}).apply(null, arguments);", self, name + f"/* getAttribute */return ({getAttribute_js}).apply(null, arguments);", + self, + name, ) return attribute_value @@ -296,7 +301,9 @@ def send_keys(self, *value: str) -> None: if self.parent._is_remote: local_files = list( map( - lambda keys_to_send: self.parent.file_detector.is_local_file(str(keys_to_send)), + lambda keys_to_send: self.parent.file_detector.is_local_file( + str(keys_to_send) + ), "".join(map(str, value)).split("\n"), ) ) @@ -307,7 +314,8 @@ def send_keys(self, *value: str) -> None: value = tuple("\n".join(remote_files)) self._execute( - Command.SEND_KEYS_TO_ELEMENT, {"text": "".join(keys_to_typing(value)), "value": keys_to_typing(value)} + Command.SEND_KEYS_TO_ELEMENT, + {"text": "".join(keys_to_typing(value)), "value": keys_to_typing(value)}, ) @property @@ -343,7 +351,9 @@ def is_displayed(self) -> bool: # Only go into this conditional for browsers that don't use the atom themselves if isDisplayed_js is None: _load_js() - return self.parent.execute_script(f"/* isDisplayed */return ({isDisplayed_js}).apply(null, arguments);", self) + return self.parent.execute_script( + f"/* isDisplayed */return ({isDisplayed_js}).apply(null, arguments);", self + ) @property def location_once_scrolled_into_view(self) -> dict: @@ -401,7 +411,9 @@ def value_of_css_property(self, property_name) -> str: -------- >>> value = element.value_of_css_property("color") """ - return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {"propertyName": property_name})["value"] + return self._execute( + Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {"propertyName": property_name} + )["value"] @property def location(self) -> dict: @@ -577,7 +589,9 @@ def _execute(self, command, params=None): params["id"] = self._id return self._parent.execute(command, params) - def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value=None) -> WebElement: + def find_element( + self, by: "Union[ByType, RelativeBy]" = By.ID, value=None + ) -> WebElement: """Find an element given a By strategy and locator. Parameters: @@ -604,9 +618,13 @@ def find_element(self, by: "Union[ByType, RelativeBy]" = By.ID, value=None) -> W The first matching `WebElement` found on the page. """ by, value = self._parent.locator_converter.convert(by, value) - return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"] + return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})[ + "value" + ] - def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value=None) -> list[WebElement]: + def find_elements( + self, by: "Union[ByType, RelativeBy]" = By.ID, value=None + ) -> list[WebElement]: """Find elements given a By strategy and locator. Parameters: @@ -633,7 +651,9 @@ def find_elements(self, by: "Union[ByType, RelativeBy]" = By.ID, value=None) -> list of `WebElements` matching locator strategy found on the page. """ by, value = self._parent.locator_converter.convert(by, value) - return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"] + return self._execute( + Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value} + )["value"] def __hash__(self) -> int: return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16) diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index bd4fdf4f7d6c4..43b728e5c3532 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -17,20 +17,17 @@ import re from collections.abc import Iterable -from typing import Any, Callable, Literal, TypeVar, Union, Tuple +from typing import Any, Callable, Literal, Tuple, TypeVar, Union -from selenium.webdriver.common.by import ByType -from selenium.webdriver.support.relative_locator import RelativeBy - -from selenium.common.exceptions import ( - NoAlertPresentException, - NoSuchElementException, - NoSuchFrameException, - StaleElementReferenceException, - WebDriverException, -) +from selenium.common.exceptions import (NoAlertPresentException, + NoSuchElementException, + NoSuchFrameException, + StaleElementReferenceException, + WebDriverException) from selenium.webdriver.common.alert import Alert +from selenium.webdriver.common.by import ByType from selenium.webdriver.remote.webdriver import WebDriver, WebElement +from selenium.webdriver.support.relative_locator import RelativeBy """ * Canned "Expected Conditions" which are generally useful within webdriver @@ -83,7 +80,9 @@ def _predicate(driver: WebDriver): return _predicate -def presence_of_element_located(locator: LocatorType) -> Callable[[WebDriverOrWebElement], WebElement]: +def presence_of_element_located( + locator: LocatorType, +) -> Callable[[WebDriverOrWebElement], WebElement]: """An expectation for checking that an element is present on the DOM of a page. This does not necessarily mean that the element is visible. @@ -225,7 +224,9 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def visibility_of(element: WebElement) -> Callable[[Any], Union[Literal[False], WebElement]]: +def visibility_of( + element: WebElement, +) -> Callable[[Any], Union[Literal[False], WebElement]]: """An expectation for checking that an element, known to be present on the DOM of a page, is visible. @@ -258,7 +259,9 @@ def _predicate(_): return _predicate -def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[Literal[False], WebElement]: +def _element_if_visible( + element: WebElement, visibility: bool = True +) -> Union[Literal[False], WebElement]: """An expectation for checking that an element, known to be present on the DOM of a page, is of the expected visibility. @@ -276,7 +279,9 @@ def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[L return element if element.is_displayed() == visibility else False -def presence_of_all_elements_located(locator: LocatorType) -> Callable[[WebDriverOrWebElement], list[WebElement]]: +def presence_of_all_elements_located( + locator: LocatorType, +) -> Callable[[WebDriverOrWebElement], list[WebElement]]: """An expectation for checking that there is at least one element present on a web page. @@ -303,7 +308,9 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def visibility_of_any_elements_located(locator: LocatorType) -> Callable[[WebDriverOrWebElement], list[WebElement]]: +def visibility_of_any_elements_located( + locator: LocatorType, +) -> Callable[[WebDriverOrWebElement], list[WebElement]]: """An expectation for checking that there is at least one element visible on a web page. @@ -325,7 +332,11 @@ def visibility_of_any_elements_located(locator: LocatorType) -> Callable[[WebDri """ def _predicate(driver: WebDriverOrWebElement): - return [element for element in driver.find_elements(*locator) if _element_if_visible(element)] + return [ + element + for element in driver.find_elements(*locator) + if _element_if_visible(element) + ] return _predicate @@ -367,7 +378,9 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def text_to_be_present_in_element(locator: LocatorType, text_: str) -> Callable[[WebDriverOrWebElement], bool]: +def text_to_be_present_in_element( + locator: LocatorType, text_: str +) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for checking if the given text is present in the specified element. @@ -691,7 +704,9 @@ def _predicate(_): return _predicate -def element_located_to_be_selected(locator: LocatorType) -> Callable[[WebDriverOrWebElement], bool]: +def element_located_to_be_selected( + locator: LocatorType, +) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for the element to be located is selected. Parameters: @@ -717,7 +732,9 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def element_selection_state_to_be(element: WebElement, is_selected: bool) -> Callable[[Any], bool]: +def element_selection_state_to_be( + element: WebElement, is_selected: bool +) -> Callable[[Any], bool]: """An expectation for checking if the given element is selected. Parameters: @@ -862,7 +879,9 @@ def _predicate(driver: WebDriver): return _predicate -def element_attribute_to_include(locator: LocatorType, attribute_: str) -> Callable[[WebDriverOrWebElement], bool]: +def element_attribute_to_include( + locator: LocatorType, attribute_: str +) -> Callable[[WebDriverOrWebElement], bool]: """An expectation for checking if the given attribute is included in the specified element. @@ -897,7 +916,9 @@ def _predicate(driver: WebDriverOrWebElement): return _predicate -def any_of(*expected_conditions: Callable[[D], T]) -> Callable[[D], Union[Literal[False], T]]: +def any_of( + *expected_conditions: Callable[[D], T] +) -> Callable[[D], Union[Literal[False], T]]: """An expectation that any of multiple expected conditions is true. Parameters: diff --git a/py/test/unit/selenium/webdriver/remote/test_relative_by_annotations.py b/py/test/unit/selenium/webdriver/remote/test_relative_by_annotations.py new file mode 100644 index 0000000000000..84863a8f581ca --- /dev/null +++ b/py/test/unit/selenium/webdriver/remote/test_relative_by_annotations.py @@ -0,0 +1,66 @@ +from unittest.mock import MagicMock, Mock + +import pytest +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.shadowroot import ShadowRoot +from selenium.webdriver.remote.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement +from selenium.webdriver.support.relative_locator import RelativeBy, locate_with + + +class TestRelativeByAnnotations: + """Test that RelativeBy is properly accepted in type annotations""" + + def test_webdriver_find_element_accepts_relative_by(self): + """Test WebDriver.find_element accepts RelativeBy""" + driver = Mock(spec=WebDriver) + relative_by = locate_with(By.TAG_NAME, "div").above({By.ID: "footer"}) + + # This should not raise type checking errors + driver.find_element(by=relative_by) + driver.find_element(relative_by) + + def test_webdriver_find_elements_accepts_relative_by(self): + """Test WebDriver.find_elements accepts RelativeBy""" + driver = Mock(spec=WebDriver) + relative_by = locate_with(By.TAG_NAME, "div").below({By.ID: "header"}) + + # This should not raise type checking errors + driver.find_elements(by=relative_by) + driver.find_elements(relative_by) + + def test_webelement_find_element_accepts_relative_by(self): + """Test WebElement.find_element accepts RelativeBy""" + element = Mock(spec=WebElement) + relative_by = locate_with(By.TAG_NAME, "span").near({By.CLASS_NAME: "button"}) + + # This should not raise type checking errors + element.find_element(by=relative_by) + element.find_element(relative_by) + + def test_webelement_find_elements_accepts_relative_by(self): + """Test WebElement.find_elements accepts RelativeBy""" + element = Mock(spec=WebElement) + relative_by = locate_with(By.TAG_NAME, "input").to_left_of({By.ID: "submit"}) + + # This should not raise type checking errors + element.find_elements(by=relative_by) + element.find_elements(relative_by) + + def test_shadowroot_find_element_accepts_relative_by(self): + """Test ShadowRoot.find_element accepts RelativeBy""" + shadow_root = Mock(spec=ShadowRoot) + relative_by = locate_with(By.TAG_NAME, "button").to_right_of({By.ID: "cancel"}) + + # This should not raise type checking errors + shadow_root.find_element(by=relative_by) + shadow_root.find_element(relative_by) + + def test_shadowroot_find_elements_accepts_relative_by(self): + """Test ShadowRoot.find_elements accepts RelativeBy""" + shadow_root = Mock(spec=ShadowRoot) + relative_by = locate_with(By.CSS_SELECTOR, ".item").above({By.ID: "footer"}) + + # This should not raise type checking errors + shadow_root.find_elements(by=relative_by) + shadow_root.find_elements(relative_by) diff --git a/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py b/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py index 9a6dd2eabdfde..a119c51a8e808 100644 --- a/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py +++ b/py/test/unit/selenium/webdriver/support/test_expected_conditions_relative_by.py @@ -1,57 +1,58 @@ +from unittest.mock import Mock + import pytest +from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.relative_locator import locate_with -from selenium.webdriver.common.by import By -from unittest.mock import Mock class TestExpectedConditionsRelativeBy: """Test that expected conditions accept RelativeBy in type annotations""" - + def test_presence_of_element_located_accepts_relative_by(self): """Test presence_of_element_located accepts RelativeBy""" relative_by = locate_with(By.TAG_NAME, "div").above({By.ID: "footer"}) condition = EC.presence_of_element_located(relative_by) assert condition is not None - + def test_visibility_of_element_located_accepts_relative_by(self): """Test visibility_of_element_located accepts RelativeBy""" relative_by = locate_with(By.TAG_NAME, "button").near({By.CLASS_NAME: "submit"}) condition = EC.visibility_of_element_located(relative_by) assert condition is not None - + def test_presence_of_all_elements_located_accepts_relative_by(self): """Test presence_of_all_elements_located accepts RelativeBy""" relative_by = locate_with(By.CSS_SELECTOR, ".item").below({By.ID: "header"}) condition = EC.presence_of_all_elements_located(relative_by) assert condition is not None - + def test_visibility_of_any_elements_located_accepts_relative_by(self): """Test visibility_of_any_elements_located accepts RelativeBy""" relative_by = locate_with(By.TAG_NAME, "span").to_left_of({By.ID: "sidebar"}) condition = EC.visibility_of_any_elements_located(relative_by) assert condition is not None - + def test_text_to_be_present_in_element_accepts_relative_by(self): """Test text_to_be_present_in_element accepts RelativeBy""" relative_by = locate_with(By.TAG_NAME, "p").above({By.CLASS_NAME: "footer"}) condition = EC.text_to_be_present_in_element(relative_by, "Hello") assert condition is not None - + def test_element_to_be_clickable_accepts_relative_by(self): """Test element_to_be_clickable accepts RelativeBy""" relative_by = locate_with(By.TAG_NAME, "button").near({By.ID: "form"}) condition = EC.element_to_be_clickable(relative_by) assert condition is not None - + def test_invisibility_of_element_located_accepts_relative_by(self): """Test invisibility_of_element_located accepts RelativeBy""" relative_by = locate_with(By.CSS_SELECTOR, ".loading").above({By.ID: "content"}) condition = EC.invisibility_of_element_located(relative_by) assert condition is not None - + def test_element_located_to_be_selected_accepts_relative_by(self): """Test element_located_to_be_selected accepts RelativeBy""" relative_by = locate_with(By.TAG_NAME, "input").near({By.ID: "terms-label"}) condition = EC.element_located_to_be_selected(relative_by) - assert condition is not None \ No newline at end of file + assert condition is not None diff --git a/py/test/unit/selenium/webdriver/test_relative_by_annotations.py b/py/test/unit/selenium/webdriver/test_relative_by_annotations.py index 0e149e7b86d18..84863a8f581ca 100644 --- a/py/test/unit/selenium/webdriver/test_relative_by_annotations.py +++ b/py/test/unit/selenium/webdriver/test_relative_by_annotations.py @@ -1,65 +1,66 @@ +from unittest.mock import MagicMock, Mock + import pytest from selenium.webdriver.common.by import By -from selenium.webdriver.support.relative_locator import RelativeBy, locate_with +from selenium.webdriver.remote.shadowroot import ShadowRoot from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webelement import WebElement -from selenium.webdriver.remote.shadowroot import ShadowRoot -from unittest.mock import Mock, MagicMock +from selenium.webdriver.support.relative_locator import RelativeBy, locate_with class TestRelativeByAnnotations: """Test that RelativeBy is properly accepted in type annotations""" - + def test_webdriver_find_element_accepts_relative_by(self): """Test WebDriver.find_element accepts RelativeBy""" driver = Mock(spec=WebDriver) relative_by = locate_with(By.TAG_NAME, "div").above({By.ID: "footer"}) - + # This should not raise type checking errors driver.find_element(by=relative_by) driver.find_element(relative_by) - + def test_webdriver_find_elements_accepts_relative_by(self): """Test WebDriver.find_elements accepts RelativeBy""" driver = Mock(spec=WebDriver) relative_by = locate_with(By.TAG_NAME, "div").below({By.ID: "header"}) - + # This should not raise type checking errors driver.find_elements(by=relative_by) driver.find_elements(relative_by) - + def test_webelement_find_element_accepts_relative_by(self): """Test WebElement.find_element accepts RelativeBy""" element = Mock(spec=WebElement) relative_by = locate_with(By.TAG_NAME, "span").near({By.CLASS_NAME: "button"}) - + # This should not raise type checking errors element.find_element(by=relative_by) element.find_element(relative_by) - + def test_webelement_find_elements_accepts_relative_by(self): """Test WebElement.find_elements accepts RelativeBy""" element = Mock(spec=WebElement) relative_by = locate_with(By.TAG_NAME, "input").to_left_of({By.ID: "submit"}) - + # This should not raise type checking errors element.find_elements(by=relative_by) element.find_elements(relative_by) - + def test_shadowroot_find_element_accepts_relative_by(self): """Test ShadowRoot.find_element accepts RelativeBy""" shadow_root = Mock(spec=ShadowRoot) relative_by = locate_with(By.TAG_NAME, "button").to_right_of({By.ID: "cancel"}) - + # This should not raise type checking errors shadow_root.find_element(by=relative_by) shadow_root.find_element(relative_by) - + def test_shadowroot_find_elements_accepts_relative_by(self): """Test ShadowRoot.find_elements accepts RelativeBy""" shadow_root = Mock(spec=ShadowRoot) relative_by = locate_with(By.CSS_SELECTOR, ".item").above({By.ID: "footer"}) - + # This should not raise type checking errors shadow_root.find_elements(by=relative_by) - shadow_root.find_elements(relative_by) \ No newline at end of file + shadow_root.find_elements(relative_by)