class Capybara::Selenium::Node

Constants

GET_XPATH_SCRIPT
OBSCURED_OR_OFFSET_SCRIPT
RAPID_APPEND_TEXT

Public Instance Methods

==(other) click to toggle source
# File lib/capybara/selenium/node.rb, line 202
def ==(other)
  native == other.native
end
[](name) click to toggle source
# File lib/capybara/selenium/node.rb, line 25
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 16
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
checked?()
Alias for: selected?
click(keys = [], **options) click to toggle source
# File lib/capybara/selenium/node.rb, line 103
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)
      if w3c?
        action.pause(action.pointer_inputs.first, click_options.delay)
      else
        action.pause(click_options.delay)
      end
      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 198
def content_editable?
  native.attribute('isContentEditable') == 'true'
end
disabled?() click to toggle source
# File lib/capybara/selenium/node.rb, line 191
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 147
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 164
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 177
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 160
def hover
  scroll_if_needed { browser_action.move_to(native).perform }
end
multiple?() click to toggle source
# File lib/capybara/selenium/node.rb, line 187
def multiple?; boolean_attr(self[:multiple]); end
obscured?(x: nil, y: nil) click to toggle source
# File lib/capybara/selenium/node.rb, line 210
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 206
def path
  driver.evaluate_script GET_XPATH_SCRIPT, self
end
readonly?() click to toggle source
# File lib/capybara/selenium/node.rb, line 186
def readonly?; boolean_attr(self[:readonly]); end
rect() click to toggle source
# File lib/capybara/selenium/node.rb, line 217
def rect
  native.rect
end
right_click(keys = [], **options) click to toggle source
# File lib/capybara/selenium/node.rb, line 130
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)
            .pause(action.pointer_inputs.first, click_options.delay)
            .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 93
def select_option
  click unless selected? || disabled?
end
selected?() click to toggle source
# File lib/capybara/selenium/node.rb, line 188
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 156
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]
# File lib/capybara/selenium/node.rb, line 56
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
style(styles) click to toggle source
# File lib/capybara/selenium/node.rb, line 39
def style(styles)
  styles.each_with_object({}) do |style, result|
    result[style] = native.css_value(style)
  end
end
tag_name() click to toggle source
# File lib/capybara/selenium/node.rb, line 181
def tag_name
  @tag_name ||= native.tag_name.downcase
end
unselect_option() click to toggle source
# File lib/capybara/selenium/node.rb, line 97
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 31
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 185
def visible?; boolean_attr(native.displayed?); end
visible_text() click to toggle source
# File lib/capybara/selenium/node.rb, line 12
def visible_text
  native.text
end

Protected Instance Methods

scroll_if_needed() { || ... } click to toggle source
# File lib/capybara/selenium/node.rb, line 223
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 230
  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_with_modifiers(click_options) { |actions| ... } click to toggle source
# File lib/capybara/selenium/node.rb, line 408
def action_with_modifiers(click_options)
  actions = browser_action.tap do |acts|
    if click_options.center_offset? && click_options.coords?
      acts.move_to(native).move_by(*click_options.coords)
    else
      acts.move_to(native, *click_options.coords)
    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 479
  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 292
def auto_rapid_set_length
  30
end
boolean_attr(val) click to toggle source
# File lib/capybara/selenium/node.rb, line 261
def boolean_attr(val)
  val && (val != 'false')
end
bridge() click to toggle source
# File lib/capybara/selenium/node.rb, line 439
def bridge
  browser.send(:bridge)
end
browser() click to toggle source
# File lib/capybara/selenium/node.rb, line 435
def browser
  driver.browser
end
browser_action() click to toggle source
# File lib/capybara/selenium/node.rb, line 443
def browser_action
  browser.action
end
build_node(native_node, initial_cache = {}) click to toggle source
# File lib/capybara/selenium/node.rb, line 475
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 447
def capabilities
  browser.capabilities
end
each_key(keys, &block) click to toggle source
# File lib/capybara/selenium/node.rb, line 467
def each_key(keys, &block)
  normalize_keys(keys).each(&block)
end
find_context() click to toggle source
# File lib/capybara/selenium/node.rb, line 471
def find_context
  native
end
modifiers_down(actions, keys) click to toggle source
# File lib/capybara/selenium/node.rb, line 425
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 430
def modifiers_up(actions, keys)
  each_key(keys) { |key| actions.key_up(key) }
  actions
end
normalize_keys(keys) click to toggle source
# File lib/capybara/selenium/node.rb, line 456
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 296
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_given?
        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 266
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 334
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 384
  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 310
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 326
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 356
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 338
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 270
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..-1])
    else
      send_keys(value)
    end
  end
end
set_time(value) click to toggle source
# File lib/capybara/selenium/node.rb, line 318
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) click to toggle source
# File lib/capybara/selenium/node.rb, line 247
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 342
  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 451
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 366
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