class Capybara::Selenium::Node
Constants
- GET_XPATH_SCRIPT
- OBSCURED_OR_OFFSET_SCRIPT
- RAPID_APPEND_TEXT
Public Instance Methods
[](name)
click to toggle source
# File lib/capybara/selenium/node.rb, line 27 def [](name) native.attribute(name.to_s) rescue Selenium::WebDriver::Error::WebDriverError nil end
all_text()
click to toggle source
# File lib/capybara/selenium/node.rb, line 18 def all_text text = driver.evaluate_script('arguments[0].textContent', self) || '' text.gsub(/[\u200b\u200e\u200f]/, '') .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ') .gsub(/\A[[:space:]&&[^\u00a0]]+/, '') .gsub(/[[:space:]&&[^\u00a0]]+\z/, '') .tr("\u00a0", ' ') end
click(keys = [], **options)
click to toggle source
# File lib/capybara/selenium/node.rb, line 108 def click(keys = [], **options) click_options = ClickOptions.new(keys, options) return native.click if click_options.empty? perform_with_options(click_options) do |action| target = click_options.coords? ? nil : native if click_options.delay.zero? action.click(target) else action.click_and_hold(target) action_pause(action, click_options.delay) action.release end end rescue StandardError => e if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) || e.message.include?('Other element would receive the click') scroll_to_center end raise e end
content_editable?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 204 def content_editable? native.attribute('isContentEditable') == 'true' end
disabled?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 197 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
double_click(keys = [], **options)
click to toggle source
# File lib/capybara/selenium/node.rb, line 148 def double_click(keys = [], **options) click_options = ClickOptions.new(keys, options) raise ArgumentError, "double_click doesn't support a delay option" unless click_options.delay.zero? perform_with_options(click_options) do |action| click_options.coords? ? action.double_click : action.double_click(native) end end
drag_to(element, drop_modifiers: [], **)
click to toggle source
# File lib/capybara/selenium/node.rb, line 165 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(*_)
click to toggle source
# File lib/capybara/selenium/node.rb, line 178 def drop(*_) raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser' end
hover()
click to toggle source
# File lib/capybara/selenium/node.rb, line 161 def hover scroll_if_needed { browser_action.move_to(native).perform } end
multiple?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 193 def multiple?; boolean_attr(self[:multiple]); end
obscured?(x: nil, y: nil)
click to toggle source
# File lib/capybara/selenium/node.rb, line 212 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()
click to toggle source
# File lib/capybara/selenium/node.rb, line 208 def path driver.evaluate_script GET_XPATH_SCRIPT, self end
readonly?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 192 def readonly?; boolean_attr(self[:readonly]); end
rect()
click to toggle source
# File lib/capybara/selenium/node.rb, line 219 def rect native.rect end
right_click(keys = [], **options)
click to toggle source
# File lib/capybara/selenium/node.rb, line 131 def right_click(keys = [], **options) click_options = ClickOptions.new(keys, options) perform_with_options(click_options) do |action| target = click_options.coords? ? nil : native if click_options.delay.zero? action.context_click(target) elsif w3c? action.move_to(target) if target action.pointer_down(:right).then do |act| action_pause(act, click_options.delay) end.pointer_up(:right) else raise ArgumentError, 'Delay is not supported when right clicking with legacy (non-w3c) selenium driver' end end end
select_option()
click to toggle source
# File lib/capybara/selenium/node.rb, line 98 def select_option click unless selected? || disabled? end
selected?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 194 def selected?; boolean_attr(native.selected?); end
Also aliased as: checked?
send_keys(*args)
click to toggle source
# File lib/capybara/selenium/node.rb, line 157 def send_keys(*args) native.send_keys(*args) end
set(value, **options)
click to toggle source
Set the value of the form element to the given value.
@param [String] value The new value @param [Hash{}] options Driver specific options for how to set the value @option options [Symbol,Array] :clear (nil) The method used to clear the previous value <br/>
nil => clear via javascript <br/> :none => append the new value to the existing value <br/> :backspace => send backspace keystrokes to clear the field <br/> Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
@option options [Boolean] :rapid (nil) Whether setting text inputs should use a faster "rapid" mode<br/>
nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode<br/> true => Rapid mode will be used regardless of input length<br/> false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain Javascript interactions on form inputs<br/>
# File lib/capybara/selenium/node.rb, line 61 def set(value, **options) 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, **options) end when 'textarea' set_text(value, **options) else set_content_editable(value) end end
shadow_root()
click to toggle source
# File lib/capybara/selenium/node.rb, line 223 def shadow_root raise_error 'You must be using Selenium 4.1+ for shadow_root support' unless native.respond_to? :shadow_root root = native.shadow_root root && build_node(native.shadow_root) end
style(styles)
click to toggle source
# File lib/capybara/selenium/node.rb, line 41 def style(styles) styles.to_h { |style| [style, native.css_value(style)] } end
tag_name()
click to toggle source
# File lib/capybara/selenium/node.rb, line 182 def tag_name @tag_name ||= if native.respond_to? :tag_name native.tag_name.downcase else shadow_root? ? 'ShadowRoot' : 'Unknown' end end
unselect_option()
click to toggle source
# File lib/capybara/selenium/node.rb, line 102 def unselect_option raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple? click if selected? end
value()
click to toggle source
# File lib/capybara/selenium/node.rb, line 33 def value if tag_name == 'select' && multiple? native.find_elements(:css, 'option:checked').map { |el| el[:value] || el.text } else native[:value] end end
visible?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 191 def visible?; boolean_attr(native.displayed?); end
visible_text()
click to toggle source
# File lib/capybara/selenium/node.rb, line 12 def visible_text raise NotImplementedError, 'Getting visible text is not currently supported directly on shadow roots' if shadow_root? native.text end
Protected Instance Methods
scroll_if_needed() { || ... }
click to toggle source
# File lib/capybara/selenium/node.rb, line 232 def scroll_if_needed yield rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError scroll_to_center yield end
scroll_to_center()
click to toggle source
# File lib/capybara/selenium/node.rb, line 239 def scroll_to_center script = <<-'JS' try { arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'}); } catch(e) { arguments[0].scrollIntoView(true); } JS begin driver.execute_script(script, self) rescue StandardError # Swallow error if scrollIntoView with options isn't supported end end
Private Instance Methods
action_pause(action, duration)
click to toggle source
# File lib/capybara/selenium/node.rb, line 485 def action_pause(action, duration) if w3c? if Selenium::WebDriver::VERSION.to_f >= 4.2 action.pause(device: action.pointer_inputs.first, duration: duration) else action.pause(action.pointer_inputs.first, duration) end else action.pause(duration) end end
action_with_modifiers(click_options) { |actions| ... }
click to toggle source
# File lib/capybara/selenium/node.rb, line 417 def action_with_modifiers(click_options) actions = browser_action.tap do |acts| if click_options.coords? if click_options.center_offset? if Selenium::WebDriver::VERSION.to_f >= 4.3 acts.move_to(native, *click_options.coords) else ::Selenium::WebDriver.logger.suppress_deprecations do acts.move_to(native).move_by(*click_options.coords) end end elsif Selenium::WebDriver::VERSION.to_f >= 4.3 right_by, down_by = *click_options.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) else ::Selenium::WebDriver.logger.suppress_deprecations do acts.move_to(native, *click_options.coords) end end else acts.move_to(native) end end modifiers_down(actions, click_options.keys) yield actions modifiers_up(actions, click_options.keys) actions.perform ensure act = browser_action act.release_actions if act.respond_to?(:release_actions) end
attrs(*attr_names)
click to toggle source
# File lib/capybara/selenium/node.rb, line 520 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()
click to toggle source
# File lib/capybara/selenium/node.rb, line 301 def auto_rapid_set_length 30 end
boolean_attr(val)
click to toggle source
# File lib/capybara/selenium/node.rb, line 270 def boolean_attr(val) val && (val != 'false') end
bridge()
click to toggle source
# File lib/capybara/selenium/node.rb, line 468 def bridge browser.send(:bridge) end
browser()
click to toggle source
# File lib/capybara/selenium/node.rb, line 464 def browser driver.browser end
browser_action()
click to toggle source
# File lib/capybara/selenium/node.rb, line 472 def browser_action browser.action end
build_node(native_node, initial_cache = {})
click to toggle source
# File lib/capybara/selenium/node.rb, line 516 def build_node(native_node, initial_cache = {}) self.class.new(driver, native_node, initial_cache) end
capabilities()
click to toggle source
# File lib/capybara/selenium/node.rb, line 476 def capabilities browser.capabilities end
each_key(keys, &block)
click to toggle source
# File lib/capybara/selenium/node.rb, line 508 def each_key(keys, &block) normalize_keys(keys).each(&block) end
find_context()
click to toggle source
# File lib/capybara/selenium/node.rb, line 512 def find_context native end
modifiers_down(actions, keys)
click to toggle source
# File lib/capybara/selenium/node.rb, line 454 def modifiers_down(actions, keys) each_key(keys) { |key| actions.key_down(key) } actions end
modifiers_up(actions, keys)
click to toggle source
# File lib/capybara/selenium/node.rb, line 459 def modifiers_up(actions, keys) each_key(keys) { |key| actions.key_up(key) } actions end
native_id()
click to toggle source
# File lib/capybara/selenium/node.rb, line 532 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)
click to toggle source
# File lib/capybara/selenium/node.rb, line 497 def normalize_keys(keys) keys.map do |key| case key when :ctrl then :control when :command, :cmd then :meta else key end end end
perform_with_options(click_options) { |action| ... }
click to toggle source
# File lib/capybara/selenium/node.rb, line 305 def perform_with_options(click_options, &block) raise ArgumentError, 'A block must be provided' unless block scroll_if_needed do action_with_modifiers(click_options) do |action| if block yield action else click_options.coords? ? action.click : action.click(native) end end end end
select_node()
click to toggle source
a reference to the select node if this is an option node
# File lib/capybara/selenium/node.rb, line 275 def select_node find_xpath(XPath.ancestor(:select)[1]).first end
set_color(value)
click to toggle source
# File lib/capybara/selenium/node.rb, line 343 def set_color(value) # rubocop:disable Naming/AccessorMethodName update_value_js(value) end
set_content_editable(value)
click to toggle source
# File lib/capybara/selenium/node.rb, line 393 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)
click to toggle source
# File lib/capybara/selenium/node.rb, line 319 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)
click to toggle source
# File lib/capybara/selenium/node.rb, line 335 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)
click to toggle source
# File lib/capybara/selenium/node.rb, line 365 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.expand_path(pn) end.join("\n") native.send_keys(file_names) end end
set_range(value)
click to toggle source
# File lib/capybara/selenium/node.rb, line 347 def set_range(value) # rubocop:disable Naming/AccessorMethodName update_value_js(value) end
set_text(value, clear: nil, rapid: nil, **_unused)
click to toggle source
# File lib/capybara/selenium/node.rb, line 279 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)
click to toggle source
# File lib/capybara/selenium/node.rb, line 327 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
shadow_root?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 538 def shadow_root? defined?(::Selenium::WebDriver::ShadowRoot) && native.is_a?(::Selenium::WebDriver::ShadowRoot) end
sibling_index(parent, node, selector)
click to toggle source
# File lib/capybara/selenium/node.rb, line 256 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
update_value_js(value)
click to toggle source
# File lib/capybara/selenium/node.rb, line 351 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
w3c?()
click to toggle source
# File lib/capybara/selenium/node.rb, line 480 def w3c? (defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= 4)) || capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities) end
with_file_detector() { || ... }
click to toggle source
# File lib/capybara/selenium/node.rb, line 375 def with_file_detector if driver.options[: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