From 0e114aae6e7e477363adb3d00b60878b6a9bc87e Mon Sep 17 00:00:00 2001 From: pinterior Date: Mon, 30 Oct 2023 22:15:55 +0900 Subject: [PATCH 1/4] add type hint to s.w.support.expected_conditions --- .../webdriver/support/expected_conditions.py | 131 +++++++++--------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index 65006552996dc..5ddfc6ebc3223 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -16,9 +16,12 @@ # under the License. import re +from typing import Any from typing import Callable from typing import List +from typing import Literal from typing import Tuple +from typing import TypeVar from typing import Union from selenium.common.exceptions import NoAlertPresentException @@ -26,36 +29,32 @@ from selenium.common.exceptions import NoSuchFrameException from selenium.common.exceptions import StaleElementReferenceException from selenium.common.exceptions import WebDriverException -from selenium.webdriver import Chrome -from selenium.webdriver import Edge -from selenium.webdriver import Firefox -from selenium.webdriver import Ie -from selenium.webdriver import Safari from selenium.webdriver.common.alert import Alert +from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webdriver import WebElement -# All driver types -AnyDriver = Union[Chrome, Firefox, Safari, Ie, Edge] + +T = TypeVar("T") """ * Canned "Expected Conditions" which are generally useful within webdriver * tests. """ -def title_is(title: str) -> Callable[[AnyDriver], bool]: +def title_is(title: str) -> Callable[[WebDriver], bool]: """An expectation for checking the title of a page. title is the expected title, which must be an exact match returns True if the title matches, false otherwise. """ - def _predicate(driver): + def _predicate(driver: WebDriver): return driver.title == title return _predicate -def title_contains(title: str) -> Callable[[AnyDriver], bool]: +def title_contains(title: str) -> Callable[[WebDriver], bool]: """An expectation for checking that the title contains a case-sensitive substring. @@ -63,13 +62,13 @@ def title_contains(title: str) -> Callable[[AnyDriver], bool]: matches, False otherwise """ - def _predicate(driver): + def _predicate(driver: WebDriver): return title in driver.title return _predicate -def presence_of_element_located(locator: Tuple[str, str]) -> Callable[[AnyDriver], WebElement]: +def presence_of_element_located(locator: Tuple[str, str]) -> Callable[[WebDriver], 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. @@ -77,13 +76,13 @@ def presence_of_element_located(locator: Tuple[str, str]) -> Callable[[AnyDriver returns the WebElement once it is located """ - def _predicate(driver): + def _predicate(driver: WebDriver): return driver.find_element(*locator) return _predicate -def url_contains(url: str) -> Callable[[AnyDriver], bool]: +def url_contains(url: str) -> Callable[[WebDriver], bool]: """An expectation for checking that the current url contains a case- sensitive substring. @@ -91,13 +90,13 @@ def url_contains(url: str) -> Callable[[AnyDriver], bool]: matches, False otherwise """ - def _predicate(driver): + def _predicate(driver: WebDriver): return url in driver.current_url return _predicate -def url_matches(pattern: str) -> Callable[[AnyDriver], bool]: +def url_matches(pattern: str) -> Callable[[WebDriver], bool]: """An expectation for checking the current url. pattern is the expected pattern. This finds the first occurrence of @@ -105,39 +104,39 @@ def url_matches(pattern: str) -> Callable[[AnyDriver], bool]: full match. """ - def _predicate(driver): + def _predicate(driver: WebDriver): return re.search(pattern, driver.current_url) is not None return _predicate -def url_to_be(url: str) -> Callable[[AnyDriver], bool]: +def url_to_be(url: str) -> Callable[[WebDriver], bool]: """An expectation for checking the current url. url is the expected url, which must be an exact match returns True if the url matches, false otherwise. """ - def _predicate(driver): + def _predicate(driver: WebDriver): return url == driver.current_url return _predicate -def url_changes(url: str) -> Callable[[AnyDriver], bool]: +def url_changes(url: str) -> Callable[[WebDriver], bool]: """An expectation for checking the current url. url is the expected url, which must not be an exact match returns True if the url is different, false otherwise. """ - def _predicate(driver): + def _predicate(driver: WebDriver): return url != driver.current_url return _predicate -def visibility_of_element_located(locator: Tuple[str, str]) -> Callable[[AnyDriver], Union[WebElement, bool]]: +def visibility_of_element_located(locator: Tuple[str, str]) -> Callable[[WebDriver], 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 but also has a height and width that is greater than 0. @@ -146,7 +145,7 @@ def visibility_of_element_located(locator: Tuple[str, str]) -> Callable[[AnyDriv returns the WebElement once it is located and visible """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: return _element_if_visible(driver.find_element(*locator)) except StaleElementReferenceException: @@ -155,7 +154,7 @@ def _predicate(driver): return _predicate -def visibility_of(element: WebElement) -> Callable[[AnyDriver], Union[WebElement, bool]]: +def visibility_of(element: WebElement) -> Callable[[WebDriver], Union[Literal[False], WebElement]]: """An expectation for checking that an element, known to be present on the DOM of a page, is visible. @@ -170,11 +169,11 @@ def _predicate(_): return _predicate -def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[WebElement, bool]: +def _element_if_visible(element: WebElement, visibility: bool = True) -> Union[Literal[False], WebElement]: return element if element.is_displayed() == visibility else False -def presence_of_all_elements_located(locator: Tuple[str, str]) -> Callable[[AnyDriver], List[WebElement]]: +def presence_of_all_elements_located(locator: Tuple[str, str]) -> Callable[[WebDriver], List[WebElement]]: """An expectation for checking that there is at least one element present on a web page. @@ -182,13 +181,13 @@ def presence_of_all_elements_located(locator: Tuple[str, str]) -> Callable[[AnyD once they are located """ - def _predicate(driver): + def _predicate(driver: WebDriver): return driver.find_elements(*locator) return _predicate -def visibility_of_any_elements_located(locator: Tuple[str, str]) -> Callable[[AnyDriver], List[WebElement]]: +def visibility_of_any_elements_located(locator: Tuple[str, str]) -> Callable[[WebDriver], List[WebElement]]: """An expectation for checking that there is at least one element visible on a web page. @@ -196,7 +195,7 @@ def visibility_of_any_elements_located(locator: Tuple[str, str]) -> Callable[[An once they are located """ - def _predicate(driver): + def _predicate(driver: WebDriver): return [element for element in driver.find_elements(*locator) if _element_if_visible(element)] return _predicate @@ -204,7 +203,7 @@ def _predicate(driver): def visibility_of_all_elements_located( locator: Tuple[str, str] -) -> Callable[[AnyDriver], Union[bool, List[WebElement]]]: +) -> Callable[[WebDriver], 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 displayed but also has a height and width that is greater than 0. @@ -213,7 +212,7 @@ def visibility_of_all_elements_located( returns the list of WebElements once they are located and visible """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: elements = driver.find_elements(*locator) for element in elements: @@ -226,14 +225,14 @@ def _predicate(driver): return _predicate -def text_to_be_present_in_element(locator: Tuple[str, str], text_: str) -> Callable[[AnyDriver], bool]: +def text_to_be_present_in_element(locator: Tuple[str, str], text_: str) -> Callable[[WebDriver], bool]: """An expectation for checking if the given text is present in the specified element. locator, text """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: element_text = driver.find_element(*locator).text return text_ in element_text @@ -243,14 +242,14 @@ def _predicate(driver): return _predicate -def text_to_be_present_in_element_value(locator: Tuple[str, str], text_: str) -> Callable[[AnyDriver], bool]: +def text_to_be_present_in_element_value(locator: Tuple[str, str], text_: str) -> Callable[[WebDriver], bool]: """An expectation for checking if the given text is present in the element's value. locator, text """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: element_text = driver.find_element(*locator).get_attribute("value") return text_ in element_text @@ -262,14 +261,14 @@ def _predicate(driver): def text_to_be_present_in_element_attribute( locator: Tuple[str, str], attribute_: str, text_: str -) -> Callable[[AnyDriver], bool]: +) -> Callable[[WebDriver], bool]: """An expectation for checking if the given text is present in the element's attribute. locator, attribute, text """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: element_text = driver.find_element(*locator).get_attribute(attribute_) if element_text is None: @@ -281,7 +280,7 @@ def _predicate(driver): return _predicate -def frame_to_be_available_and_switch_to_it(locator: Union[Tuple[str, str], str]) -> Callable[[AnyDriver], bool]: +def frame_to_be_available_and_switch_to_it(locator: Union[Tuple[str, str], str]) -> Callable[[WebDriver], bool]: """An expectation for checking whether the given frame is available to switch to. @@ -289,7 +288,7 @@ def frame_to_be_available_and_switch_to_it(locator: Union[Tuple[str, str], str]) specified frame. """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: if hasattr(locator, "__iter__") and not isinstance(locator, str): driver.switch_to.frame(driver.find_element(*locator)) @@ -304,14 +303,14 @@ def _predicate(driver): def invisibility_of_element_located( locator: Union[WebElement, Tuple[str, str]] -) -> Callable[[AnyDriver], Union[WebElement, bool]]: +) -> Callable[[WebDriver], Union[WebElement, bool]]: """An Expectation for checking that an element is either invisible or not present on the DOM. locator used to find the element """ - def _predicate(driver): + def _predicate(driver :WebDriver): try: target = locator if not isinstance(target, WebElement): @@ -330,7 +329,7 @@ def _predicate(driver): def invisibility_of_element( element: Union[WebElement, Tuple[str, str]] -) -> Callable[[AnyDriver], Union[WebElement, bool]]: +) -> Callable[[WebDriver], Union[WebElement, bool]]: """An Expectation for checking that an element is either invisible or not present on the DOM. @@ -339,7 +338,7 @@ def invisibility_of_element( return invisibility_of_element_located(element) -def element_to_be_clickable(mark: Union[WebElement, Tuple[str, str]]) -> Callable[[AnyDriver], Union[WebElement, bool]]: +def element_to_be_clickable(mark: Union[WebElement, Tuple[str, str]]) -> Callable[[WebDriver], Union[Literal[False], WebElement]]: """An Expectation for checking an element is visible and enabled such that you can click it. @@ -348,7 +347,7 @@ def element_to_be_clickable(mark: Union[WebElement, Tuple[str, str]]) -> Callabl # renamed argument to 'mark', to indicate that both locator # and WebElement args are valid - def _predicate(driver): + def _predicate(driver: WebDriver): target = mark if not isinstance(target, WebElement): # if given locator instead of WebElement target = driver.find_element(*target) # grab element at locator @@ -360,7 +359,7 @@ def _predicate(driver): return _predicate -def staleness_of(element: WebElement) -> Callable[[AnyDriver], bool]: +def staleness_of(element: WebElement) -> Callable[[WebDriver], bool]: """Wait until an element is no longer attached to the DOM. element is the element to wait for. returns False if the element is @@ -378,7 +377,7 @@ def _predicate(_): return _predicate -def element_to_be_selected(element: WebElement) -> Callable[[AnyDriver], bool]: +def element_to_be_selected(element: WebElement) -> Callable[[WebDriver], bool]: """An expectation for checking the selection is selected. element is WebElement object @@ -390,19 +389,19 @@ def _predicate(_): return _predicate -def element_located_to_be_selected(locator: Tuple[str, str]) -> Callable[[AnyDriver], bool]: +def element_located_to_be_selected(locator: Tuple[str, str]) -> Callable[[WebDriver], bool]: """An expectation for the element to be located is selected. locator is a tuple of (by, path) """ - def _predicate(driver): + def _predicate(driver: WebDriver): return driver.find_element(*locator).is_selected() return _predicate -def element_selection_state_to_be(element: WebElement, is_selected: bool) -> Callable[[AnyDriver], bool]: +def element_selection_state_to_be(element: WebElement, is_selected: bool) -> Callable[[WebDriver], bool]: """An expectation for checking if the given element is selected. element is WebElement object is_selected is a Boolean. @@ -414,14 +413,14 @@ def _predicate(_): return _predicate -def element_located_selection_state_to_be(locator: Tuple[str, str], is_selected: bool) -> Callable[[AnyDriver], bool]: +def element_located_selection_state_to_be(locator: Tuple[str, str], is_selected: bool) -> Callable[[WebDriver], bool]: """An expectation to locate an element and check if the selection state specified is in that state. locator is a tuple of (by, path) is_selected is a boolean """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: element = driver.find_element(*locator) return element.is_selected() == is_selected @@ -431,30 +430,30 @@ def _predicate(driver): return _predicate -def number_of_windows_to_be(num_windows: int) -> Callable[[AnyDriver], bool]: +def number_of_windows_to_be(num_windows: int) -> Callable[[WebDriver], bool]: """An expectation for the number of windows to be a certain value.""" - def _predicate(driver): + def _predicate(driver: WebDriver): return len(driver.window_handles) == num_windows return _predicate -def new_window_is_opened(current_handles: List[str]) -> Callable[[AnyDriver], bool]: +def new_window_is_opened(current_handles: List[str]) -> Callable[[WebDriver], bool]: """An expectation that a new window will be opened and have the number of windows handles increase.""" - def _predicate(driver): + def _predicate(driver: WebDriver): return len(driver.window_handles) > len(current_handles) return _predicate -def alert_is_present() -> Callable[[AnyDriver], Union[Alert, bool]]: +def alert_is_present() -> Callable[[WebDriver], Union[Alert, Literal[False]]]: """An expectation for checking if an alert is currently present and switching to it.""" - def _predicate(driver): + def _predicate(driver: WebDriver): try: return driver.switch_to.alert except NoAlertPresentException: @@ -463,14 +462,14 @@ def _predicate(driver): return _predicate -def element_attribute_to_include(locator: Tuple[str, str], attribute_: str) -> Callable[[AnyDriver], bool]: +def element_attribute_to_include(locator: Tuple[str, str], attribute_: str) -> Callable[[WebDriver], bool]: """An expectation for checking if the given attribute is included in the specified element. locator, attribute """ - def _predicate(driver): + def _predicate(driver: WebDriver): try: element_attribute = driver.find_element(*locator).get_attribute(attribute_) return element_attribute is not None @@ -480,14 +479,14 @@ def _predicate(driver): return _predicate -def any_of(*expected_conditions) -> Callable[[AnyDriver], Union[WebElement, bool]]: +def any_of(*expected_conditions: Callable[[WebDriver], T]) -> Callable[[WebDriver], Union[Literal[False], T]]: """An expectation that any of multiple expected conditions is true. Equivalent to a logical 'OR'. Returns results of the first matching condition, or False if none do. """ - def any_of_condition(driver): + def any_of_condition(driver: WebDriver): for expected_condition in expected_conditions: try: result = expected_condition(driver) @@ -500,7 +499,7 @@ def any_of_condition(driver): return any_of_condition -def all_of(*expected_conditions) -> Callable[[AnyDriver], Union[WebElement, bool]]: +def all_of(*expected_conditions: Callable[[WebDriver], Union[T, Literal[False]]]) -> Callable[[WebDriver], Union[List[T], Literal[False]]]: """An expectation that all of multiple expected conditions is true. Equivalent to a logical 'AND'. @@ -508,8 +507,8 @@ def all_of(*expected_conditions) -> Callable[[AnyDriver], Union[WebElement, bool When all ExpectedConditions are met: A List with each ExpectedCondition's return value. """ - def all_of_condition(driver): - results = [] + def all_of_condition(driver: WebDriver): + results: List[T] = [] for expected_condition in expected_conditions: try: result = expected_condition(driver) @@ -523,13 +522,13 @@ def all_of_condition(driver): return all_of_condition -def none_of(*expected_conditions) -> Callable[[AnyDriver], bool]: +def none_of(*expected_conditions: Callable[[WebDriver], Any]) -> Callable[[WebDriver], bool]: """An expectation that none of 1 or multiple expected conditions is true. Equivalent to a logical 'NOT-OR'. Returns a Boolean """ - def none_of_condition(driver): + def none_of_condition(driver: WebDriver): for expected_condition in expected_conditions: try: result = expected_condition(driver) From baf435335b072f67b81933a0ce0260d92024951a Mon Sep 17 00:00:00 2001 From: pinterior Date: Mon, 30 Oct 2023 22:21:17 +0900 Subject: [PATCH 2/4] add type hint to s.w.support.wait --- py/selenium/webdriver/support/wait.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index d8a848a23e95c..73478e5c51c2f 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -17,19 +17,25 @@ import time import typing +from typing import Callable +from typing import Union +from typing import Literal from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import TimeoutException +from selenium.webdriver.remote.webdriver import WebDriver from selenium.types import WaitExcTypes POLL_FREQUENCY: float = 0.5 # How long to sleep in between calls to the method IGNORED_EXCEPTIONS: typing.Tuple[typing.Type[Exception]] = (NoSuchElementException,) # default to be ignored. +T = typing.TypeVar("T") + class WebDriverWait: def __init__( self, - driver, + driver: WebDriver, timeout: float, poll_frequency: float = POLL_FREQUENCY, ignored_exceptions: typing.Optional[WaitExcTypes] = None, @@ -68,7 +74,7 @@ def __init__( def __repr__(self): return f'<{type(self).__module__}.{type(self).__name__} (session="{self._driver.session_id}")>' - def until(self, method, message: str = ""): + def until(self, method: Callable[[WebDriver], Union[Literal[False], T]], message: str = "") -> T: """Calls the method provided with the driver as an argument until the \ return value does not evaluate to ``False``. @@ -94,7 +100,7 @@ def until(self, method, message: str = ""): break raise TimeoutException(message, screen, stacktrace) - def until_not(self, method, message: str = ""): + def until_not(self, method: Callable[[WebDriver], T], message: str = "") -> Union[T, Literal[True]]: """Calls the method provided with the driver as an argument until the \ return value evaluates to ``False``. From a76a8c0b4f62253015f92f3c20528eeea57e532b Mon Sep 17 00:00:00 2001 From: pinterior Date: Mon, 30 Oct 2023 22:38:46 +0900 Subject: [PATCH 3/4] trivial changes for type checking --- py/selenium/webdriver/support/expected_conditions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index 5ddfc6ebc3223..d598e6e3a4bda 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -16,6 +16,7 @@ # under the License. import re +from collections.abc import Iterable from typing import Any from typing import Callable from typing import List @@ -290,7 +291,7 @@ def frame_to_be_available_and_switch_to_it(locator: Union[Tuple[str, str], str]) def _predicate(driver: WebDriver): try: - if hasattr(locator, "__iter__") and not isinstance(locator, str): + if isinstance(locator, Iterable) and not isinstance(locator, str): driver.switch_to.frame(driver.find_element(*locator)) else: driver.switch_to.frame(locator) @@ -351,9 +352,9 @@ def _predicate(driver: WebDriver): target = mark if not isinstance(target, WebElement): # if given locator instead of WebElement target = driver.find_element(*target) # grab element at locator - target = visibility_of(target)(driver) - if target and target.is_enabled(): - return target + element = visibility_of(target)(driver) + if element and element.is_enabled(): + return element return False return _predicate From 68cd6078ade18fed7acdecae0d839da94f31d805 Mon Sep 17 00:00:00 2001 From: pinterior Date: Tue, 31 Oct 2023 01:50:50 +0900 Subject: [PATCH 4/4] linting --- py/selenium/webdriver/support/expected_conditions.py | 11 +++++++---- py/selenium/webdriver/support/wait.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index d598e6e3a4bda..6f84a4db27819 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -34,7 +34,6 @@ from selenium.webdriver.remote.webdriver import WebDriver from selenium.webdriver.remote.webdriver import WebElement - T = TypeVar("T") """ * Canned "Expected Conditions" which are generally useful within webdriver @@ -311,7 +310,7 @@ def invisibility_of_element_located( locator used to find the element """ - def _predicate(driver :WebDriver): + def _predicate(driver: WebDriver): try: target = locator if not isinstance(target, WebElement): @@ -339,7 +338,9 @@ def invisibility_of_element( return invisibility_of_element_located(element) -def element_to_be_clickable(mark: Union[WebElement, Tuple[str, str]]) -> Callable[[WebDriver], Union[Literal[False], WebElement]]: +def element_to_be_clickable( + mark: Union[WebElement, Tuple[str, str]] +) -> Callable[[WebDriver], Union[Literal[False], WebElement]]: """An Expectation for checking an element is visible and enabled such that you can click it. @@ -500,7 +501,9 @@ def any_of_condition(driver: WebDriver): return any_of_condition -def all_of(*expected_conditions: Callable[[WebDriver], Union[T, Literal[False]]]) -> Callable[[WebDriver], Union[List[T], Literal[False]]]: +def all_of( + *expected_conditions: Callable[[WebDriver], Union[T, Literal[False]]] +) -> Callable[[WebDriver], Union[List[T], Literal[False]]]: """An expectation that all of multiple expected conditions is true. Equivalent to a logical 'AND'. diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index 73478e5c51c2f..3c453c3032cfe 100644 --- a/py/selenium/webdriver/support/wait.py +++ b/py/selenium/webdriver/support/wait.py @@ -18,13 +18,13 @@ import time import typing from typing import Callable -from typing import Union from typing import Literal +from typing import Union from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import TimeoutException -from selenium.webdriver.remote.webdriver import WebDriver from selenium.types import WaitExcTypes +from selenium.webdriver.remote.webdriver import WebDriver POLL_FREQUENCY: float = 0.5 # How long to sleep in between calls to the method IGNORED_EXCEPTIONS: typing.Tuple[typing.Type[Exception]] = (NoSuchElementException,) # default to be ignored.