Class: Capybara::Selenium::Node
Relationships & Source Files | |
Namespace Children | |
Modules:
| |
Classes:
| |
Extension / Inclusion / Inheritance Descendants | |
Subclasses:
|
|
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
Driver::Node
|
|
Instance Chain:
|
|
Inherits: |
Capybara::Driver::Node
|
Defined in: | lib/capybara/selenium/node.rb, lib/capybara/selenium/extensions/file_input_click_emulation.rb, lib/capybara/selenium/extensions/html5_drag.rb, lib/capybara/selenium/extensions/modifier_keys_stack.rb |
Constant Summary
-
GET_XPATH_SCRIPT =
# File 'lib/capybara/selenium/node.rb', line 513<<~'JS' (function(el, xml){ var xpath = ''; var pos, tempitem2; if (el.getRootNode && el.getRootNode() instanceof ShadowRoot) { return "(: Shadow DOM element - no XPath :)"; }; while(el !== xml.documentElement) { pos = 0; tempitem2 = el; while(tempitem2) { if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) { // If it is ELEMENT_NODE of the same name pos += 1; } tempitem2 = tempitem2.previousSibling; } if (el.namespaceURI != xml.documentElement.namespaceURI) { xpath = "*[local-name()='"el.nodeName"' and namespace-uri()='"(el.namespaceURI===null?'':el.namespaceURI)"']["pos']''/'xpath; } else { xpath = el.nodeName.toUpperCase()"["pos"]/"xpath; } el = el.parentNode; } xpath = '/'xml.documentElement.nodeName.toUpperCase()'/'+xpath; xpath = xpath.replace(/\/$/, ''); return xpath; })(arguments[0], document) JS
-
OBSCURED_OR_OFFSET_SCRIPT =
# File 'lib/capybara/selenium/node.rb', line 545<<~JS (function(el, x, y) { var box = el.getBoundingClientRect(); if (x == null) x = box.width/2; if (y == null) y = box.height/2 ; var px = box.left + x, py = box.top + y, e = document.elementFromPoint(px, py); if (!el.contains(e)) return true; return { x: px, y: py }; })(arguments[0], arguments[1], arguments[2]) JS
-
RAPID_APPEND_TEXT =
# File 'lib/capybara/selenium/node.rb', line 562<<~JS (function(el, value) { value = el.value + value; if (el.maxLength && el.maxLength != -1){ value = value.slice(0, el.maxLength); } el.value = value; })(arguments[0], arguments[1]) JS
Node::WhitespaceNormalizer
- Included
BREAKING_SPACES, EMPTY_LINES, LEADING_SPACES, LEFT_TO_RIGHT_MARK, LINE_SEPERATOR, NON_BREAKING_SPACE, PARAGRAPH_SEPERATOR, REMOVED_CHARACTERS, RIGHT_TO_LEFT_MARK, SQUEEZED_SPACES, TRAILING_SPACES, ZERO_WIDTH_SPACE
Scroll
- Included
Class Method Summary
Driver::Node
- Inherited
Instance Attribute Summary
-
#checked?
readonly
Alias for #selected?.
- #content_editable? ⇒ Boolean readonly
- #disabled? ⇒ Boolean readonly
- #multiple? ⇒ Boolean readonly
- #readonly? ⇒ Boolean readonly
- #selected? ⇒ Boolean (also: #checked?) readonly
- #shadow_root readonly
- #visible? ⇒ Boolean readonly
- #shadow_root? ⇒ Boolean readonly private
Driver::Node
- Inherited
Instance Method Summary
- #[](name)
- #all_text
- #click(keys = [], **options)
- #double_click(keys = [], **options)
- #drag_to(element, drop_modifiers: [])
- #drop(*_)
- #hover
- #obscured?(x: nil, y: nil) ⇒ Boolean
- #path
- #rect
- #right_click(keys = [], **options)
- #select_option
- #send_keys(*args)
-
#set(value, **options)
Set the value of the form element to the given value.
- #style(styles)
- #tag_name
- #unselect_option
- #value
- #visible_text
- #action_pause(action, duration) private
- #action_with_modifiers(click_options) private
- #attrs(*attr_names) private
- #auto_rapid_set_length private
- #boolean_attr(val) private
- #bridge private
- #browser private
- #browser_action private
- #build_node(native_node, initial_cache = {}) private
- #capabilities private
- #each_key(keys, &block) private
- #find_context private
- #modifiers_down(actions, keys) private
- #modifiers_up(actions, keys) private
- #native_id private
- #normalize_keys(keys) private
- #perform_with_options(click_options, &block) private
-
#select_node
private
a reference to the select node if this is an option node.
- #set_color(value) private
- #set_content_editable(value) private
- #set_date(value) private
- #set_datetime_local(value) private
- #set_file(value) private
- #set_range(value) private
- #set_text(value, clear: nil, rapid: nil, **_unused) private
- #set_time(value) private
- #sibling_index(parent, node, selector) private
- #update_value_js(value) private
- #with_file_detector private
Scroll
- Included
Find
- Included
#find_css, #find_xpath, #build_hints_js, #es_context, #filter_by_text, #find_by, #gather_hints, #is_displayed_atom |
Node::WhitespaceNormalizer
- Included
#normalize_spacing | Normalizes the spacing of a node’s text to be similar to what matchers might expect. |
#normalize_visible_spacing | Variant on |
Driver::Node
- Inherited
Constructor Details
This class inherits a constructor from Capybara::Driver::Node
Instance Attribute Details
#checked? (readonly)
Alias for #selected?.
# File 'lib/capybara/selenium/node.rb', line 191
alias :checked? :selected?
#content_editable? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/capybara/selenium/node.rb', line 200
def content_editable? native.attribute('isContentEditable') == 'true' end
#disabled? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/capybara/selenium/node.rb', line 193
def disabled? return true unless native.enabled? # WebDriver only defines `disabled?` for form controls but fieldset makes sense too find_xpath('self::fieldset/ancestor-or-self::fieldset[@disabled]').any? end
#multiple? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/capybara/selenium/node.rb', line 189
def multiple?; boolean_attr(self[:multiple]); end
#readonly? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/capybara/selenium/node.rb', line 188
def readonly?; boolean_attr(self[:readonly]); end
#selected? ⇒ Boolean
(readonly)
Also known as: #checked?
[ GitHub ]
# File 'lib/capybara/selenium/node.rb', line 190
def selected?; boolean_attr(native.selected?); end
#shadow_root (readonly)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 219
def shadow_root root = native.shadow_root root && build_node(native.shadow_root) end
#shadow_root? ⇒ Boolean
(readonly, private)
[ GitHub ]
#visible? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/capybara/selenium/node.rb', line 187
def visible?; boolean_attr(native.displayed?); end
Instance Method Details
#[](name)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 25
def [](name) native.attribute(name.to_s) rescue Selenium::WebDriver::Error::WebDriverError nil end
#action_pause(action, duration) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 464
def action_pause(action, duration) action.pause(device: action.pointer_inputs.first, duration: duration) end
#action_with_modifiers(click_options) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 411
def action_with_modifiers( ) actions = browser_action.tap do |acts| if .coords? if .center_offset? acts.move_to(native, * .coords) else right_by, down_by = * .coords size = native.size left_offset = (size[:width] / 2).to_i top_offset = (size[:height] / 2).to_i left = -left_offset + right_by top = -top_offset + down_by acts.move_to(native, left, top) end else acts.move_to(native) end end modifiers_down(actions, .keys) yield actions modifiers_up(actions, .keys) actions.perform ensure act = browser_action act.release_actions if act.respond_to?(:release_actions) end
#all_text
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 20
def all_text text = driver.evaluate_script('arguments[0].textContent', self) || '' normalize_spacing(text) end
#attrs(*attr_names) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 491
def attrs(*attr_names) return attr_names.map { |name| self[name.to_s] } if ENV['CAPYBARA_THOROUGH'] driver.evaluate_script <<~JS, self, attr_names.map(&:to_s) (function(el, names){ return names.map(function(name){ return el[name] }); })(arguments[0], arguments[1]); JS end
#auto_rapid_set_length (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 295
def auto_rapid_set_length 30 end
#boolean_attr(val) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 264
def boolean_attr(val) val && (val != 'false') end
#bridge (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 452
def bridge browser.send(:bridge) end
#browser (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 448
def browser driver.browser end
#browser_action (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 456
def browser_action browser.action end
#build_node(native_node, initial_cache = {}) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 487
def build_node(native_node, initial_cache = {}) self.class.new(driver, native_node, initial_cache) end
#capabilities (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 460
def capabilities browser.capabilities end
#click(keys = [], **options)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 106
def click(keys = [], ** ) = ClickOptions.new(keys, ) return native.click if .empty? ( ) do |action| target = .coords? ? nil : native if .delay.zero? action.click(target) else action.click_and_hold(target) action_pause(action, .delay) action.release end end rescue StandardError => e if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) || e. .include?('Other element would receive the click') scroll_to_center end raise e end
#double_click(keys = [], **options)
# File 'lib/capybara/selenium/node.rb', line 144
def double_click(keys = [], ** ) = ClickOptions.new(keys, ) raise ArgumentError, "double_click doesn't support a delay option" unless .delay.zero? ( ) do |action| .coords? ? action.double_click : action.double_click(native) end end
#drag_to(element, drop_modifiers: [])
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 161
def drag_to(element, drop_modifiers: [], **) drop_modifiers = Array(drop_modifiers) # Due to W3C spec compliance - The Actions API no longer scrolls to elements when necessary # which means Seleniums `drag_and_drop` is now broken - do it manually scroll_if_needed { browser_action.click_and_hold(native).perform } # element.scroll_if_needed { browser_action.move_to(element.native).release.perform } element.scroll_if_needed do keys_down = modifiers_down(browser_action, drop_modifiers) keys_up = modifiers_up(keys_down.move_to(element.native).release, drop_modifiers) keys_up.perform end end
#drop(*_)
# File 'lib/capybara/selenium/node.rb', line 174
def drop(*_) raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser' end
#each_key(keys, &block) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 479
def each_key(keys, &block) normalize_keys(keys).each(&block) end
#find_context (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 483
def find_context native end
#hover
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 157
def hover scroll_if_needed { browser_action.move_to(native).perform } end
#modifiers_down(actions, keys) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 438
def modifiers_down(actions, keys) each_key(keys) { |key| actions.key_down(key) } actions end
#modifiers_up(actions, keys) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 443
def modifiers_up(actions, keys) each_key(keys) { |key| actions.key_up(key) } actions end
#native_id (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 503
def native_id # Selenium 3 -> 4 changed the return of ref type_or_id, id = native.ref id || type_or_id end
#normalize_keys(keys) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 468
def normalize_keys(keys) keys.map do |key| case key when :ctrl then :control when :command, :cmd then : else key end end end
#obscured?(x: nil, y: nil) ⇒ Boolean
# File 'lib/capybara/selenium/node.rb', line 208
def obscured?(x: nil, y: nil) res = driver.evaluate_script(OBSCURED_OR_OFFSET_SCRIPT, self, x, y) return true if res == true driver.frame_obscured_at?(x: res['x'], y: res['y']) end
#path
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 204
def path driver.evaluate_script GET_XPATH_SCRIPT, self end
#perform_with_options(click_options, &block) (private)
# File 'lib/capybara/selenium/node.rb', line 299
def (, &block) raise ArgumentError, 'A block must be provided' unless block scroll_if_needed do action_with_modifiers( ) do |action| if block yield action else .coords? ? action.click : action.click(native) end end end end
#rect
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 215
def rect native.rect end
#right_click(keys = [], **options)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 129
def right_click(keys = [], ** ) = ClickOptions.new(keys, ) ( ) do |action| target = .coords? ? nil : native if .delay.zero? action.context_click(target) else action.move_to(target) if target action.pointer_down(:right).then do |act| action_pause(act, .delay) end.pointer_up(:right) end end end
#select_node (private)
a reference to the select node if this is an option node
# File 'lib/capybara/selenium/node.rb', line 269
def select_node find_xpath(XPath.ancestor(:select)[1]).first end
#select_option
[ GitHub ]#send_keys(*args)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 153
def send_keys(*args) native.send_keys(*args) end
#set(value, **options)
Set the value of the form element to the given value.
# File 'lib/capybara/selenium/node.rb', line 59
def set(value, ** ) if value.is_a?(Array) && !multiple? raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" end tag_name, type = attrs(:tagName, :type).map { |val| val&.downcase } @tag_name ||= tag_name case tag_name when 'input' case type when 'radio' click when 'checkbox' click if value ^ checked? when 'file' set_file(value) when 'date' set_date(value) when 'time' set_time(value) when 'datetime-local' set_datetime_local(value) when 'color' set_color(value) when 'range' set_range(value) else set_text(value, ** ) end when 'textarea' set_text(value, ** ) else set_content_editable(value) end end
#set_color(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 337
def set_color(value) # rubocop:disable Naming/AccessorMethodName update_value_js(value) end
#set_content_editable(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 387
def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName # Ensure we are focused on the element click editable = driver.execute_script <<-JS, self if (arguments[0].isContentEditable) { var range = document.createRange(); var sel = window.getSelection(); arguments[0].focus(); range.selectNodeContents(arguments[0]); sel.removeAllRanges(); sel.addRange(range); return true; } return false; JS # The action api has a speed problem but both chrome and firefox 58 raise errors # if we use the faster direct send_keys. For now just send_keys to the element # we've already focused. # native.send_keys(value.to_s) browser_action.send_keys(value.to_s).perform if editable end
#set_date(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 313
def set_date(value) # rubocop:disable Naming/AccessorMethodName value = SettableValue.new(value) return set_text(value) unless value.dateable? # TODO: this would be better if locale can be detected and correct keystrokes sent update_value_js(value.to_date_str) end
#set_datetime_local(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 329
def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName value = SettableValue.new(value) return set_text(value) unless value.timeable? # TODO: this would be better if locale can be detected and correct keystrokes sent update_value_js(value.to_datetime_str) end
#set_file(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 359
def set_file(value) # rubocop:disable Naming/AccessorMethodName with_file_detector do path_names = value.to_s.empty? ? [] : value file_names = Array(path_names).map do |pn| Pathname.new(pn).absolute? ? pn : File. (pn) end.join("\n") native.send_keys(file_names) end end
#set_range(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 341
def set_range(value) # rubocop:disable Naming/AccessorMethodName update_value_js(value) end
#set_text(value, clear: nil, rapid: nil, **_unused) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 273
def set_text(value, clear: nil, rapid: nil, **_unused) value = value.to_s if value.empty? && clear.nil? native.clear elsif clear == :backspace # Clear field by sending the correct number of backspace keys. backspaces = [:backspace] * self.value.to_s.length send_keys(:end, *backspaces, value) elsif clear.is_a? Array send_keys(*clear, value) else driver.execute_script 'arguments[0].select()', self unless clear == :none if rapid == true || ((value.length > auto_rapid_set_length) && rapid != false) send_keys(value[0..3]) driver.execute_script RAPID_APPEND_TEXT, self, value[4...-3] send_keys(value[-3..]) else send_keys(value) end end end
#set_time(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 321
def set_time(value) # rubocop:disable Naming/AccessorMethodName value = SettableValue.new(value) return set_text(value) unless value.timeable? # TODO: this would be better if locale can be detected and correct keystrokes sent update_value_js(value.to_time_str) end
#sibling_index(parent, node, selector) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 250
def sibling_index(parent, node, selector) siblings = parent.find_xpath(selector) case siblings.size when 0 '[ERROR]' # IE doesn't support full XPath (namespace-uri, etc) when 1 '' # index not necessary when only one matching element else idx = siblings.index(node) # Element may not be found in the siblings if it has gone away idx.nil? ? '[ERROR]' : "[#{idx + 1}]" end end
#style(styles)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 39
def style(styles) styles.to_h { |style| [style, native.css_value(style)] } end
#tag_name
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 178
def tag_name @tag_name ||= if native.respond_to? :tag_name native.tag_name.downcase else shadow_root? ? 'ShadowRoot' : 'Unknown' end end
#unselect_option
# File 'lib/capybara/selenium/node.rb', line 100
def unselect_option raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple? click if selected? end
#update_value_js(value) (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 345
def update_value_js(value) driver.execute_script(<<-JS, self, value) if (arguments[0].readOnly) { return }; if (document.activeElement !== arguments[0]){ arguments[0].focus(); } if (arguments[0].value != arguments[1]) { arguments[0].value = arguments[1] arguments[0].dispatchEvent(new InputEvent('input')); arguments[0].dispatchEvent(new Event('change', { bubbles: true })); } JS end
#value
[ GitHub ]#visible_text
# File 'lib/capybara/selenium/node.rb', line 14
def visible_text raise NotImplementedError, 'Getting visible text is not currently supported directly on shadow roots' if shadow_root? native.text end
#with_file_detector (private)
[ GitHub ]# File 'lib/capybara/selenium/node.rb', line 369
def with_file_detector if driver. [:browser] == :remote && bridge.respond_to?(:file_detector) && bridge.file_detector.nil? begin bridge.file_detector = lambda do |(fn, *)| str = fn.to_s str if File.exist?(str) end yield ensure bridge.file_detector = nil end else yield end end