Source code for capybara.queries.selector_query

from collections import Hashable
from functools import reduce
import re
from xpath.expression import AbstractExpression
from xpath.renderer import to_xpath

import capybara
from capybara.compat import bytes_, str_
from capybara.helpers import desc, normalize_text, toregex
from capybara.queries.base_query import BaseQuery
from capybara.result import Result
from capybara.selector import selectors
from capybara.utils import isregex


VALID_MATCH = ["first", "one", "prefer_exact", "smart"]


[docs]class SelectorQuery(BaseQuery): """ Queries for elements using a selector. If no locator is provided and the given selector is not a valid selector, the first argument is assumed to be the locator and the default selector will be used. Args: selector (str): The name of the selector to use. locator (str): An identifying string to use to locate desired elements. between (Iterable[int], optional): A range of acceptable counts. count (int, optional): The number of times the selector should match. Defaults to any number of times greater than zero. exact (bool, optional): Whether to exactly match the locator string. Defaults to :data:`capybara.exact`. exact_text (bool | str, optional): Whether to exactly match the text, or the exact text to match. Defaults to False. filter (Callable[[Element], bool], optional): A function for filtering results. match (str, optional): The matching strategy to use. Defaults to :data:`capybara.match`. 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. text (str | RegexObject, optional): Text that should be contained in matched elements. visible (bool | str, optional): The desired element visibility. Defaults to :data:`capybara.ignore_hidden_elements`. wait (bool | int | float, optional): Whether and how long to wait for synchronization. Defaults to :data:`capybara.default_max_wait_time`. **filter_options: Arbitrary keyword arguments for the selector's filters. """ def __init__(self, selector, locator=None, between=None, count=None, exact=None, exact_text=None, filter=None, match=None, maximum=None, minimum=None, text=None, visible=None, wait=None, **filter_options): if locator is None and ( not isinstance(selector, Hashable) or selector not in selectors ): locator = selector selector = capybara.default_selector self.selector = selectors[selector] self.expression = self.selector(locator) self.locator = locator self.options = { "between": between, "count": count, "exact": exact, "exact_text": exact_text, "filter": filter, "match": match, "maximum": maximum, "minimum": minimum, "text": text, "visible": visible, "wait": wait} self.filter_options = filter_options assert self.match in VALID_MATCH, \ "invalid option {match} for match, should be one of {valid_values}".format( match=desc(self.match), valid_values=", ".join(desc(VALID_MATCH))) @property def name(self): """ str: The name of selector. """ return self.selector.name @property def label(self): """ str: A short description of the selector. """ return self.selector.label or self.selector.name @property def kwargs(self): """ Dict[str, Any]: The keyword arguments with which this query was initialized. """ kwargs = {} kwargs.update(self.options) kwargs.update(self.filter_options) return kwargs @property def description(self): """ str: A long description of this query. """ description = self.label if self.locator: description += " {}".format(desc(self.locator)) if self.options["text"] is not None: description += " with text {}".format(desc(self.options["text"])) description += self.selector.description(self.filter_options) return description @property def exact(self): """ bool: Whether to exactly match the locator string. """ if self.options["exact"] is not None: return self.options["exact"] else: return capybara.exact @property def exact_text(self): return self.options["exact_text"] @property def match(self): """ str: The matching strategy to use. """ if self.options["match"] is not None: return self.options["match"] else: return capybara.match @property def visible(self): """ str: The desired element visibility. """ if self.options["visible"] is not None: if self.options["visible"] is True: return "visible" elif self.options["visible"] is False: return "all" else: return self.options["visible"] else: if capybara.ignore_hidden_elements: return "visible" else: return "all" @property def wait(self): """ int | float: How long to wait for synchronization. """ return self.normalize_wait(self.options["wait"])
[docs] def css(self): """ str: The CSS query for this selector. """ return self.expression
[docs] def xpath(self, exact=None): """ Returns the XPath query for this selector. Args: exact (bool, optional): Whether to exactly match text. Returns: str: The XPath query for this selector. """ exact = exact if exact is not None else self.exact if isinstance(self.expression, AbstractExpression): expression = self._apply_expression_filters(self.expression) return to_xpath(expression, exact=exact) else: return str_(self.expression)
[docs] def resolve_for(self, node, exact=None): """ Resolves this query relative to the given node. Args: node (node.Base): The node relative to which this query should be resolved. exact (bool, optional): Whether to exactly match text. Returns: list[Element]: A list of elements matched by this query. """ from capybara.driver.node import Node from capybara.node.element import Element from capybara.node.simple import Simple @node.synchronize def resolve(): if self.selector.format == "css": children = node._find_css(self.css()) else: children = node._find_xpath(self.xpath(exact)) def wrap(child): if isinstance(child, Node): return Element(node.session, child, node, self) else: return Simple(child) children = [wrap(child) for child in children] return Result(children, self) return resolve()
[docs] def matches_filters(self, node): """ Returns whether the given node matches all filters. Args: node (Element): The node to evaluate. Returns: bool: Whether the given node matches. """ visible = self.visible if self.options["text"]: if isregex(self.options["text"]): regex = self.options["text"] elif self.exact_text is True: regex = re.compile(r"\A{}\Z".format(re.escape(self.options["text"]))) else: regex = toregex(self.options["text"]) text = normalize_text( node.all_text if visible == "all" else node.visible_text) if not regex.search(text): return False if isinstance(self.exact_text, (bytes_, str_)): regex = re.compile(r"\A{}\Z".format(re.escape(self.exact_text))) text = normalize_text( node.all_text if visible == "all" else node.visible_text) if not regex.search(text): return False if visible == "visible": if not node.visible: return False elif visible == "hidden": if node.visible: return False for name, node_filter in iter(self._node_filters.items()): if name in self.filter_options: if not node_filter.matches(node, self.filter_options[name]): return False elif node_filter.has_default: if not node_filter.matches(node, node_filter.default): return False if self.options["filter"] and not self.options["filter"](node): return False return True
def _apply_expression_filters(self, expr): def apply_filter(memo, item): name, ef = item if name in self.filter_options: return ef.apply_filter(memo, self.filter_options[name]) elif ef.has_default: return ef.apply_filter(memo, ef.default) else: return memo return reduce(apply_filter, iter(self._expression_filters.items()), expr) @property def _expression_filters(self): return self.selector.expression_filters @property def _node_filters(self): return self.selector.node_filters