from contextlib import contextmanager

class Base(object):
    """ The base class for drivers used by sessions. """

    @property
    def needs_server(self):
        """ bool: Whether this driver needs to communicate with a real HTTP server. """
        return False

    @property
    def current_url(self):
        """ str: The current URL. """
        raise NotImplementedError()

    @property
    def title(self):
        """ str: The current page title. """
        raise NotImplementedError()

    @property
    def html(self):
        """ str: A snapshot of the DOM of the current document, as it looks right now. """
        raise NotImplementedError()

    def switch_to_frame(self, frame):
        """
        Switch to the given frame.

        Args:
            frame (Element | str): The element of the desired frame, or "parent".
        """

        raise NotImplementedError()

    @property
    def frame_title(self):
        """ str: The title for the current frame. """
        elements = self._find_xpath("/html/head/title")
        titles = [element.all_text for element in elements]
        return titles[0] if len(titles) else ""

    @property
    def frame_url(self):
        """ str: The URL for the current frame. """
        try:
            return self.evaluate_script("document.location.href")
        except NotImplementedError:
            raise NotImplementedError()

    @property
    def current_window_handle(self):
        """ object: The current window handle. """
        raise NotImplementedError()

    def window_size(self, handle):
        """ List[int, int]: The size of the given window handle. """
        raise NotImplementedError()

    def resize_window_to(self, handle, width, height):
        """
        Resizes the given window handle to the given dimensions.

        Args:
            handle (object): The driver handle for the window.
            width (int): The desired window width.
            height (int): The desired window height.
        """

        raise NotImplementedError()

    def maximize_window(self, handle):
        """ Maximizes the given window handle. """
        raise NotImplementedError()

    def fullscreen_window(self, handle):
        """ Fullscreens the given window handle. """
        raise NotImplementedError()

    def close_window(self, handle):
        """
        Closes the window with the given handle.

        args:
            handle (object): The handle for the desired window.
        """

        raise NotImplementedError()

    @property
    def window_handles(self):
        """ List[object]: The handles for all opened windows. """
        raise NotImplementedError()

    def open_new_window(self):
        """ Opens a new window. """
        raise NotImplementedError()

    def switch_to_window(self, handle):
        """
        Switches to the given window handle.

        Args:
            handle (object): The handle for the desired window.
        """

        raise NotImplementedError()

    @property
    def no_such_window_error(self):
        """ Exception: The error that is thrown when a window cannot be found. """
        raise NotImplementedError()

    def visit(self, path):
        """
        Visits the given path.

        Args:
            path (str): The path to visit.
        """

        raise NotImplementedError()

    def refresh(self):
        """ Refresh the page. """
        raise NotImplementedError()

    def go_back(self):
        """ Move back a single entry in the browser's history. """
        raise NotImplementedError()

    def go_forward(self):
        """ Move forward a single entry in the browser's history. """
        raise NotImplementedError()

    def execute_script(self, script, *args):
        """
        Executes the given script.

        Args:
            script (str): A JavaScript string to execute.
            *args: Variable length argument list to pass to the executed JavaScript string.
        """

        raise NotImplementedError()

    def evaluate_script(self, script, *args):
        """
        Evaluates and returns the result of the given JavaScript.

        Args:
            script (str): A string of JavaScript to evaluate.
            *args: Variable length argument list to pass to the executed JavaScript string.

        Returns:
            object: The result of the evaluated JavaScript.
        """

        raise NotImplementedError()

    def evaluate_async_script(self, script, *args):
        """
        Evaluates the given JavaScript and obtains the result from a callback function.

        Args:
            script (str): A string of JavaScript to evaluate.
            *args: Variable length argument list to pass to the executed JavaScript string.

        Returns:
            object: The result of the evaluated JavaScript.
        """

        raise NotImplementedError()

    def save_screenshot(self, path, **kwargs):
        """
        Saves a screenshot of the current state of the page.

        Args:
            path (str): The path to where it should be saved.
            **kwargs: Arbitrary keywords arguments for the driver.
        """

        raise NotImplementedError()

    @contextmanager
    def accept_modal(self, modal_type, text=None, response=None, wait=None):
        """
        Accepts the modal that appears matching the given type and, optionally, text.

        Args:
            modal_type (str): The type of modal that should be accepted.
            text (str | RegexObject, optional): Text that is expected to appear in the modal.
            response (str, optional): Text to enter for a response, if applicable.
            wait (int, optional): The number of seconds to wait for the modal to appear.
        """

        raise NotImplementedError()

    @contextmanager
    def dismiss_modal(self, modal_type, text=None, wait=None):
        """
        Dismisses the modal that appears matching the given type and, optionally, text.

        Args:
            modal_type (str): The type of modal that should be dismissed.
            text (str | RegexObject, optional): Text that is expected to appear in the modal.
            wait (int, optional): The number of seconds to wait for the modal to appear.
        """

        raise NotImplementedError()

    @property
    def invalid_element_errors(self):
        """ Tuple[Exception]: A tuple of exceptions that indicate an element is invalid. """
        return ()

    def reset(self):
        """ Resets the driver. """
        pass

    def _find_css(self, query):
        """
        A private method for finding nodes matching a given CSS query.

        Args:
            query (str): The CSS query to match.

        Returns:
            list(driver.Node): A list of matching nodes found by the driver.
        """

        raise NotImplementedError()

    def _find_xpath(self, query):
        """
        A private method for finding nodes matching a given XPath query.

        Args:
            query (str): The XPath query to match.

        Returns:
            List[driver.Node]: A list of matching nodes found by the driver.
        """

        raise NotImplementedError()


import re
from xpath import dsl as x
from xpath.renderer import to_xpath

from capybara.compat import bytes_, str_
from capybara.node.document_matchers import DocumentMatchersMixin
from capybara.node.finders import FindersMixin
from capybara.node.matchers import MatchersMixin
from capybara.utils import decode_bytes, inner_content

class Simple(FindersMixin, MatchersMixin, DocumentMatchersMixin, object):
    """
    A :class:`Simple` is a simpler version of :class:`Base` which includes only
    :class:`FindersMixin` and :class:`MatchersMixin` and does not include :class:`ActionsMixin`.
    This type of node is returned when using :func:`capybara.string`.

    It is useful in that it does not require a session, an application, or a driver, but can still
    use Capybara's finders and matchers on any string that contains HTML.
    """

    def __init__(self, native):
        if isinstance(native, (bytes_, str_)):
            from lxml import etree
            native = decode_bytes(native)
            native = etree.HTML(native)
        self.native = native

    def __getitem__(self, name):
        """
        Return the given attribute. ::

            element["title"]  # => HTML title attribute

        Args:
            name (str): The attribute name to retrieve.

        Returns:
            str: The value of the attribute.
        """

        return self.native.get(name)

    @property
    def tag_name(self):
        """ str: The tag name of the element. """
        return self.native.tag

    @property
    def path(self):
        """ str: An XPath expression describing where on the page the element can be found. """
        return self.native.getroottree().getpath(self.native)

    @property
    def text(self):
        """ str: The text of the element. """
        return self.native.text

    @property
    def value(self):
        """ str: The value of the form element. """

        if self.tag_name == "textarea":
            return inner_content(self.native)
        elif self.tag_name == "select":
            if self["multiple"] == "multiple":
                selected_options = self._find_xpath(".//option[@selected='selected']")
                return [_get_option_value(option) for option in selected_options]
            else:
                options = (
                    self._find_xpath(".//option[@selected='selected']") +
                    self._find_xpath(".//option"))
                return _get_option_value(options[0]) if options else None
        elif self.tag_name == "input" and self["type"] in ["checkbox", "radio"]:
            return self["value"] or "on"
        else:
            return self["value"]

    @property
    def visible(self):
        """ bool: Whether or not the element is visible. """
        return self._visible()

    @property
    def checked(self):
        """ bool: Whether or not the element is checked. """
        return "checked" in self.native.attrib

    @property
    def disabled(self):
        """ bool: Whether or not the element is disabled. """
        return "disabled" in self.native.attrib

    @property
    def readonly(self):
        """ bool: Whether or not the element is readonly. """
        return "readonly" in self.native.attrib

    @property
    def multiple(self):
        """ bool: Whether or not the select supports multiple values. """
        return "multiple" in self.native.attrib

    @property
    def selected(self):
        """ bool: Whether or not the element is selected. """
        return "selected" in self.native.attrib

    @property
    def allow_reload(self):
        return False

    @allow_reload.setter
    def allow_reload(self, value):
        pass

    @property
    def title(self):
        """ str: The current page title. """
        elements = self.native.xpath("/html/head/title | /html/title")
        return elements[0].text if elements else None

    def synchronize(self, func=None, **kwargs):
        # Simple nodes don't need to wait.
        return func if func else lambda func: func

    def _visible(self, check_ancestor_visibility=True):
        if self.tag_name == "input" and self.native.get("type") == "hidden":
            return False

        if check_ancestor_visibility:
            return not self.native.xpath(
                "./ancestor-or-self::*["
                "contains(@style, 'display:none') or "
                "contains(@style, 'display: none') or "
                "@hidden or "
                "name()='script' or "
                "name()='head'"
                "]")

        if "hidden" in self.native.attrib:
            return False
        if re.compile(r"display:\s?none").search(self.native.get("style", "")):
            return False
        if self.tag_name in ["script", "head"]:
            return False

        return True

    def _find_xpath(self, xpath):
        return self.native.xpath(xpath)

    def _find_css(self, css):
        xpath = to_xpath(x.css(css))
        return self._find_xpath(xpath)

