class Capybara::Selenium::Node

def ==(other)

def ==(other)
  native == other.native
end

def [](name)

def [](name)
  native.attribute(name.to_s)
rescue Selenium::WebDriver::Error::WebDriverError
  nil
end

def action_with_modifiers(click_options)

def action_with_modifiers(click_options)
  actions = browser_action.move_to(native, *click_options.coords)
  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

def all_text

def all_text
  text = driver.execute_script('return 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

def boolean_attr(val)

def boolean_attr(val)
  val && (val != 'false')
end

def browser_action

def browser_action
  driver.browser.action
end

def click(keys = [], **options)

def click(keys = [], **options)
  click_options = ClickOptions.new(keys, options)
  return native.click if click_options.empty?
  click_with_options(click_options)
rescue StandardError => err
  if err.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
     err.message =~ /Other element would receive the click/
    scroll_to_center
  end
  raise err
end

def click_with_options(click_options)

def click_with_options(click_options)
  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

def content_editable?

def content_editable?
  native.attribute('isContentEditable')
end

def disabled?

def disabled?
  return true unless native.enabled?
  # WebDriver only defines `disabled?` for form controls but fieldset makes sense too
  tag_name == 'fieldset' && find_xpath('ancestor-or-self::fieldset[@disabled]').any?
end

def double_click(keys = [], **options)

def double_click(keys = [], **options)
  click_options = ClickOptions.new(keys, options)
  click_with_options(click_options) do |action|
    click_options.coords? ? action.double_click : action.double_click(native)
  end
end

def drag_to(element)

def drag_to(element)
  # 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 }
end

def each_key(keys)

def each_key(keys)
  keys.each do |key|
    key = case key
    when :ctrl then :control
    when :command, :cmd then :meta
    else
      key
    end
    yield key
  end
end

def find_css(locator)

def find_css(locator)
  native.find_elements(:css, locator).map { |el| self.class.new(driver, el) }
end

def find_xpath(locator)

def find_xpath(locator)
  native.find_elements(:xpath, locator).map { |el| self.class.new(driver, el) }
end

def hover

def hover
  scroll_if_needed { browser_action.move_to(native).perform }
end

def modifiers_down(actions, keys)

def modifiers_down(actions, keys)
  each_key(keys) { |key| actions.key_down(key) }
end

def modifiers_up(actions, keys)

def modifiers_up(actions, keys)
  each_key(keys) { |key| actions.key_up(key) }
end

def multiple?; boolean_attr(self[:multiple]); end

def multiple?; boolean_attr(self[:multiple]); end

def path

def path
  path = find_xpath(XPath.ancestor_or_self).reverse
  result = []
  default_ns = path.last[:namespaceURI]
  while (node = path.shift)
    parent = path.first
    selector = node[:tagName]
    if node[:namespaceURI] != default_ns
      selector = XPath.child.where((XPath.local_name == selector) & (XPath.namespace_uri == node[:namespaceURI])).to_s
      selector
    end
    if parent
      siblings = parent.find_xpath(selector)
      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
        "[#{siblings.index(node) + 1}]"
      end
    end
    result.push selector
  end
  '/' + result.reverse.join('/')
end

def readonly?; boolean_attr(self[:readonly]); end

def readonly?; boolean_attr(self[:readonly]); end

def right_click(keys = [], **options)

def right_click(keys = [], **options)
  click_options = ClickOptions.new(keys, options)
  click_with_options(click_options) do |action|
    click_options.coords? ? action.context_click : action.context_click(native)
  end
end

def scroll_if_needed

def scroll_if_needed
  yield
rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
  scroll_to_center
  yield
end

def scroll_to_center

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 # rubocop:disable Lint/HandleExceptions
    # Swallow error if scrollIntoView with options isn't supported
  end
end

def select_node

a reference to the select node if this is an option node
def select_node
  find_xpath(XPath.ancestor(:select)[1]).first
end

def select_option

def select_option
  click unless selected? || disabled?
end

def selected?; boolean_attr(native.selected?); end

def selected?; boolean_attr(native.selected?); end

def send_keys(*args)

def send_keys(*args)
  native.send_keys(*args)
end

def set(value, **options)

Options Hash: (**options)
  • :clear (Symbol, Array) -- The method used to clear the previous value

Parameters:
  • options (Hash{}) -- Driver specific options for how to set the value
  • value (String) -- The new value
def set(value, **options)
  raise ArgumentError, "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}" if value.is_a?(Array) && !multiple?
  case tag_name
  when 'input'
    case self[: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)
    else
      set_text(value, options)
    end
  when 'textarea'
    set_text(value, options)
  else
    set_content_editable(value) if content_editable?
  end
end

def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName

rubocop:disable Naming/AccessorMethodName
def set_content_editable(value) # rubocop:disable Naming/AccessorMethodName
  # Ensure we are focused on the element
  click
  script = <<-JS
    var range = document.createRange();
    var sel = window.getSelection();
    arguments[0].focus();
    range.selectNodeContents(arguments[0]);
    sel.removeAllRanges();
    sel.addRange(range);
  JS
  driver.execute_script script, self
  # 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
end

def set_date(value) # rubocop:disable Naming/AccessorMethodName

rubocop:disable Naming/AccessorMethodName
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

def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName

rubocop:disable Naming/AccessorMethodName
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

def set_file(value) # rubocop:disable Naming/AccessorMethodName

rubocop:disable Naming/AccessorMethodName
def set_file(value) # rubocop:disable Naming/AccessorMethodName
  path_names = value.to_s.empty? ? [] : value
  native.send_keys(Array(path_names).join("\n"))
end

def set_text(value, clear: nil, **_unused)

def set_text(value, clear: 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
    # Clear field by JavaScript assignment of the value property.
    # Script can change a readonly element which user input cannot, so
    # don't execute if readonly.
    driver.execute_script "arguments[0].value = ''", self unless clear == :none
    send_keys(value)
  end
end

def set_time(value) # rubocop:disable Naming/AccessorMethodName

rubocop:disable Naming/AccessorMethodName
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

def style(styles)

def style(styles)
  styles.each_with_object({}) do |style, result|
    result[style] = native.css_value(style)
  end
end

def tag_name

def tag_name
  native.tag_name.downcase
end

def unselect_option

def unselect_option
  raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
  click if selected?
end

def update_value_js(value)

def update_value_js(value)
  driver.execute_script(<<-JS, self, value)
    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

def value

def value
  if tag_name == 'select' && multiple?
    native.find_elements(:css, 'option:checked').map { |el| el[:value] || el.text }
  else
    native[:value]
  end
end

def visible?; boolean_attr(native.displayed?); end

def visible?; boolean_attr(native.displayed?); end

def visible_text

def visible_text
  native.text
end