diff --git a/py/selenium/webdriver/support/expected_conditions.py b/py/selenium/webdriver/support/expected_conditions.py index 65006552996dc..6f84a4db27819 100644 --- a/py/selenium/webdriver/support/expected_conditions.py +++ b/py/selenium/webdriver/support/expected_conditions.py @@ -16,9 +16,13 @@ # under the License. import re +from collections.abc import Iterable +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 +30,31 @@ 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,9 +288,9 @@ 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): + if isinstance(locator, Iterable) and not isinstance(locator, str): driver.switch_to.frame(driver.find_element(*locator)) else: driver.switch_to.frame(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,9 @@ 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,19 +349,19 @@ 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 - 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 -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 +379,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 +391,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 +415,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 +432,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 +464,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 +481,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 +501,9 @@ 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 +511,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 +526,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) diff --git a/py/selenium/webdriver/support/wait.py b/py/selenium/webdriver/support/wait.py index d8a848a23e95c..3c453c3032cfe 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 Literal +from typing import Union from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import TimeoutException 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. +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``.