def _get_option_value(option):
    return option.get("value") or inner_content(option)


from contextlib import contextmanager

from capybara.selector.expression_filter import ExpressionFilter
from capybara.selector.node_filter import NodeFilter

filter_sets = {}
# Dict[str, FilterSet]: A dictionary of global filter sets keyed by name.

class FilterSet(object):
    """
    A group of filters for use by :class:`Selector` objects.

    Args:
        name (str): The name of this set of filters.
        descriptions (List[Callable[[Dict[str, Any]], str]]): Functions that generate descriptions
            for options.
        filters (Dict[str, AbstractFilter], optional): The filters in this set. Defaults to {}.
    """

    def __init__(self, name, descriptions=None, filters=None):
        self.name = name
        self.descriptions = descriptions or []
        self.filters = filters or {}

    def description(self, options):
        """
        Returns a description of the given filter options relevant to this filter set.

        Args:
            options (Dict[str, Any]): The filter options to describe.

        Returns:
            str: A description of the filter options.
        """

        return "".join([describe(options) for describe in self.descriptions])

class FilterSetFactory(object):
    """ A factory for configuring and building :class:`FilterSet` instances. """

    def __init__(self, name):
        self.name = name
        self.descriptions = []
        self.filters = {}

    def describe(self, func):
        """
        Decorates a function that builds a description of some filter set options.

        Args:
            func (Callable[[Dict[str, Any]], str]): The description builder function.
        """

        self.descriptions.append(func)

    def expression_filter(self, name, **kwargs):
        def decorator(func):
            self.filters[name] = ExpressionFilter(name, func, **kwargs)

        return decorator

    def node_filter(self, name, **kwargs):
        """
        Returns a decorator function for adding node filters to built sets.

        Args:
            name (str): The name of the filter.
            **kwargs: Variable keyword arguments for the filter.

        Returns:
            Callable[[Callable[[Element, Any], bool]]]: A decorator function for adding filters to
                built sets.
        """

        def decorator(func):
            self.filters[name] = NodeFilter(name, func, **kwargs)

        return decorator

    def build_filter_set(self):
        """ FilterSet: Returns a new :class:`FilterSet` with the current factory config. """
        return FilterSet(self.name, self.descriptions, self.filters)

@contextmanager
def add_filter_set(name):
    """
    Builds and registers a global :class:`FilterSet`.

    Args:
        name (str): The name of the set.

    Yields:
        FilterSetFactory: A configurable factory for building a :class:`FilterSet`.
    """

    factory = FilterSetFactory(name)
    yield factory
    filter_sets[name] = factory.build_filter_set()

def remove_filter_set(name):
    """
    Unregisters filter set with the given name.

    Args:
        name (str): The name of the filter set.
    """

    filter_sets.pop(name, None)


from __future__ import absolute_import
from contextlib import contextmanager

from capybara.version import __version__

app = None
""" object: The WSGI-compliant app to test. """

app_host = None
""" str: The default host to use when giving a relative URL to visit. Must be a valid URL. """

default_driver = "werkzeug"
""" str: The name of the driver default driver to use. """

javascript_driver = "selenium"
""" str: The name of the driver to use when JavaScript is required. """

current_driver = None
""" str: The name of the driver currently in use. """

server_name = "default"
""" str: The name of the server to use to serve the app. """

server_host = "127.0.0.1"
""" str: The IP address bound by the default server. """

server_port = None
""" int, optional: The port bound by the default server. """

automatic_label_click = False
""" bool: Whether checkbox/radio actions will try to click the label of invisible elements. """

automatic_reload = True
""" bool: Whether to automatically reload elements as Capybara is waiting. """

enable_aria_label = False
""" bool: Whether fields, links, and buttons will match against aria-label attributes. """

exact = False
""" bool: Whether to match the exact label name/contents. """

default_max_wait_time = 2
""" int: The maximum number of seconds to wait for asynchronous processes to finish. """

default_selector = "css"
""" str: The name of the default selector used to find elements. """

ignore_hidden_elements = True
""" bool: Whether to ignore hidden elements on the page. """

match = "smart"
""" str: The matching strategy to use. """

predicates_wait = True
""" bool: Whether Capybara's predicate matchers use waiting behavior by default. """

raise_server_errors = True
""" bool: Whether errors raised in the server should be raised in the tests. """

save_path = None
""" str, optional: Where to put saved pages and screenshots. """

visible_text_only = False
""" bool: Whether to only consider visible text. """

wait_on_first_by_default = False
""" bool: Whether :meth:`find_first` should wait for at least one element to appear. """

servers = {}
# Dict[str, Callable[[object, str, int], None]]: A dictionary of server initialization functions.

drivers = {}
# Dict[str, Callable[[object], object]]: A dictionary of driver initialization functions.

session_name = "default"
""" str: The current session name. """

_session_pool = {}
# Dict[str, Session]: A pool of `Session` objects, keyed by driver and app.

DSL_METHODS = ["using_session", "using_wait_time"]

def register_server(name):
    """
    Register a server initialization function.

    Args:
        name (str): The name of the server.

    Returns:
        Callable[[Callable[[object, str, int], None]], None]: A decorator that takes a function
            that initializes a server for the given WSGI-compliant app, host, and port.
    """

    def register(init_func):
        servers[name] = init_func

    return register

def register_driver(name):
    """
    Register a driver initialization function.

    Args:
        name (str): The name of the driver.

    Returns:
        Callable[[Callable[[object], object], None]: A decorator that takes a function that
            initializes a driver for the given WSGI-compliant app.
    """

    def register(init_func):
        drivers[name] = init_func

    return register

def run_default_server(app, port):
    servers["werkzeug"](app, port, server_host)

def use_default_driver():
    """ Use the default driver as the current driver. """
    global current_driver
    current_driver = None

@contextmanager
def using_driver(driver):
    """
    Execute the wrapped code using a specific driver.

    Args:
        driver (str): The name of the desired driver.
    """

    global current_driver

    original_current_driver = current_driver
    current_driver = driver
    try:
        yield
    finally:
        current_driver = original_current_driver

@contextmanager
def using_wait_time(seconds):
    """
    Execute a context using a specific wait time.

    Args:
        seconds (int | float): The new wait time.
    """

    global default_max_wait_time

    original_max_wait_time = default_max_wait_time
    default_max_wait_time = seconds
    try:
        yield
    finally:
        default_max_wait_time = original_max_wait_time

def current_session():
    """
    Returns the :class:`Session` for the current driver and app, instantiating one if needed.

    Returns:
        Session: The :class:`Session` for the current driver and app.
    """

    driver = current_driver or default_driver

    session_key = "{driver}:{session}:{app}".format(
        driver=driver, session=session_name, app=str(id(app)))
    session = _session_pool.get(session_key, None)

    if session is None:
        from capybara.session import Session
        session = Session(driver, app)
        _session_pool[session_key] = session

    return session

