From 041b165bfab935677251a7cafdb0676633433587 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 20 Jun 2022 17:40:47 -0400 Subject: [PATCH] Add method: "wait_for_element_clickable()" --- help_docs/method_summary.md | 4 + seleniumbase/fixtures/base_case.py | 22 ++++++ seleniumbase/fixtures/page_actions.py | 105 ++++++++++++++++++++++++++ 3 files changed, 131 insertions(+) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index 0e0f4bfb..4685c7f6 100755 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -701,6 +701,10 @@ self.assert_element_absent(selector, by="css selector", timeout=None) ############ +self.wait_for_element_clickable(selector, by="css selector", timeout=None) + +############ + self.wait_for_element_not_visible(selector, by="css selector", timeout=None) self.assert_element_not_visible(selector, by="css selector", timeout=None) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 82ece98c..cf5081fa 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -7217,6 +7217,28 @@ class BaseCase(unittest.TestCase): original_selector=original_selector, ) + def wait_for_element_clickable( + self, selector, by="css selector", timeout=None + ): + """Waits for the element to be clickable, but does NOT click it.""" + self.__check_scope() + if not timeout: + timeout = settings.LARGE_TIMEOUT + if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: + timeout = self.__get_new_timeout(timeout) + original_selector = selector + selector, by = self.__recalculate_selector(selector, by) + if self.__is_shadow_selector(selector): + # If a shadow selector, use visible instead of clickable + return self.__wait_for_shadow_element_visible(selector, timeout) + return page_actions.wait_for_element_clickable( + self.driver, + selector, + by, + timeout=timeout, + original_selector=original_selector, + ) + def wait_for_element_not_present( self, selector, by="css selector", timeout=None ): diff --git a/seleniumbase/fixtures/page_actions.py b/seleniumbase/fixtures/page_actions.py index 50f411e5..00a0f049 100755 --- a/seleniumbase/fixtures/page_actions.py +++ b/seleniumbase/fixtures/page_actions.py @@ -707,6 +707,111 @@ def wait_for_attribute( return element +def wait_for_element_clickable( + driver, + selector, + by="css selector", + timeout=settings.LARGE_TIMEOUT, + original_selector=None, +): + """ + Searches for the specified element by the given selector. Returns the + element object if the element is present, visible, & clickable on the page. + Raises NoSuchElementException if the element does not exist in the HTML + within the specified timeout. + Raises ElementNotVisibleException if the element exists in the HTML, + but is not visible (eg. opacity is "0") within the specified timeout. + Raises ElementNotInteractableException if the element is not clickable. + @Params + driver - the webdriver object (required) + selector - the locator for identifying the page element (required) + by - the type of selector being used (Default: By.CSS_SELECTOR) + timeout - the time to wait for elements in seconds + original_selector - handle pre-converted ":contains(TEXT)" selector + @Returns + A web element object + """ + from selenium.webdriver.support import expected_conditions as EC + from selenium.webdriver.support.ui import WebDriverWait + + element = None + is_present = False + is_visible = False + start_ms = time.time() * 1000.0 + stop_ms = start_ms + (timeout * 1000.0) + for x in range(int(timeout * 10)): + shared_utils.check_if_time_limit_exceeded() + try: + element = driver.find_element(by=by, value=selector) + is_present = True + if element.is_displayed(): + is_visible = True + if WebDriverWait(driver, 0.001).until( + EC.element_to_be_clickable((by, selector)) + ): + return element + else: + element = None + raise Exception() + else: + element = None + raise Exception() + except Exception: + now_ms = time.time() * 1000.0 + if now_ms >= stop_ms: + break + time.sleep(0.1) + plural = "s" + if timeout == 1: + plural = "" + if not element and by != By.LINK_TEXT: + if ( + original_selector + and ":contains(" in original_selector + and "contains(." in selector + ): + selector = original_selector + if not is_present: + # The element does not exist in the HTML + message = "Element {%s} was not present after %s second%s!" % ( + selector, + timeout, + plural, + ) + timeout_exception(NoSuchElementException, message) + if not is_visible: + # The element exists in the HTML, but is not visible + message = "Element {%s} was not visible after %s second%s!" % ( + selector, + timeout, + plural, + ) + timeout_exception(ElementNotVisibleException, message) + # The element is visible in the HTML, but is not clickable + message = "Element {%s} was not clickable after %s second%s!" % ( + selector, + timeout, + plural, + ) + timeout_exception(ElementNotInteractableException, message) + elif not element and by == By.LINK_TEXT and not is_visible: + message = "Link text {%s} was not visible after %s second%s!" % ( + selector, + timeout, + plural, + ) + timeout_exception(ElementNotVisibleException, message) + elif not element and by == By.LINK_TEXT and is_visible: + message = "Link text {%s} was not clickable after %s second%s!" % ( + selector, + timeout, + plural, + ) + timeout_exception(ElementNotInteractableException, message) + else: + return element + + def wait_for_element_absent( driver, selector,