Source code for capybara.selenium.node

from contextlib import contextmanager
from functools import wraps
from selenium.common.exceptions import MoveTargetOutOfBoundsException, WebDriverException
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys

try:
    from selenium.common.exceptions import ElementClickInterceptedException
except ImportError:
    class ElementClickInterceptedException(WebDriverException):
        pass

from capybara.exceptions import ReadOnlyElementError, UnselectNotAllowed
from capybara.driver.node import Node as Base
from capybara.helpers import normalize_whitespace


[docs]class Node(Base): @property def tag_name(self): return self.native.tag_name @property def visible(self): return self.native.is_displayed() @property def value(self): if self.tag_name == "select" and self.multiple: options = self.native.find_elements_by_xpath(".//option") selected_options = filter(lambda opt: opt.is_selected(), options) return [opt.get_attribute("value") for opt in selected_options] else: return self.native.get_attribute("value")
[docs] def style(self, styles): return {style: self.native.value_of_css_property(style) for style in styles}
@property def selected(self): return self.native.is_selected() @property def multiple(self): val = self["multiple"] return bool(val) and val != "false" @property def path(self): path = [self] + list(reversed(list(self._find_xpath("ancestor::*")))) result = [] while path: node = path.pop(0) parent = path[0] if path else None if parent: siblings = parent._find_xpath(node.tag_name) index = next(i for i, elem in enumerate(siblings) if elem == node) if index == 0: try: next(siblings) except StopIteration: index = None else: index = None if index is None: result.insert(0, node.tag_name) else: result.insert(0, "{tag}[{i}]".format(tag=node.tag_name, i=index + 1)) return "/" + "/".join(result) @property def all_text(self): text = self.driver.browser.execute_script("return arguments[0].textContent", self.native) return normalize_whitespace(text) @property def visible_text(self): return normalize_whitespace(self.native.text) def __getitem__(self, name): return self.native.get_attribute(name) def _find_css(self, css): cls = type(self) return (cls(self.driver, element) for element in self.native.find_elements_by_css_selector(css)) def _find_xpath(self, xpath): cls = type(self) return (cls(self.driver, element) for element in self.native.find_elements_by_xpath(xpath))
[docs] def click(self, *keys, **offset): try: if not any(keys) and not self._has_coords(offset): self.native.click() else: @self._scroll_if_needed def click(): with self._action_with_modifiers(keys, offset) as a: a.click() if self._has_coords(offset) else a.click(self.native) click() except WebDriverException as e: if ( # Marionette isinstance(e, ElementClickInterceptedException) or # Chrome "Other element would receive the click" in e.msg ): self._scroll_to_center() raise
[docs] def double_click(self, *keys, **offset): @self._scroll_if_needed def double_click(): with self._action_with_modifiers(keys, offset) as a: a.double_click() if self._has_coords(offset) else a.double_click(self.native) double_click()
[docs] def drag_to(self, element): ActionChains(self.driver.browser).drag_and_drop(self.native, element.native).perform()
[docs] def hover(self): ActionChains(self.driver.browser).move_to_element(self.native).perform()
[docs] def right_click(self, *keys, **offset): @self._scroll_if_needed def right_click(): with self._action_with_modifiers(keys, offset) as a: a.context_click() if self._has_coords(offset) else a.context_click(self.native) right_click()
[docs] def select_option(self): if not self.selected and not self.disabled: self.native.click()
[docs] def send_keys(self, *args): self.native.send_keys(*args)
[docs] def set(self, value, clear=None): tag_name = self.tag_name type_attr = self["type"] if tag_name == "input" and type_attr == "radio": self.click() elif tag_name == "input" and type_attr == "checkbox": current = self.native.get_attribute("checked") == "true" if current ^ value: self.click() elif tag_name == "textarea" or tag_name == "input": if self.readonly: raise ReadOnlyElementError() if clear == "backspace": # Clear field by sending the correct number of backspace keys. backspaces = [Keys.BACKSPACE] * len(self.value) self.native.send_keys(*([Keys.END] + backspaces + [value])) else: # Clear field by JavaScript assignment of the value property. self.driver.browser.execute_script("arguments[0].value = ''", self.native) self.native.send_keys(value) elif self["isContentEditable"]: self.native.click() script = """ var range = document.createRange(); var sel = window.getSelection(); range.selectNodeContents(arguments[0]); sel.removeAllRanges(); sel.addRange(range); """ self.driver.browser.execute_script(script, self.native) self.native.send_keys(value)
[docs] def unselect_option(self): if not self._select_node.multiple: raise UnselectNotAllowed("Cannot unselect option from single select box.") if self.selected: self.native.click()
def __eq__(self, other): return self.native == other.native def __hash__(self): return hash(self.native) @property def checked(self): return self.selected @property def selected(self): return self.native.is_selected() @property def disabled(self): if not self.native.is_enabled(): return True if self.tag_name == "fieldset": return any(self._find_xpath("ancestor-or-self::fieldset[@disabled]")) return False @property def readonly(self): val = self["readonly"] return bool(val) and val != "false" @property def _select_node(self): return next(self._find_xpath("./ancestor::select")) @staticmethod def _has_coords(options): return bool(options.get('x')) and bool(options.get('y')) def _scroll_if_needed(self, fn): @wraps(fn) def wrapper(): try: fn() except MoveTargetOutOfBoundsException: self._scroll_to_center() fn() return wrapper def _scroll_to_center(self): try: self.driver.execute_script(""" arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'}) """, self) except: # Swallow error if scrollIntoView with options isn't supported. pass @contextmanager def _action_with_modifiers(self, keys, offset): actions = ActionChains(self.driver.browser) if self._has_coords(offset): actions.move_to_element_with_offset(self.native, offset['x'], offset['y']) self._modifiers_down(actions, keys) yield actions self._modifiers_up(actions, keys) actions.perform() @staticmethod def _modifiers_down(actions, keys): for key in keys: actions.key_down(key) @staticmethod def _modifiers_up(actions, keys): for key in keys: actions.key_up(key)