@contextmanager
def using_session(name):
    """
    Execute the wrapped code using a specific session name.

    Args:
        name (str): The name of the session to use.
    """

    global session_name

    previous_session_name = session_name
    session_name = name
    try:
        yield
    finally:
        session_name = previous_session_name

def reset_sessions():
    """ Resets all sessions. """

    for session in iter(_session_pool.values()):
        session.reset()

reset = reset_sessions
""" Alias for :func:`reset_sessions`. """

def string(html):
    """
    Wraps the given string, which should contain an HTML document or fragment in a :class:`Simple`
    which exposes :class:`MatchersMixin`, :class:`FindersMixin`, and :class:`DocumentMatchersMixin`.
    This allows you to query any string containing HTML in the exact same way you would query the
    current document in a Capybara session.

    Example: A single element ::

        node = Capybara.string(\"""bar\""")
        anchor = node.find_first("a")
        anchor["href"]  # => "foo"
        anchor.text  # => "bar"

    Example: Multiple elements ::

        node = Capybara.string(\"""

Home

Projects

        \""")

        node.find("#projects").text  # => "Projects"
        node.has_selector("li#home", text="Home")
        node.has_selector("#projects")
        node.find("ul").find("li:first-child").text  # => "Home"

    Args:
        html (str | lxml.etree.Element): An HTML fragment or document.

    Returns:
        Simple: A node which has Capybara's finders and matchers.
    """

    from capybara.node.simple import Simple

    return Simple(html)

@register_server("default")
def init_default_server(app, port, host):
    run_default_server(app, port)

@register_server("werkzeug")
def init_werkzeug_server(app, port, host):
    try:
        import werkzeug
    except ImportError:
        raise ImportError(
            'Capybara\'s werkzeug server is unable to load `werkzeug`, please install the package '
            'and add `werkzeug` to your requirements.txt file.')

    from werkzeug.serving import make_server
    from logging import getLogger

    # Mute the server.
    log = getLogger('werkzeug')
    log.disabled = True

    server = make_server(host, port, app, threaded=True)

    # Inform Python that it shouldn't wait for request threads to terminate before
    # exiting. (They will still be appropriately terminated when the process exits.)
    server.daemon_threads = True

    server.serve_forever()

@register_driver("selenium")
def init_selenium_driver(app):
    try:
        import selenium
    except ImportError:
        raise ImportError(
            'Capybara\'s selenium driver is unable to load `selenium`, please install the package '
            'and add `selenium` to your requirements.txt file.')

    from capybara.selenium.driver import Driver

    return Driver(app)

@register_driver("werkzeug")
def init_werkzeug_driver(app):
    try:
        import werkzeug
    except ImportError:
        raise ImportError(
            'Capybara\'s werkzeug driver is unable to load `werkzeug`, please install the package '
            'and add `werkzeug` to your requirements.txt file.')

    from capybara.werkzeug.driver import Driver

    return Driver(app)


from collections import Hashable
from functools import wraps

import capybara
from capybara.exceptions import ExpectationNotMet
from capybara.helpers import expects_none, matches_count
from capybara.selector import selectors
from capybara.queries.selector_query import SelectorQuery
from capybara.queries.style_query import StyleQuery
from capybara.queries.text_query import TextQuery

