Source code for capybara.node.base

from functools import wraps
from time import sleep

import capybara
from capybara.exceptions import ElementNotFound, FrozenInTime, ScopeError
from capybara.helpers import Timer
from capybara.node.actions import ActionsMixin
from capybara.node.finders import FindersMixin
from capybara.node.matchers import MatchersMixin


[docs]class Base(FindersMixin, ActionsMixin, MatchersMixin, object): """ A :class:`Base` represents either an element on a page through the subclass :class:`Element` or a document through :class:`Document`. Both types of Node share the same methods, used for interacting with the elements on the page. These methods are divided into three categories: finders, actions, and matchers. These are found in the classes :class:`FindersMixin`, :class:`ActionsMixin`, and :class:`MatchersMixin` respectively. A :class:`Session` exposes all methods from :class:`Document` directly:: session = Session("selenium", my_app) session.visit("/") session.fill_in("Foo", value="Bar") # from capybara.node.actions.ActionsMixin bar = session.find("#bar") # from capybara.node.finders.FindersMixin bar.select("Baz", field="Quox") # from capybara.node.actions.ActionsMixin session.has_css("#foobar") # from capybara.node.matchers.MatchersMixin Args: session (Session): The session from which this node originated. base (driver.Node): The underlying driver node. """ def __init__(self, session, base): self.session = session self.base = base self.allow_reload = False self._contexts = []
[docs] def reload(self): """ Reloads the underlying driver node. Returns: node.Base: This node. """ return self
def __eq__(self, other): return id(self) == id(other) or (hasattr(other, "base") and self.base == other.base) def __hash__(self): return hash(self.base) def __getitem__(self, name): """ Retrieve the given attribute. Args: name (str): The attribute to retrieve. Returns: str: The value of the attribute. """ raise NotImplementedError() def __enter__(self): context = ( self.session.frame(self) if self.tag_name in {"frame", "iframe"} else self.session.scope(self)) self._contexts.append(context) return context.__enter__() def __exit__(self, *args): context = self._contexts.pop() context.__exit__(*args) @property def text(self): """ str: The text of the node. """ raise NotImplementedError() @property def all_text(self): """ str: All of the text of the node. """ raise NotImplementedError() @property def visible_text(self): """ str: Only the visible text of the node. """ raise NotImplementedError()
[docs] def synchronize(self, func=None, wait=None, errors=()): """ This method is Capybara's primary defense against asynchronicity problems. It works by attempting to run a given decorated function until it succeeds. The exact behavior of this method depends on a number of factors. Basically there are certain exceptions which, when raised from the decorated function, instead of bubbling up, are caught, and the function is re-run. Certain drivers have no support for asynchronous processes. These drivers run the function, and any error raised bubbles up immediately. This allows faster turn around in the case where an expectation fails. Only exceptions that are :exc:`ElementNotFound` or any subclass thereof cause the block to be rerun. Drivers may specify additional exceptions which also cause reruns. This usually occurs when a node is manipulated which no longer exists on the page. For example, the Selenium driver specifies ``selenium.common.exceptions.StateElementReferenceException``. As long as any of these exceptions are thrown, the function is re-run, until a certain amount of time passes. The amount of time defaults to :data:`capybara.default_max_wait_time` and can be overridden through the ``wait`` argument. This time is compared with the system time to see how much time has passed. If the return value of ``time.time()`` is stubbed out, Capybara will raise :exc:`FrozenInTime`. Args: func (Callable, optional): The function to decorate. wait (int, optional): Number of seconds to retry this function. errors (Tuple[Type[Exception]], optional): Exception types that cause the function to be rerun. Defaults to ``driver.invalid_element_errors`` + :exc:`ElementNotFound`. Returns: Callable: The decorated function, or a decorator function. Raises: FrozenInTime: If the return value of ``time.time()`` appears stuck. """ def decorator(func): @wraps(func) def outer(*args, **kwargs): seconds = wait if wait is not None else capybara.default_max_wait_time def inner(): return func(*args, **kwargs) if self.session.synchronized: return inner() else: timer = Timer(seconds) self.session.synchronized = True try: while True: try: return inner() except Exception as e: self.session.raise_server_error() if not self._should_catch_error(e, errors): raise if timer.expired: raise sleep(0.05) if timer.stalled: raise FrozenInTime( "time appears to be frozen, Capybara does not work with " "libraries which freeze time, consider using time " "traveling instead") if capybara.automatic_reload: self.reload() finally: self.session.synchronized = False return outer if func: return decorator(func) else: return decorator
def _should_catch_error(self, error, errors=()): """ Returns whether to catch the given error. Args: error (Exception): The error to consider. errors (Tuple[Type[Exception], ...], optional): The exception types that should be caught. Defaults to :class:`ElementNotFound` plus any driver-specific invalid element errors. Returns: bool: Whether to catch the given error. """ caught_errors = ( errors or self.session.driver.invalid_element_errors + (ElementNotFound,)) return isinstance(error, caught_errors) def _find_css(self, css): return self.base._find_css(css) def _find_xpath(self, xpath): return self.base._find_xpath(xpath)
[docs]def synchronize(func): """ Decorator for :meth:`synchronize`. """ @wraps(func) def outer(self, *args, **kwargs): @self.synchronize def inner(self, *args, **kwargs): return func(self, *args, **kwargs) return inner(self, *args, **kwargs) return outer