diff --git a/src/widgetastic/browser.py b/src/widgetastic/browser.py index e1985bc8..1740a489 100644 --- a/src/widgetastic/browser.py +++ b/src/widgetastic/browser.py @@ -21,9 +21,9 @@ from .exceptions import ( NoSuchElementException, UnexpectedAlertPresentException, MoveTargetOutOfBoundsException, StaleElementReferenceException, NoAlertPresentException, LocatorNotImplemented) -from .log import create_widget_logger, null_logger +from .log import create_widget_logger, logged, null_logger +from .utils import repeat_once_on_exceptions from .xpath import normalize_space -from .utils import crop_string_middle # TODO: Resolve this issue in smartloc @@ -269,6 +269,7 @@ def elements( return result + @repeat_once_on_exceptions(NoSuchElementException, check_safe=True) def element(self, locator, *args, **kwargs): """Returns one :py:class:`selenium.webdriver.remote.webelement.WebElement` @@ -280,6 +281,8 @@ def element(self, locator, *args, **kwargs): Raises: :py:class:`selenium.common.exceptions.NoSuchElementException` """ + if 'check_safe' not in kwargs: + kwargs['check_safe'] = False try: vcheck = self._locator_force_visibility_check(locator) if vcheck is not None: @@ -294,64 +297,64 @@ def element(self, locator, *args, **kwargs): else: return elements[0] except IndexError: - raise NoSuchElementException('Could not find an element {}'.format(repr(locator))) + raise NoSuchElementException('Could not find an element {!r}'.format(locator)) - def perform_click(self): + def perform_click(self, ignore_ajax=False): """Clicks the left mouse button at the current mouse position.""" ActionChains(self.selenium).click().perform() + if not ignore_ajax: + try: + self.plugin.ensure_page_safe() + except UnexpectedAlertPresentException: + pass - def perform_double_click(self): + def perform_double_click(self, ignore_ajax=False): """Double-clicks the left mouse button at the current mouse position.""" ActionChains(self.selenium).double_click().perform() + if not ignore_ajax: + try: + self.plugin.ensure_page_safe() + except UnexpectedAlertPresentException: + pass + @logged(log_args=True, only_after=True, debug_only=True, log_full_exception=False) def click(self, locator, *args, **kwargs): """Clicks at a specific element using two separate events (mouse move, mouse click). Args: See :py:meth:`elements` """ - self.logger.debug('click: %r', locator) ignore_ajax = kwargs.pop('ignore_ajax', False) el = self.move_to_element(locator, *args, **kwargs) self.plugin.before_click(el) # and then click on current mouse position - self.perform_click() - if not ignore_ajax: - try: - self.plugin.ensure_page_safe() - except UnexpectedAlertPresentException: - pass + self.perform_click(ignore_ajax) try: self.plugin.after_click(el) except (StaleElementReferenceException, UnexpectedAlertPresentException): pass + @logged(log_args=True, only_after=True, debug_only=True, log_full_exception=False) def double_click(self, locator, *args, **kwargs): """Double-clicks at a specific element using two separate events (mouse move, mouse click). Args: See :py:meth:`elements` """ - self.logger.debug('double_click: %r', locator) ignore_ajax = kwargs.pop('ignore_ajax', False) el = self.move_to_element(locator, *args, **kwargs) self.plugin.before_click(el) # and then click on current mouse position - self.perform_double_click() - if not ignore_ajax: - try: - self.plugin.ensure_page_safe() - except UnexpectedAlertPresentException: - pass + self.perform_double_click(ignore_ajax) try: self.plugin.after_click(el) except (StaleElementReferenceException, UnexpectedAlertPresentException): pass + @logged(log_args=True, only_after=True, debug_only=True, log_full_exception=False) def raw_click(self, locator, *args, **kwargs): """Clicks at a specific element using the direct event. Args: See :py:meth:`elements` """ - self.logger.debug('raw_click: %r', locator) ignore_ajax = kwargs.pop('ignore_ajax', False) el = self.element(locator, *args, **kwargs) self.plugin.before_click(el) @@ -377,11 +380,20 @@ def is_displayed(self, locator, *args, **kwargs): kwargs['check_visibility'] = False retry = True tries = 10 + checked_page_safe = False while retry: retry = False try: return self.move_to_element(locator, *args, **kwargs).is_displayed() - except (NoSuchElementException, MoveTargetOutOfBoundsException): + except NoSuchElementException: + self.logger.debug( + 'is_displayed(%r): Element could not be found on the page', + locator) + return False + except MoveTargetOutOfBoundsException: + self.logger.debug( + 'is_displayed(%r): Could not scroll to the element', + locator) return False except StaleElementReferenceException: if isinstance(locator, WebElement) or tries <= 0: @@ -389,11 +401,24 @@ def is_displayed(self, locator, *args, **kwargs): raise retry = True tries -= 1 - time.sleep(0.1) + # Before looping again ... + time.sleep(0.1) + if not checked_page_safe: + # Check if hte page is safe ONCE + self.plugin.ensure_page_safe() + checked_page_safe = True + + self.logger.warning( + 'is_displayed(%r): Most likely continuous stale element reference.', + locator) # Just in case return False + @logged(log_args=True, only_after=True, debug_only=True, log_full_exception=False) + @repeat_once_on_exceptions( + StaleElementReferenceException, MoveTargetOutOfBoundsException, + check_safe=True) def move_to_element(self, locator, *args, **kwargs): """Moves the mouse cursor to the middle of the element represented by the locator. @@ -402,23 +427,31 @@ def move_to_element(self, locator, *args, **kwargs): Args: See :py:meth:`elements` + Keywords: + workaround: Default True, tells whether it can or can not perform the JS workaround. + Returns: :py:class:`selenium.webdriver.remote.webelement.WebElement` """ - self.logger.debug('move_to_element: %r', locator) + if 'check_safe' not in kwargs: + kwargs['check_safe'] = False + workaround = kwargs.pop('workaround', True) el = self.element(locator, *args, **kwargs) if el.tag_name == "option": # Instead of option, let's move on its parent