Source code for capybara.result
import re
from capybara.compat import cmp
from capybara.helpers import declension, desc, failure_message
from capybara.utils import cached_property
[docs]class Result(object):
"""
A :class:`Result` represents a collection of :class:`Element` objects on the page. It is
possible to interact with this collection similar to a List because it implements
``__getitem__`` and offers the following container methods through delegation:
* ``__len__``
* ``__nonzero__`` (Python 2)
Args:
elements (List[Element]): The initial list of elements found by the query.
query (SelectorQuery): The query used to find elements.
"""
def __init__(self, elements, query):
self._elements = elements
self._result_cache = []
self._result_iter = (node for node in elements
if query.matches_filters(node))
self.query = query
def __getitem__(self, key):
max_index = key.stop if isinstance(key, slice) else key
if max_index is None or max_index < 0:
return self._full_results[key]
else:
self._cache_at_least(max_index + 1)
return self._result_cache[key]
def __len__(self):
return len(self._full_results)
def __nonzero__(self):
return self._cache_at_least(1)
def __iter__(self):
for node in self._result_cache:
yield node
for node in self._result_iter:
self._result_cache.append(node)
yield node
@property
def compare_count(self):
"""
Returns how the result count compares to the query options.
The return value is negative if too few results were found, zero if enough were found, and
positive if too many were found.
Returns:
int: -1, 0, or 1.
"""
if self.query.options["count"] is not None:
count_opt = int(self.query.options["count"])
self._cache_at_least(count_opt + 1)
return cmp(len(self._result_cache), count_opt)
if self.query.options["minimum"] is not None:
min_opt = int(self.query.options["minimum"])
if not self._cache_at_least(min_opt):
return -1
if self.query.options["maximum"] is not None:
max_opt = int(self.query.options["maximum"])
if self._cache_at_least(max_opt + 1):
return 1
if self.query.options["between"] is not None:
between = self.query.options["between"]
min_opt, max_opt = between[0], between[-1]
if not self._cache_at_least(min_opt):
return -1
if self._cache_at_least(max_opt + 1):
return 1
return 0
return 0
@property
def matches_count(self):
""" bool: Whether the result count matches the query options. """
return self.compare_count == 0
@property
def failure_message(self):
""" str: A message describing the query failure. """
message = failure_message(self.query.description, self.query.options)
if len(self) > 0:
message += ", found {count} {matches}: {results}".format(
count=len(self),
matches=declension("match", "matches", len(self)),
results=", ".join([desc(node.text) for node in self]))
else:
message += " but there were no matches"
if self._rest:
elements = ", ".join([desc(element.text) for element in self._rest])
message += (". Also found {}, which matched the selector"
" but not all filters.".format(elements))
return message
@property
def negative_failure_message(self):
return re.sub(r"(to find)", r"not \1", self.failure_message)
def _cache_at_least(self, size):
"""
Attempts to fill the result cache with at least the given number of results.
Returns:
bool: Whether the cache contains at least the given size.
"""
try:
while len(self._result_cache) < size:
self._result_cache.append(next(self._result_iter))
return True
except StopIteration:
return False
@cached_property
def _full_results(self):
return list(iter(self))
@cached_property
def _rest(self):
return list(set(self._elements) - set(self._full_results))