From 00ef684bde24bbc0c526c63bb14bb6ebf41df2b7 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Thu, 18 Feb 2021 23:31:51 -0500 Subject: [PATCH] Improve the error output for incorrect usage of page objects --- seleniumbase/common/exceptions.py | 5 + seleniumbase/fixtures/base_case.py | 208 +++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) diff --git a/seleniumbase/common/exceptions.py b/seleniumbase/common/exceptions.py index dc1edc8c..60b8aed0 100755 --- a/seleniumbase/common/exceptions.py +++ b/seleniumbase/common/exceptions.py @@ -1,5 +1,6 @@ """ SeleniumBase Exceptions NoSuchFileException => Used by self.assert_downloaded_file(...) + OutOfScopeException => Used by BaseCase methods when setUp() is skipped TimeLimitExceededException => Used by "--time-limit=SECONDS" """ @@ -8,5 +9,9 @@ class NoSuchFileException(Exception): pass +class OutOfScopeException(Exception): + pass + + class TimeLimitExceededException(Exception): pass diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 7ee807e1..4dd96c6b 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -99,6 +99,7 @@ class BaseCase(unittest.TestCase): def open(self, url): """ Navigates the current browser window to the specified page. """ + self.__check_scope() if type(url) is str: url = url.strip() # Remove leading and trailing whitespace if (type(url) is not str) or not self.__looks_like_a_page_url(url): @@ -124,12 +125,14 @@ class BaseCase(unittest.TestCase): self.get("https://seleniumbase.io") # Navigates to the URL self.get("input.class") # Finds and returns the WebElement """ + self.__check_scope() if self.__looks_like_a_page_url(url): self.open(url) else: return self.get_element(url) # url is treated like a selector def click(self, selector, by=By.CSS_SELECTOR, timeout=None, delay=0): + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -210,6 +213,7 @@ class BaseCase(unittest.TestCase): To set the user-agent, use: ``--agent=AGENT``. Here's an example message from GitHub's bot-blocker: ``You have triggered an abuse detection mechanism...`` """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -224,6 +228,7 @@ class BaseCase(unittest.TestCase): def double_click(self, selector, by=By.CSS_SELECTOR, timeout=None): from selenium.webdriver.common.action_chains import ActionChains + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -276,6 +281,7 @@ class BaseCase(unittest.TestCase): timeout=None, spacing=0): """ This method clicks on a list of elements in succession. 'spacing' is the amount of time to wait between clicks. (sec) """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -301,6 +307,7 @@ class BaseCase(unittest.TestCase): timeout - how long to wait for the selector to be visible retry - if True, use JS if the Selenium text update fails """ + self.__check_scope() if not timeout: timeout = settings.LARGE_TIMEOUT if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: @@ -371,6 +378,7 @@ class BaseCase(unittest.TestCase): def add_text(self, selector, text, by=By.CSS_SELECTOR, timeout=None): """ The more-reliable version of driver.send_keys() Similar to update_text(), but won't clear the text field first. """ + self.__check_scope() if not timeout: timeout = settings.LARGE_TIMEOUT if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: @@ -433,6 +441,7 @@ class BaseCase(unittest.TestCase): retry - if True, use JS if the Selenium text update fails DO NOT confuse self.type() with Python type()! They are different! """ + self.__check_scope() if not timeout: timeout = settings.LARGE_TIMEOUT if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: @@ -442,6 +451,7 @@ class BaseCase(unittest.TestCase): def submit(self, selector, by=By.CSS_SELECTOR): """ Alternative to self.driver.find_element_by_*(SELECTOR).submit() """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) element = self.wait_for_element_visible( selector, by=by, timeout=settings.SMALL_TIMEOUT) @@ -462,6 +472,7 @@ class BaseCase(unittest.TestCase): by - the type of selector to search by (Default: CSS Selector) timeout - how long to wait for the selector to be visible """ + self.__check_scope() if not timeout: timeout = settings.LARGE_TIMEOUT if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT: @@ -489,6 +500,7 @@ class BaseCase(unittest.TestCase): element.clear() def refresh_page(self): + self.__check_scope() self.__last_page_load_url = None js_utils.clear_out_console_logs(self.driver) self.driver.refresh() @@ -499,6 +511,7 @@ class BaseCase(unittest.TestCase): self.refresh_page() def get_current_url(self): + self.__check_scope() current_url = self.driver.current_url if "%" in current_url and sys.version_info[0] >= 3: try: @@ -523,15 +536,18 @@ class BaseCase(unittest.TestCase): return self.get_page_title() def get_user_agent(self): + self.__check_scope() user_agent = self.driver.execute_script("return navigator.userAgent;") return user_agent def get_locale_code(self): + self.__check_scope() locale_code = self.driver.execute_script( "return navigator.language || navigator.languages[0];") return locale_code def go_back(self): + self.__check_scope() self.__last_page_load_url = None if self.browser != "safari": self.driver.back() @@ -543,6 +559,7 @@ class BaseCase(unittest.TestCase): self.__demo_mode_pause_if_active() def go_forward(self): + self.__check_scope() self.__last_page_load_url = None if self.browser != "safari": self.driver.forward() @@ -554,10 +571,12 @@ class BaseCase(unittest.TestCase): self.__demo_mode_pause_if_active() def is_element_present(self, selector, by=By.CSS_SELECTOR): + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) return page_actions.is_element_present(self.driver, selector, by) def is_element_visible(self, selector, by=By.CSS_SELECTOR): + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) return page_actions.is_element_visible(self.driver, selector, by) @@ -583,6 +602,7 @@ class BaseCase(unittest.TestCase): """ Returns True if the link text appears in the HTML of the page. The element doesn't need to be visible, such as elements hidden inside a dropdown selection. """ + self.__check_scope() soup = self.get_beautiful_soup() html_links = soup.find_all('a') for html_link in html_links: @@ -594,6 +614,7 @@ class BaseCase(unittest.TestCase): """ Returns True if the partial link appears in the HTML of the page. The element doesn't need to be visible, such as elements hidden inside a dropdown selection. """ + self.__check_scope() soup = self.get_beautiful_soup() html_links = soup.find_all('a') for html_link in html_links: @@ -605,6 +626,7 @@ class BaseCase(unittest.TestCase): """ Finds a link by link text and then returns the attribute's value. If the link text or attribute cannot be found, an exception will get raised if hard_fail is True (otherwise None is returned). """ + self.__check_scope() soup = self.get_beautiful_soup() html_links = soup.find_all('a') for html_link in html_links: @@ -636,6 +658,7 @@ class BaseCase(unittest.TestCase): value. If the partial link text or attribute cannot be found, an exception will get raised if hard_fail is True (otherwise None is returned). """ + self.__check_scope() soup = self.get_beautiful_soup() html_links = soup.find_all('a') for html_link in html_links: @@ -659,6 +682,7 @@ class BaseCase(unittest.TestCase): def click_link_text(self, link_text, timeout=None): """ This method clicks link text on a page """ # If using phantomjs, might need to extract and open the link directly + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -760,6 +784,7 @@ class BaseCase(unittest.TestCase): def click_partial_link_text(self, partial_link_text, timeout=None): """ This method clicks the partial link text on a page. """ # If using phantomjs, might need to extract and open the link directly + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -864,6 +889,7 @@ class BaseCase(unittest.TestCase): self.__slow_mode_pause_if_active() def get_text(self, selector, by=By.CSS_SELECTOR, timeout=None): + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -886,6 +912,7 @@ class BaseCase(unittest.TestCase): def get_attribute(self, selector, attribute, by=By.CSS_SELECTOR, timeout=None, hard_fail=True): """ This method uses JavaScript to get the value of an attribute. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -916,6 +943,7 @@ class BaseCase(unittest.TestCase): timeout=None): """ This method uses JavaScript to set/update an attribute. Only the first matching selector from querySelector() is used. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -942,6 +970,7 @@ class BaseCase(unittest.TestCase): All matching selectors from querySelectorAll() are used. Example => (Make all links on a website redirect to Google): self.set_attributes("a", "href", "https://google.com") """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) attribute = re.escape(attribute) attribute = self.__escape_quotes_if_needed(attribute) @@ -973,6 +1002,7 @@ class BaseCase(unittest.TestCase): timeout=None): """ This method uses JavaScript to remove an attribute. Only the first matching selector from querySelector() is used. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -995,6 +1025,7 @@ class BaseCase(unittest.TestCase): def remove_attributes(self, selector, attribute, by=By.CSS_SELECTOR): """ This method uses JavaScript to remove a common attribute. All matching selectors from querySelectorAll() are used. """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) attribute = re.escape(attribute) attribute = self.__escape_quotes_if_needed(attribute) @@ -1017,6 +1048,7 @@ class BaseCase(unittest.TestCase): Example: opacity = self.get_property_value("html body a", "opacity") self.assertTrue(float(opacity) > 0, "Element not visible!") """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1046,6 +1078,7 @@ class BaseCase(unittest.TestCase): def get_image_url(self, selector, by=By.CSS_SELECTOR, timeout=None): """ Extracts the URL from an image element on the page. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1085,6 +1118,7 @@ class BaseCase(unittest.TestCase): Also clicks elements that become visible from previous clicks. Works best for actions such as clicking all checkboxes on a page. Example: self.click_visible_elements('input[type="checkbox"]') """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1144,6 +1178,7 @@ class BaseCase(unittest.TestCase): """ Finds all matching page elements and clicks the nth visible one. Example: self.click_nth_visible_element('[type="checkbox"]', 5) (Clicks the 5th visible checkbox on the page.) """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1182,6 +1217,7 @@ class BaseCase(unittest.TestCase): Returns False if the element is not checked. If the element is not present on the page, raises an exception. If the element is not a checkbox or radio, raises an exception. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1203,6 +1239,7 @@ class BaseCase(unittest.TestCase): def check_if_unchecked(self, selector, by=By.CSS_SELECTOR): """ If a checkbox or radio button is not checked, will check it. """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) if not self.is_checked(selector, by=by): if self.is_element_visible(selector, by=by): @@ -1217,6 +1254,7 @@ class BaseCase(unittest.TestCase): def uncheck_if_checked(self, selector, by=By.CSS_SELECTOR): """ If a checkbox is checked, will uncheck it. """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) if self.is_checked(selector, by=by): if self.is_element_visible(selector, by=by): @@ -1232,6 +1270,7 @@ class BaseCase(unittest.TestCase): def is_element_in_an_iframe(self, selector, by=By.CSS_SELECTOR): """ Returns True if the selector's element is located in an iframe. Otherwise returns False. """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) if self.is_element_present(selector, by=by): return False @@ -1260,6 +1299,7 @@ class BaseCase(unittest.TestCase): element is in a single-nested iframe) and returns the iframe name. If element is not in an iframe, returns None, and nothing happens. May not work if multiple iframes are nested within each other. """ + self.__check_scope() selector, by = self.__recalculate_selector(selector, by) if self.is_element_present(selector, by=by): return None @@ -1293,6 +1333,7 @@ class BaseCase(unittest.TestCase): "element {%s}!" % selector) def hover_on_element(self, selector, by=By.CSS_SELECTOR): + self.__check_scope() original_selector = selector original_by = by selector, by = self.__recalculate_selector(selector, by) @@ -1341,6 +1382,7 @@ class BaseCase(unittest.TestCase): timeout=None): """ When you want to hover over an element or dropdown menu, and then click an element that appears after that. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1404,6 +1446,7 @@ class BaseCase(unittest.TestCase): timeout=None): """ When you want to hover over an element or dropdown menu, and then double-click an element that appears after that. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1454,6 +1497,7 @@ class BaseCase(unittest.TestCase): drag_by=By.CSS_SELECTOR, drop_by=By.CSS_SELECTOR, timeout=None): """ Drag and drop an element from one selector to another. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1488,6 +1532,7 @@ class BaseCase(unittest.TestCase): def drag_and_drop_with_offset( self, selector, x, y, by=By.CSS_SELECTOR, timeout=None): """ Drag and drop an element to an {X,Y}-offset location. """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1514,6 +1559,7 @@ class BaseCase(unittest.TestCase): Option specifications are by "text", "index", or "value". Defaults to "text" if option_by is unspecified or unknown. """ from selenium.webdriver.support.ui import Select + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1562,6 +1608,7 @@ class BaseCase(unittest.TestCase): @Params dropdown_selector - the selector option - the index number of the option """ + self.__check_scope() if not timeout: timeout = settings.SMALL_TIMEOUT if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT: @@ -1592,6 +1640,7 @@ class BaseCase(unittest.TestCase): @Params dropdown_selector - the