def predicate(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if "wait" not in kwargs and not capybara.predicates_wait:
            kwargs["wait"] = 0

        try:
            return func(*args, **kwargs)
        except ExpectationNotMet:
            return False

    return wrapper

class MatchersMixin(object):
    @predicate
    def has_selector(self, *args, **kwargs):
        """
        Checks if a given selector is on the page or a descendant of the current node. ::

            page.has_selector("p#foo")
            page.has_selector("xpath", ".//p[@id='foo']")

        By default it will check if the expression occurs at least once, but a different number can
        be specified. ::

            page.has_selector("p.foo", count=4)

        This will check if the expression occurs exactly 4 times.

        It also accepts all options that :meth:`find_all` accepts, such as ``text`` and
        ``visible``. ::

            page.has_selector("li", text="Horse", visible=True)

        ``has_selector`` can also accept XPath expressions generated by the ``xpath-py`` package::

            from xpath import dsl as x

            page.has_selector("xpath", x.descendant("p"))

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: If the expression exists.
        """

        return self.assert_selector(*args, **kwargs)

    @predicate
    def has_no_selector(self, *args, **kwargs):
        """
        Checks if a given selector is not on the page or a descendant of the current node. Usage is
        identical to :meth:`has_selector`.

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.assert_no_selector(*args, **kwargs)

    @predicate
    def has_style(self, styles, **kwargs):
        """
        Checks if an element has the specified CSS styles. ::

            element.has_style({"color": "rgb(0,0,255)", "font-size": re.compile(r"px")})

        Args:
            styles (Dict[str, str | RegexObject]): The expected styles.

        Returns:
            bool: Whether the styles match.
        """

        return self.assert_style(styles, **kwargs)

    @predicate
    def has_all_of_selectors(self, selector, *locators, **kwargs):
        """
        Checks if allof the provided selectors are present on the given page or descendants of the
        current node. If options are provided, the assertion will check that each locator is present
        with those options as well (other than ``wait``). ::

            page.has_all_of_selectors("custom", "Tom", "Joe", visible="all")
            page.has_all_of_selectors("css", "#my_dif", "a.not_clicked")

        It accepts all options that :meth:`find_all` accepts, such as ``text`` and ``visible``.

        The ``wait`` option applies to all of the selectors as a group, so all of the locators must
        be present within ``wait`` (defaults to :data:`capybara.default_max_wait_time`) seconds.

        If the given selector is not a valid selector, the first argument is assumed to be a locator
        and the default selector will be used.

        Args:
            selector (str, optional): The name of the selector to use. Defaults to
                :data:`capybara.default_selector`.
            *locators (str): Variable length list of locators.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        return self.assert_all_of_selectors(selector, *locators, **kwargs)

    @predicate
    def has_none_of_selectors(self, selector, *locators, **kwargs):
        """
        Checks if none of the provided selectors are present on the given page or descendants of the
        current node. If options are provided, the assertion will check that each locator is present
        with those options as well (other than ``wait``). ::

            page.has_none_of_selectors("custom", "Tom", "Joe", visible="all")
            page.has_none_of_selectors("css", "#my_div", "a.not_clicked")

        It accepts all options that :meth:`find_all` accepts, such as ``text`` and ``visible``.

        The ``wait`` option applies to all of the selectors as a group, so none of the locators must
        be present with ``wait`` (defaults to :data:`capybara.default_max_wait_time`) seconds.

        If the given selector is not a valid selector, the first argument is assumed to be a locator
        and the default selector will be used.

        Args:
            selector (str, optional): The name of the selector to use. Defaults to
                :data:`capybara.default_selector`.
            *locators (str): Variable length list of locators.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        return self.assert_none_of_selectors(selector, *locators, **kwargs)

    @predicate
    def matches_selector(self, *args, **kwargs):
        """
        Checks if the current node matches the given selector.

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it matches.
        """

        return self.assert_matches_selector(*args, **kwargs)

    @predicate
    def not_match_selector(self, *args, **kwargs):
        """
        Checks if the current node does not match the given selector. Usage is identical to
        :meth:`has_selector`.

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't match.
        """

        return self.assert_not_match_selector(*args, **kwargs)

    def assert_selector(self, *args, **kwargs):
        """
        Asserts that a given selector is on the page or a descendant of the current node. ::

            page.assert_selector("p#foo")

        By default it will check if the expression occurs at least once, but a different number can
        be specified. ::

            page.assert_selector("p.foo", count=4)

        This will check if the expression occurs exactly 4 times. See :meth:`find_all` for other
        available result size options.

        If a ``count`` of 0 is specified, it will behave like :meth:`assert_no_selector`; however,
        use of that method is preferred over this one.

        It also accepts all options that :meth:`find_all` accepts, such as ``text`` and
        ``visible``. ::

            page.assert_selector("li", text="Horse", visible=True)

        ``assert_selector`` can also accept XPath expressions generated by the ``xpath-py``
        package::

            from xpath import dsl as x

            page.assert_selector("xpath", x.descendant("p"))

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: The given selector did not match.
        """

        query = SelectorQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_selector():
            result = query.resolve_for(self)

            if not (result.matches_count and
                    (len(result) > 0 or expects_none(query.options))):
                raise ExpectationNotMet(result.failure_message)

            return True

        return assert_selector()

    def assert_style(self, styles, **kwargs):
        """
        Asserts that an element has the specified CSS styles. ::

            element.assert_style({"color": "rgb(0,0,255)", "font-size": re.compile(r"px")})

        Args:
            styles (Dict[str, str | RegexObject]): The expected styles.

        Returns:
            True

        Raises:
            ExpectationNotMet: The element doesn't have the specified styles.
        """

        query = StyleQuery(styles, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_style():
            if not query.resolves_for(self):
                raise ExpectationNotMet(query.failure_message)

            return True

        return assert_style()

    def assert_all_of_selectors(self, selector, *locators, **kwargs):
        """
        Asserts that all of the provided selectors are present on the given page or descendants of
        the current node. If options are provided, the assertion will check that each locator is
        present with those options as well (other than ``wait``). ::

            page.assert_all_of_selectors("custom", "Tom", "Joe", visible="all")
            page.assert_all_of_selectors("css", "#my_dif", "a.not_clicked")

        It accepts all options that :meth:`find_all` accepts, such as ``text`` and ``visible``.

        The ``wait`` option applies to all of the selectors as a group, so all of the locators must
        be present within ``wait`` (defaults to :data:`capybara.default_max_wait_time`) seconds.

        If the given selector is not a valid selector, the first argument is assumed to be a locator
        and the default selector will be used.

        Args:
            selector (str, optional): The name of the selector to use. Defaults to
                :data:`capybara.default_selector`.
            *locators (str): Variable length list of locators.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        wait = kwargs['wait'] if 'wait' in kwargs else capybara.default_max_wait_time

        if not isinstance(selector, Hashable) or selector not in selectors:
            locators = (selector,) + locators
            selector = capybara.default_selector

        @self.synchronize(wait=wait)
        def assert_all_of_selectors():
            for locator in locators:
                self.assert_selector(selector, locator, **kwargs)

            return True

        return assert_all_of_selectors()

    def assert_none_of_selectors(self, selector, *locators, **kwargs):
        """
        Asserts that none of the provided selectors are present on the given page or descendants of
        the current node. If options are provided, the assertion will check that each locator is
        present with those options as well (other than ``wait``). ::

            page.assert_none_of_selectors("custom", "Tom", "Joe", visible="all")
            page.assert_none_of_selectors("css", "#my_div", "a.not_clicked")

        It accepts all options that :meth:`find_all` accepts, such as ``text`` and ``visible``.

        The ``wait`` option applies to all of the selectors as a group, so none of the locators must
        be present with ``wait`` (defaults to :data:`capybara.default_max_wait_time`) seconds.

        If the given selector is not a valid selector, the first argument is assumed to be a locator
        and the default selector will be used.

        Args:
            selector (str, optional): The name of the selector to use. Defaults to
                :data:`capybara.default_selector`.
            *locators (str): Variable length list of locators.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """
        wait = kwargs['wait'] if 'wait' in kwargs else capybara.default_max_wait_time

        if not isinstance(selector, Hashable) or selector not in selectors:
            locators = (selector,) + locators
            selector = capybara.default_selector

        @self.synchronize(wait=wait)
        def assert_none_of_selectors():
            for locator in locators:
                self.assert_no_selector(selector, locator, **kwargs)

            return True

        return assert_none_of_selectors()

    def assert_no_selector(self, *args, **kwargs):
        """
        Asserts that a given selector is not on the page or a descendant of the current node. Usage
        is identical to :meth:`assert_selector`.

        Query options such as ``count``, ``minimum``, and ``between`` are considered to be an
        integral part of the selector. This will return True, for example, if a page contains 4
        anchors but the query expects 5::

            page.assert_no_selector("a", minimum=1)  # Found, raises ExpectationNotMet
            page.assert_no_selector("a", count=4)    # Found, raises ExpectationNotMet
            page.assert_no_selector("a", count=5)    # Not Found, returns True

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: The given selector matched.
        """

        query = SelectorQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_no_selector():
            result = query.resolve_for(self)

            if result.matches_count and (
                   len(result) > 0 or expects_none(query.options)):
                raise ExpectationNotMet(result.negative_failure_message)

            return True

        return assert_no_selector()

    refute_selector = assert_no_selector
    """ Alias for :meth:`assert_no_selector`. """

    def assert_matches_selector(self, *args, **kwargs):
        """
        Asserts that the current node matches a given selector. ::

            node.assert_matches_selector("p#foo")
            node.assert_matches_selector("xpath", "//p[@id='foo']")

        It also accepts all options that :meth:`find_all` accepts, such as ``text`` and
        ``visible``. ::

            node.assert_matches_selector("li", text="Horse", visible=True)

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: If the selector does not match.
        """

        query = SelectorQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_matches_selector():
            result = query.resolve_for(self.find_first("xpath", "./parent::*", minimum=0) or self.query_scope)

            if self not in result:
                raise ExpectationNotMet("Item does not match the provided selector")

            return True

        return assert_matches_selector()

    def assert_not_match_selector(self, *args, **kwargs):
        """
        Asserts that the current node does not match a given selector. See
        :meth:`assert_matches_selector`.

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: If the selector matches.
        """

        query = SelectorQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_not_match_selector():
            result = query.resolve_for(self.find_first("xpath", "./parent::*", minimum=0) or self.query_scope)

            if self in result:
                raise ExpectationNotMet("Item matched the provided selector")

            return True

        return assert_not_match_selector()

    refute_matches_selector = assert_not_match_selector
    """ Alias for :meth:`assert_not_match_selector`. """

    def matches_xpath(self, xpath, **kwargs):
        """
        Checks if the current node matches the given XPath expression.

        Args:
            xpath (str | xpath.expression.Expression): The XPath expression to match against the
                current node.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: Whether it matches.
        """

        return self.matches_selector("xpath", xpath, **kwargs)

    def not_match_xpath(self, xpath, **kwargs):
        """
        Checks if the current node does not match the given XPath expression.

        Args:
            xpath (str | xpath.expression.Expression): The XPath expression to match against the
                current node.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: Whether it doesn't match.
        """

        return self.not_match_selector("xpath", xpath, **kwargs)

    def matches_css(self, css, **kwargs):
        """
        Checks if the current node matches the given CSS selector.

        Args:
            css (str): The CSS selector to match against the current node.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: Whether it matches.
        """

        return self.matches_selector("css", css, **kwargs)

    def not_match_css(self, css, **kwargs):
        """
        Checks if the current node does not match the given CSS selector.

        Args:
            css (str): The CSS selector to match against the current node.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: Whether it doesn't match.
        """

        return self.not_match_selector("css", css, **kwargs)

    def has_xpath(self, query, **kwargs):
        """
        Checks if a given XPath expression is on the page or a descendant of the current node. ::

            session.has_xpath(".//p[@id='foo']")

        ``has_xpath`` can also accept XPath expressions generated by the ``xpath-py`` package::

            from xpath import dsl as x

            session.has_xpath(x.descendant("p"))

        Args:
            query (str): An XPath expression.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: If the expression exists.
        """

        return self.has_selector("xpath", query, **kwargs)

    def has_no_xpath(self, path, **kwargs):
        """
        Checks if a given XPath expression is not on the page or a descendant of the current node.
        Usage is identical to :meth:`has_xpath`.

        Args:
            path (str): An XPath expression.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: If the expression does not exist.
        """

        return self.has_no_selector("xpath", path, **kwargs)

    def has_css(self, path, **kwargs):
        """
        Checks if a given CSS selector is on the page or a descendant of the current node. ::

            page.has_css("p#foo")

        By default it will check if the selector occurs at least once, but a different number can
        be specified. ::

            page.has_css("p#foo", count=4)

        This will check if the selector occurs exactly 4 times.

        It also accepts all options that :meth:`find_all` accepts, such as ``text`` and
        ``visible``. ::

            page.has_css("li", text="Horse", visible=True)

        Args:
            path (str): A CSS selector.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: If the selector exists.
        """

        return self.has_selector("css", path, **kwargs)

    def has_no_css(self, path, **kwargs):
        """
        Checks if a given CSS selector is not on the page or a descendant of the current node.
        Usage is identical to :meth:`has_css`.

        Args:
            path (str): A CSS Selector.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Return:
            bool: If the selector does not exist.
        """

        return self.has_no_selector("css", path, **kwargs)

    def has_button(self, locator, **kwargs):
        """
        Checks if the page or current node has a button with the given text, value, or id.

        Args:
            locator (str): The text, value, or id of a button to check for.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        return self.has_selector("button", locator, **kwargs)

    def has_no_button(self, locator, **kwargs):
        """
        Checks if the page or current node has no button with the given text, value, or id.

        Args:
            locator (str): The text, value, or id of a button to check for.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.has_no_selector("button", locator, **kwargs)

    def has_checked_field(self, locator, **kwargs):
        """
        Checks if the page or current node has a radio button or checkbox with the given label,
        value, or id, that is currently checked.

        Args:
            locator (str): The label, name, or id of a checked field.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        kwargs["checked"] = True
        return self.has_selector("field", locator, **kwargs)

    def has_no_checked_field(self, locator, **kwargs):
        """
        Checks if the page or current node has no radio button or checkbox with the given label,
        value, or id that is currently checked.

        Args:
            locator (str): The label, name, or id of a checked field.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        kwargs["checked"] = True
        return self.has_no_selector("field", locator, **kwargs)

    def has_field(self, locator, **kwargs):
        """
        Checks if the page or current node has a form field with the given label, name, or id.

        For text fields and other textual fields, such as textareas and HTML5 email/url/etc. fields,
        it's possible to specify a ``value`` argument to specify the text the field should contain::

            page.has_field("Name", value="Jonas")

        Args:
            locator (str): The label, name, or id of a field to check for.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        return self.has_selector("field", locator, **kwargs)

    def has_no_field(self, locator, **kwargs):
        """
        Checks if the page or current node has no form field with the given label, name, or id. See
        :meth:`has_field`.

        Args:
            locator (str): The label, name, or id of a field to check for.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.has_no_selector("field", locator, **kwargs)

    def has_link(self, locator, **kwargs):
        """
        Checks if the page or current node has a link with the given text or id.

        Args:
            locator (str): The text or id of a link to check for.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        return self.has_selector("link", locator, **kwargs)

    def has_no_link(self, locator, **kwargs):
        """
        Checks if the page or current node has no link with the given text or id.

        Args:
            locator (str): The text or id of a link to check for.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.has_no_selector("link", locator, **kwargs)

    def has_select(self, locator, **kwargs):
        """
        Checks if the page or current node has a select field with the given label, name, or id.

        It can be specified which option should currently be selected::

            page.has_select("Language", selected="German")

        For multiple select boxes, several options may be specified::

            page.has_select("Language", selected=["English", "German"])

        It's also possible to check if the exact set of options exists for this select box::

            page.has_select("Language", options=["English", "German", "Spanish"])

        You can also check for a partial set of options::

            page.has_select("Language", with_options=["English", "German"])

        Args:
            locator (str): The label, name, or id of a select box.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        return self.has_selector("select", locator, **kwargs)

    def has_no_select(self, locator, **kwargs):
        """
        Checks if the page or current node has no select field with the given label, name, or id.
        See :meth:`has_select`.

        Args:
            locator (str): The label, name, or id of a select box.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.has_no_selector("select", locator, **kwargs)

    def has_table(self, locator, **kwargs):
        """
        Checks if the page or current node has a table with the given id or caption::

            page.has_table("People")

        Args:
            locator (str): The id or caption of a table.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        return self.has_selector("table", locator, **kwargs)

    def has_no_table(self, locator, **kwargs):
        """
        Checks if the page or current node has no table with the given id or caption. See
        :meth:`has_table`.

        Args:
            locator (str): The id or caption of a table.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.has_no_selector("table", locator, **kwargs)

    def has_unchecked_field(self, locator, **kwargs):
        """
        Checks if the page or current node has a radio button or checkbox with the given label,
        value, or id, that is currently unchecked.

        Args:
            locator (str): The label, name, or id of an unchecked field.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it exists.
        """

        kwargs["checked"] = False
        return self.has_selector("field", locator, **kwargs)

    def has_no_unchecked_field(self, locator, **kwargs):
        """
        Checks if the page or current node has no radio button or checkbox with the given label,
        value, or id, that is currently unchecked.

        Args:
            locator (str): The label, name, or id of an unchecked field.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        kwargs["checked"] = False
        return self.has_no_selector("field", locator, **kwargs)

    def assert_text(self, *args, **kwargs):
        """
        Asserts that the page or current node has the given text content, ignoring any HTML tags.

        Args:
            *args: Variable length argument list for :class:`TextQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`TextQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: If the assertion hasn't succeeded during the wait time.
        """

        query = TextQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_text():
            count = query.resolve_for(self)

            if not (matches_count(count, query.options) and
                    (count > 0 or expects_none(query.options))):
                raise ExpectationNotMet(query.failure_message)

            return True

        return assert_text()

    def assert_no_text(self, *args, **kwargs):
        """
        Asserts that the page or current node doesn't have the given text content, ignoring any
        HTML tags.

        Args:
            *args: Variable length argument list for :class:`TextQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`TextQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: If the assertion hasn't succeeded during the wait time.
        """

        query = TextQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_no_text():
            count = query.resolve_for(self)

            if matches_count(count, query.options) and (
                   count > 0 or expects_none(query.options)):
                raise ExpectationNotMet(query.negative_failure_message)

            return True

        return assert_no_text()

    @predicate
    def has_text(self, *args, **kwargs):
        """
        Checks if the page or current node has the given text content, ignoring any HTML tags.

        Whitespaces are normalized in both the node's text and the passed text parameter. Note that
        whitespace isn't normalized in a passed regular expression as normalizing whitespace in a
        regular expression isn't easy and doesn't seem to be worth it.

        By default it will check if the text occurs at least once, but a different number can be
        specified. ::

            page.has_text("lorem ipsum", between=range(2, 5))

        Args:
            *args: Variable length argument list for :class:`TextQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`TextQuery`.

        Returns:
            bool: Whether it exists.
        """

        return self.assert_text(*args, **kwargs)

    has_content = has_text
    """ Alias for :meth:`has_text`. """

    @predicate
    def has_no_text(self, *args, **kwargs):
        """
        Checks if the page or current node does not have the given text content, ignoring any HTML
        tags and normalizing whitespace.

        Args:
            *args: Variable length argument list for :class:`TextQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`TextQuery`.

        Returns:
            bool: Whether it doesn't exist.
        """

        return self.assert_no_text(*args, **kwargs)

    has_no_content = has_no_text
    """ Alias for :meth:`has_no_text`. """


from capybara.exceptions import ExpectationNotMet
from capybara.queries.title_query import TitleQuery

class DocumentMatchersMixin(object):
    def assert_title(self, title, **kwargs):
        """
        Asserts that the page has the given title.

        Args:
            title (str | RegexObject): The string or regex that the title should match.
            **kwargs: Arbitrary keyword arguments for :class:`TitleQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: If the assertion hasn't succeeded during the wait time.
        """

        query = TitleQuery(title, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_title():
            if not query.resolves_for(self):
                raise ExpectationNotMet(query.failure_message)

            return True

        return assert_title()

    def assert_no_title(self, title, **kwargs):
        """
        Asserts that the page doesn't have the given title.

        Args:
            title (str | RegexObject): The string that the title should include.
            **kwargs: Arbitrary keyword arguments for :class:`TitleQuery`.

        Returns:
            True

        Raises:
            ExpectationNotMet: If the assertion hasn't succeeded during the wait time.
        """

        query = TitleQuery(title, **kwargs)

        @self.synchronize(wait=query.wait)
        def assert_no_title():
            if query.resolves_for(self):
                raise ExpectationNotMet(query.negative_failure_message)

            return True

        return assert_no_title()

    def has_title(self, title, **kwargs):
        """
        Checks if the page has the given title.

        Args:
            title (str | RegexObject): The string or regex that the title should match.
            **kwargs: Arbitrary keyword arguments for :class:`TitleQuery`.

        Returns:
            bool: Whether it matches.
        """

        try:
            self.assert_title(title, **kwargs)
            return True
        except ExpectationNotMet:
            return False

    def has_no_title(self, title, **kwargs):
        """
        Checks if the page doesn't have the given title.

        Args:
            title (str | RegexObject): The string that the title should include.
            **kwargs: Arbitrary keyword arguments for :class:`TitleQuery`.

        Returns:
            bool: Whether it doesn't match.
        """

        try:
            self.assert_no_title(title, **kwargs)
            return True
        except ExpectationNotMet:
            return False


import capybara
from capybara.exceptions import Ambiguous, ElementNotFound, ExpectationNotMet
from capybara.queries.ancestor_query import AncestorQuery
from capybara.queries.selector_query import SelectorQuery
from capybara.queries.sibling_query import SiblingQuery

class FindersMixin(object):
    """
    If the driver is capable of executing JavaScript, finders will wait for a set amount of time and
    continuously retry finding the element until either the element is found or the time expires.
    The length of time :meth:`find` will wait is controlled through
    :data:`capybara.default_max_wait_time` and defaults to 2 seconds.
    """

    def find(self, *args, **kwargs):
        """
        Find an :class:`Element` based on the given arguments. ``find`` will raise an error if the
        element is not found.

        ``find`` takes the same options as :meth:`find_all`. ::

            page.find("#foo").find(".bar")
            page.find("xpath", "//div[contains(., 'bar')]")
            page.find("li", text="Quox").click_link("Delete")

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Element: The found element.

        Raises:
            Ambiguous: If more than one element matching element is found.
            ElementNotFound: If the element can't be found before time expires.
        """

        return self._synchronized_resolve(SelectorQuery(*args, **kwargs))

    def ancestor(self, *args, **kwargs):
        """
        Find an :class:`Element` based on the given arguments that is also an ancestor of the
        element called on. ``ancestor`` will raise an error if the element is not found.

        ``ancestor`` takes the same options as :meth:`find`. ::

            element.ancestor("#foo").find(".bar")
            element.ancestor("xpath", "//div[contains(., 'bar')]")
            element.ancestor("li", text="Quox").click_link("Delete")

        Args:
            *args: Variable length argument list for :class:`AncestorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`AncestorQuery`.

        Returns:
            Element: The found element.

        Raises:
            Ambiguous: If more than one element matching element is found.
            ElementNotFound: If the element can't be found before time expires.
        """

        return self._synchronized_resolve(AncestorQuery(*args, **kwargs))

    def sibling(self, *args, **kwargs):
        """
        Find an :class:`Element` based on the given arguments that is also a sibling of the
        element called on. ``sibling`` will raise an error if the element is not found.

        ``sibling`` takes the same options as :meth:`find`. ::

            element.sibling("#foo").find(".bar")
            element.sibling("xpath", "//div[contains(., 'bar')]")
            element.sibling("li", text="Quox").click_link("Delete")

        Args:
            *args: Variable length argument list for :class:`SiblingQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SiblingQuery`.

        Returns:
            Element: The found element.

        Raises:
            Ambiguous: If more than one element matching element is found.
            ElementNotFound: If the element can't be found before time expires.
        """

        return self._synchronized_resolve(SiblingQuery(*args, **kwargs))

    def find_button(self, locator=None, **kwargs):
        """
        Find a button on the page. This can be any ``<input>`` element of type submit, reset, image,
        or button, or it can be a ``<button>`` element. All buttons can be found by their id, value,
        or title. ``<button>`` elements can also be found by their text content, and image
        ``<input>`` elements by their alt attribute.

        Args:
            locator (str, optional): The id, value, title, text content, alt of image.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Element: The found element.
        """

        return self.find("button", locator, **kwargs)

    def find_by_id(self, id, **kwargs):
        """
        Find an element on the page, given its id.

        Args:
            id (str): The id of the element.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Element: The found element.
        """

        return self.find("id", id, **kwargs)

    def find_field(self, locator=None, **kwargs):
        """
        Find a form field on the page. The field can be found by its name, id, or label text.

        Args:
            locator (str, optional): Name, id, placeholder, or text of associated label element.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Element: The found element.
        """

        return self.find("field", locator, **kwargs)

    def find_link(self, locator=None, **kwargs):
        """
        Find a link on the page. The link can be found by its id or text.

        Args:
            locator (str, optional): ID, title, text, or alt of enclosed img element.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Element: The found element.
        """

        return self.find("link", locator, **kwargs)

    def find_all(self, *args, **kwargs):
        """
        Find all elements on the page matching the given selector and options.

        Both XPath and CSS expressions are supported, but Capybara does not try to automatically
        distinguish between them. The following statements are equivalent::

            page.find_all("css", "a#person_123")
            page.find_all("xpath", "//a[@id='person_123']")

        If the type of selector is left out, Capybara uses :data:`capybara.default_selector`. It's
        set to ``"css"`` by default. ::

            page.find_all("a#person_123")

            capybara.default_selector = "xpath"
            page.find_all("//a[@id='person_123']")

        The set of found elements can further be restricted by specifying options. It's possible to
        select elements by their text or visibility::

            page.find_all("a", text="Home")
            page.find_all("#menu li", visible=True)

        By default if no elements are found, an empty list is returned; however, expectations can be
        set on the number of elements to be found which will trigger Capybara's waiting behavior for
        the expectations to match. The expectations can be set using::

            page.assert_selector("p#foo", count=4)
            page.assert_selector("p#foo", maximum=10)
            page.assert_selector("p#foo", minimum=1)
            page.assert_selector("p#foo", between=range(1, 11))

        See :func:`capybara.result.Result.matches_count` for additional information about count
        matching.

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Result: A collection of found elements.

        Raises:
            ExpectationNotMet: The matched results did not meet the expected criteria.
        """

        query = SelectorQuery(*args, **kwargs)

        @self.synchronize(wait=query.wait)
        def find_all():
            result = query.resolve_for(self)

            if not result.matches_count:
                raise ExpectationNotMet(result.failure_message)

            return result

        return find_all()

    def find_first(self, *args, **kwargs):
        """
        Find the first element on the page matching the given selector and options, or None if no
        element matches.

        By default, no waiting behavior occurs. However, if ``capybara.wait_on_first_by_default``
        is set to true, it will trigger Capybara's waiting behavior for a minimum of 1 matching
        element to be found.

        Args:
            *args: Variable length argument list for :class:`SelectorQuery`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Returns:
            Element: The found element or None.
        """

        if capybara.wait_on_first_by_default:
            kwargs.setdefault("minimum", 1)

        try:
            result = self.find_all(*args, **kwargs)
            return result[0] if len(result) > 0 else None
        except ExpectationNotMet:
            return None

    def _synchronized_resolve(self, query):
        @self.synchronize(wait=query.wait)
        def resolve():
            if query.match in ["prefer_exact", "smart"]:
                result = query.resolve_for(self, True)
                if len(result) == 0 and not query.exact:
                    result = query.resolve_for(self, False)
            else:
                result = query.resolve_for(self)

            if query.match in ["one", "smart"] and len(result) > 1:
                raise Ambiguous("Ambiguous match, found {count} elements matching {query}".format(
                    count=len(result), query=query.description))
            if len(result) == 0:
                raise ElementNotFound("Unable to find {0}".format(query.description))

            element = result[0]
            element.allow_reload = True
            return element

        return resolve()


import re

import capybara
from capybara.helpers import declension, desc, failure_message, normalize_text, toregex
from capybara.queries.base_query import BaseQuery
from capybara.utils import isregex

VALID_QUERY_TYPE = ["all", "visible"]

class TextQuery(BaseQuery):
    """
    Queries for text content in a node.

    If only one argument is provided, it will be assumed to be ``expected_text``.

    Args:
        query_type (str, optional): One of "visible" or "all". Defaults to "visible".
        expected_text (str | RegexObject): The desired text.
        between (Iterable[int], optional): A range of acceptable counts.
        count (int, optional): The number of times the text should match. Defaults to any number of
            times greater than zero.
        exact_text (bool, optional): Whether to match the text exactly. Defaults to False.
        maximum (int, optional): The maximum number of times the selector should match. Defaults to
            infinite.
        minimum (int, optional): The minimum number of times the selector should match. Defaults to
            1.
        wait (bool | int | float, optional): Whether and how long to wait for synchronization.
            Defaults to :data:`capybara.default_max_wait_time`.
    """

    def __init__(self, query_type, expected_text=None, between=None, count=None, exact_text=False,
                 maximum=None, minimum=None, wait=None):
        if expected_text is None:
            expected_text = query_type
            query_type = None

        if query_type is None:
            query_type = ("visible" if capybara.ignore_hidden_elements or capybara.visible_text_only
                          else "all")

        assert query_type in VALID_QUERY_TYPE, \
            "invalid option {query_type} for query_type, should be one of {valid_values}".format(
                query_type=desc(query_type),
                valid_values=", ".join(desc(VALID_QUERY_TYPE)))

        self.expected_text = (expected_text if isregex(expected_text)
                              else normalize_text(expected_text))
        self.query_type = query_type
        self.search_regexp = toregex(expected_text, exact=exact_text)
        self.options = {
            "between": between,
            "count": count,
            "exact_text": exact_text,
            "maximum": maximum,
            "minimum": minimum,
            "wait": wait}
        self.node = None
        self.actual_text = None
        self.count = None

    @property
    def wait(self):
        """ int | float: How long to wait for synchronization. """
        return self.normalize_wait(self.options["wait"])

    def resolve_for(self, node):
        """
        Resolves this query relative to the given node.

        Args:
            node (node.Base): The node to be evaluated.

        Returns:
            int: The number of matches found.
        """

        self.node = node
        self.actual_text = normalize_text(
            node.visible_text if self.query_type == "visible" else node.all_text)
        self.count = len(re.findall(self.search_regexp, self.actual_text))

        return self.count

    @property
    def description(self):
        if isregex(self.expected_text):
            return "text matching {}".format(desc(self.expected_text))
        elif self.options["exact_text"]:
            return "exact text {}".format(desc(self.expected_text))
        else:
            return "text {}".format(desc(self.expected_text))

    @property
    def failure_message(self):
        """ str: A message describing the query failure. """
        return self._build_message(True)

    @property
    def negative_failure_message(self):
        """ str: A message describing the negative query failure. """
        return re.sub(r"(to find)", r"not \1", self._build_message(False))

    def _build_message(self, report_on_invisible):
        message = failure_message(self.description, self.options)
        if any(self.options.values()):
            message += " but found {count} {times}".format(
                count=self.count,
                times=declension("time", "times", self.count))
        message += " in {actual}".format(actual=desc(self.actual_text))

        details_message = []

        if self.node and not self.search_regexp.flags & re.IGNORECASE:
            insensitive_regex = re.compile(
                self.search_regexp.pattern,
                flags=(self.search_regexp.flags | re.IGNORECASE))
            insensitive_count = len(re.findall(insensitive_regex, self.actual_text))
            if insensitive_count != self.count:
                details_message.append(
                    "it was found {count} {times} using a case insensitive search".format(
                        count=insensitive_count,
                        times=declension("time", "times", insensitive_count)))

        if self.node and self.query_type == "visible" and report_on_invisible:
            invisible_text = normalize_text(self.node.all_text)
            invisible_count = len(re.findall(self.search_regexp, invisible_text))
            if invisible_count != self.count:
                details_message.append(
                    "it was found {count} {times} including non-visible text".format(
                        count=invisible_count,
                        times=declension("time", "times", invisible_count)))

        if details_message:
            message += ". (However, {details}.)".format(details=" and ".join(details_message))

        return message


from socket import socket
from threading import Lock

from capybara.compat import (
    ParseResult,
    bytes_,
    parse_qsl,
    quote,
    str_,
    unquote,
    urlencode,
    urlparse)

_missing = object()

class cached_property(property):
    """ Decorates an instance method, turning it into a property whose value is cached. """

    def __init__(self, func):
        self.__name__ = func.__name__
        self.__module__ = func.__module__
        self.__doc__ = func.__doc__
        self.func = func

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        value = obj.__dict__.get(self.__name__, _missing)
        if value is _missing:
            value = self.func(obj)
            obj.__dict__[self.__name__] = value
        return value

class Counter(object):
    """ Keeps track of a running count. """

    def __init__(self):
        self._lock = Lock()
        self._value = 0

    @property
    def value(self):
        """ int: The current value of the counter. """
        return self._value

    def __enter__(self):
        with self._lock:
            self._value += 1

    def __exit__(self, *args):
        with self._lock:
            self._value -= 1

def decode_bytes(value):
    """ str: Decodes the given byte sequence. """
    return value.decode("utf-8") if isbytes(value) else value

def encode_string(value):
    """ bytes: Encodes the given string. """
    return value.encode("utf-8") if isstring(value) else value

def find_available_port():
    """ int: A random available port. """
    s = socket()
    s.bind(("", 0))
    return s.getsockname()[1]

def inner_content(node):
    """
    Returns the inner content of a given XML node, including tags.

    Args:
        node (lxml.etree.Element): The node whose inner content is desired.

    Returns:
        str: The inner content of the node.
    """

    from lxml import etree

    # Include text content at the start of the node.
    parts = [node.text]

    for child in node.getchildren():
        # Include the child serialized to raw XML.
        parts.append(etree.tostring(child, encoding="utf-8"))

        # Include any text following the child.
        parts.append(child.tail)

    # Discard any non-existent text parts and return.
    return "".join(filter(None, parts))

def inner_text(node):
    """
    Returns the inner text of a given XML node, excluding tags.

    Args:
        node: (lxml.etree.Element): The node whose inner text is desired.

    Returns:
        str: The inner text of the node.
    """

    from lxml import etree

    # Include text content at the start of the node.
    parts = [node.text]

    for child in node.getchildren():
        # Include the raw text content of the child.
        parts.append(etree.tostring(child, encoding="utf-8", method="text"))

        # Include any text following the child.
        parts.append(child.tail)

    # Discard any non-existent text parts and return.
    return "".join(map(decode_bytes, filter(None, parts)))

def isbytes(value):
    """ bool: Whether the given value is a sequence of bytes. """
    return isinstance(value, bytes_)

def isregex(possible_regex):
    """
    Returns whether the given object is (probably) a regular expression object.

    Args:
        possible_regex (object): An object which may or may not be a regular expression.

    Returns:
        bool: Whether the given object is (probably) a regular expression object.
    """

    return hasattr(possible_regex, "search") and callable(possible_regex.search)

def isstring(value):
    """ bool: Whether the given value is a string. """
    return isinstance(value, str_)

def normalize_url(url):
    """
    Returns the given URL with all query keys properly escaped.

    Args:
        url (str): The URL to normalize.

    Returns:
        str: The normalized URL.
    """

    uri = urlparse(url)
    query = uri.query or ""

    pairs = parse_qsl(query)
    decoded_pairs = [(unquote(key), value) for key, value in pairs]
    encoded_pairs = [(quote(key), value) for key, value in decoded_pairs]
    normalized_query = urlencode(encoded_pairs)

    return ParseResult(
        scheme=uri.scheme,
        netloc=uri.netloc,
        path=uri.path,
        params=uri.params,
        query=normalized_query,
        fragment=uri.fragment).geturl()

def setter_decorator(fset):
    """
    Define a write-only property that, in addition to the given setter function, also
    provides a setter decorator defined as the property's getter function.

    This allows one to set the property either through traditional assignment, as a
    method argument, or through decoration::

        class Widget(object):
            @setter_decorator
            def handler(self, value):
                self._handler = value

        widget = Widget()

        # Method 1: Traditional assignment
        widget.handler = lambda input: process(input)

        # Method 2: Assignment via method argument
        widget.handler(lambda input: process(input))

        # Method 3: Assignment via decoration
        @widget.handler
        def handler(input):
            return process(input)

        # Method 3b: Assignment via decoration with extraneous parens
        @widget.handler()
        def handler(input):
            return process(input)
    """

    def fget(self):
        def inner(value):
            fset(self, value)

        def outer(value=None):
            if value:
                # We are being called with the desired value, either directly or
                # as a decorator.
                inner(value)
            else:
                # Assume we are being called as a decorator with extraneous parens,
                # so return the setter as the actual decorator.
                return inner

        return outer

    fdoc = fset.__doc__

    return property(fget, fset, None, fdoc)


class AbstractFilter(object):
    """
    A rule to apply to identify desired nodes.

    Args:
        name (str): The name of this filter.
        func (Callable[[Element, Any], bool]): A function that determines whether a given node
            matches a desired value.
        boolean (bool, optional): Whether the filter evaluates boolean values. Defaults to False.
        default (object, optional): A default desired value, if any. Defaults to None.
        skip_if (object, optional): A value which, if provided, signifies that this rule
            should be skipped.
        valid_values (List[Any], optional): The values the filter supports.
    """

    def __init__(self, name, func, boolean=None, default=None, skip_if=None, valid_values=None):
        self.name = name
        self.func = func
        self.default = default
        self.skip_if = skip_if
        self.valid_values = [True, False] if boolean else valid_values

    @property
    def has_default(self):
        """ bool: Whether this rule has a default desired value. """
        return self.default is not None

    @property
    def has_skip_if(self):
        """ bool: Whether this rule has a value for which it should be skipped. """
        return self.skip_if is not None

    def skip(self, value):
        """
        Returns whether this rule should be skipped for the given value.

        Args:
            value (Any): A value which may be used to match nodes.

        Returns:
            bool: Whether this rule should be skipped.
        """

        return self.has_skip_if and value == self.skip_if

    def _valid_value(self, value):
        """ bool: Whether the given value is valid. """

        if not self.valid_values:
            return True

        valid_values = (self.valid_values if isinstance(self.valid_values, list)
                        else list(self.valid_values))
        return value in valid_values


import os.path

import capybara
from capybara.exceptions import ElementNotFound, FileNotFound
from capybara.queries.base_query import BaseQuery

class ActionsMixin(object):
    """
    If the driver is capable of executing JavaScript, actions will wait for a set amount of time and
    continuously retry finding the element until either the element is found or the time expires.
    The length of time :meth:`find` will wait is controlled through
    :data:`capybara.default_max_wait_time`.
    """

    def attach_file(self, locator_or_path, path=None, **kwargs):
        """
        Find a file field on the page and attach a file given its path. The file field can be found
        via its name, id, or label text. ::

            page.attach_file(locator, "/path/to/file.png")

        Args:
            locator_or_path (str): Which field to attach the file to, or the path of the file that
                will be attached.
            path (str, optional): The path of the file that will be attached. Defaults to
                ``locator_or_path``.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.

        Raises:
            FileNotFound: No file exists at the given path.
        """

        if path is None:
            locator, path = None, locator_or_path
        else:
            locator = locator_or_path

        if not os.path.isfile(path):
            raise FileNotFound("cannot attach file, {0} does not exist".format(path))

        self.find("file_field", locator, **kwargs).set(path)

    def check(self, locator=None, allow_label_click=None, **kwargs):
        """
        Find a check box and mark it as checked. The check box can be found via name, id, or label
        text. ::

            page.check("German")

        Args:
            locator (str, optional): Which check box to check.
            allow_label_click (bool, optional): Attempt to click the label to toggle state if
                element is non-visible. Defaults to :data:`capybara.automatic_label_click`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        self._check_with_label(
            "checkbox", True, locator=locator, allow_label_click=allow_label_click, **kwargs)

    def choose(self, locator=None, allow_label_click=None, **kwargs):
        """
        Find a radio button and mark it as checked. The radio button can be found via name, id, or
        label text. ::

            page.choose("Male")

        Args:
            locator (str, optional): Which radio button to choose.
            allow_label_click (bool, optional): Attempt to click the label to toggle state if
                element is non-visible. Defaults to :data:`capybara.automatic_label_click`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        self._check_with_label(
            "radio_button", True, locator=locator, allow_label_click=allow_label_click, **kwargs)

    def click_button(self, locator=None, **kwargs):
        """
        Finds a button on the page and clicks it. This can be any ``<input>`` element of type
        submit, reset, image, or button, or it can be any ``<button>`` element. All buttons can be
        found by their id, value, or title. ``<button>`` elements can also be found by their text
        content, and image ``<input>`` elements by their alt attribute.

        Args:
            locator (str, optional): Which button to find.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        return self.find("button", locator, **kwargs).click()

    def click_link(self, locator=None, **kwargs):
        """
        Finds a link by id, text, or title and clicks it. Also looks at image alt text inside the
        link.

        Args:
            locator (str, optional): Text, id, title, or nested image's alt attribute.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        return self.find("link", locator, **kwargs).click()

    def click_link_or_button(self, locator=None, **kwargs):
        """
        Finds a button or link by id, text or value and clicks it. Also looks at image alt text
        inside the link.

        Args:
            locator (str, optional): Text, id, or value of link or button.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        return self.find("link_or_button", locator, **kwargs).click()

    click_on = click_link_or_button
    """ Alias for :meth:`click_link_or_button`. """

    def fill_in(self, locator=None, current_value=None, value=None, fill_options=None, **kwargs):
        """
        Locate a text field or text area and fill it in with the given text. The field can be found
        via its name, id, or label text. ::

            page.fill_in("Name", value="Bob")

        Args:
            locator (str, optional): Which field to fill in.
            current_value (str, optional): The current value of the field.
            value (str, optional): The value to fill in. Defaults to None.
            fill_options (Dict, optional): Driver-specific options regarding how to fill fields.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        if current_value is not None:
            kwargs["value"] = current_value

        fill_options = fill_options or {}

        self.find("fillable_field", locator, **kwargs).set(value, **fill_options)

    def select(self, value=None, field=None, **kwargs):
        """
        If the ``field`` argument is present, ``select`` finds a select box on the page and selects
        a particular option from it. Otherwise it finds an option inside the current scope and
        selects it. If the select box is a multiple select, ``select`` can be called multiple times
        to select more than one option. The select box can be found via its name, id, or label text.
        The option can be found by its text. ::

            page.select("March", field="Month")

        Args:
            value (str, optional): Which option to select.
            field (str, optional): The id, name, or label of the select box.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        if field:
            self.find("select", field, **kwargs).find("option", value, **kwargs).select_option()
        else:
            self.find("option", value, **kwargs).select_option()

    def uncheck(self, locator=None, allow_label_click=None, **kwargs):
        """
        Find a check box and uncheck it. The check box can be found via name, id, or label text. ::

            page.uncheck("German")

        Args:
            locator (str, optional): Which check box to uncheck.
            allow_label_click (bool, optional): Attempt to click the label to toggle state if
                element is non-visible. Defaults to :data:`capybara.automatic_label_click`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        self._check_with_label(
            "checkbox", False, locator=locator, allow_label_click=allow_label_click, **kwargs)

    def unselect(self, value=None, field=None, **kwargs):
        """
        Find a select box on the page and unselect a particular option from it. If the select box is
        a multiple select, ``unselect`` can be called multiple times to unselect more than one
        option. The select box can be found via its name, id, or label text. ::

            page.unselect("March", field="Month")

        Args:
            value (str, optional): Which option to unselect.
            field (str, optional): The id, name, or label of the select box.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        if field:
            self.find("select", field, **kwargs).find("option", value, **kwargs).unselect_option()
        else:
            self.find("option", value, **kwargs).unselect_option()

    def _check_with_label(self, selector, checked, locator=None, allow_label_click=None, visible=None, wait=None,
                          **kwargs):
        """
        Args:
            selector (str): The selector for the type of element that should be checked/unchecked.
            checked (bool): Whether the element should be checked.
            locator (str, optional): Which element to check.
            allow_label_click (bool, optional): Attempt to click the label to toggle state if
                element is non-visible. Defaults to :data:`capybara.automatic_label_click`.
            visible (bool | str, optional): The desired element visibility. Defaults to
                :data:`capybara.ignore_hidden_elements`.
            wait (int | float, optional): The number of seconds to wait to check the element.
                Defaults to :data:`capybara.default_max_wait_time`.
            **kwargs: Arbitrary keyword arguments for :class:`SelectorQuery`.
        """

        if allow_label_click is None:
            allow_label_click = capybara.automatic_label_click

        @self.synchronize(wait=BaseQuery.normalize_wait(wait))
        def check_with_label():
            element = None
            try:
                element = self.find(selector, locator, visible=visible, **kwargs)
                element.set(checked)
            except Exception as e:
                if not allow_label_click or not self._should_catch_error(e):
                    raise
                try:
                    if not element:
                        element = self.find(selector, locator, visible="all", **kwargs)
                    label = self.find("label", field=element, visible=True)
                    if element.checked != checked:
                        label.click()
                except Exception:
                    raise e

        check_with_label()


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

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 = []

    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()

    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)

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