Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:Teamcapybara Capybara Firefox Node

From Leeroopedia

Overview

Capybara::Selenium::FirefoxNode is a Firefox-specific Selenium node class that extends Capybara::Selenium::Node with workarounds and optimizations tailored to the geckodriver/Marionette browser automation interface. It addresses known Firefox quirks such as table row click failures, multi-file upload limitations in older versions, the :space symbol-to-string conversion for key events, native element display detection, and modifier key handling through action chains.

Source File

Property Value
File lib/capybara/selenium/nodes/firefox_node.rb
Lines 136
Language Ruby
Parent Class Capybara::Selenium::Node
Modules Included Html5Drag, FileInputClickEmulation

Class Definition

class Capybara::Selenium::FirefoxNode < Capybara::Selenium::Node
  include Html5Drag
  include FileInputClickEmulation
end

The class includes two extension modules:

  • Html5Drag -- provides HTML5 drag-and-drop support
  • FileInputClickEmulation -- emulates click behavior on file input elements

Public Methods

click(keys = [], **options)

Performs a click on the element, delegating to the parent implementation via super. If an ElementNotInteractableError is raised and the element is a <tr> (table row), the method works around a known geckodriver/Marionette issue (geckodriver#1228) by emitting a warning and clicking the first cell (th:first-child or td:first-child) instead.

def click(keys = [], **options)
  super
rescue ::Selenium::WebDriver::Error::ElementNotInteractableError
  if tag_name == 'tr'
    warn 'You are attempting to click a table row which has issues in geckodriver/marionette - ...'
    return find_css('th:first-child,td:first-child')[0].click(keys, **options)
  end
  raise
end

disabled?

Returns whether the element matches the CSS :disabled pseudo-class or is a child of a disabled <select> element. Uses JavaScript evaluation rather than the standard WebDriver property check to handle Firefox-specific behavior.

def disabled?
  driver.evaluate_script("arguments[0].matches(':disabled, select:disabled *')", self)
end

set_file(value)

Sets file input values with special handling for multiple file uploads. Clears any existing files on multi-file inputs before setting new values. For Firefox versions 62.0 and above, delegates to the parent implementation. For older versions, works around the lack of native multiple file upload support by uploading files one at a time via native.send_keys.

def set_file(value)
  driver.execute_script(<<~JS, self)
    if (arguments[0].multiple && arguments[0].files.length){
      arguments[0].value = null;
    }
  JS
  return super if browser_version >= 62.0
  # Workaround for older versions: upload one file at a time
  path_names = value.to_s.empty? ? [] : Array(value)
  ...
end

focused?

Returns whether the element is the currently active (focused) element in the document by comparing it against document.activeElement via JavaScript evaluation.

def focused?
  driver.evaluate_script('arguments[0] == document.activeElement', self)
end

send_keys(*args)

Sends keystrokes to the element with Firefox-specific handling. Maps the :space symbol to a literal space character (' ') to work around geckodriver#846. When arrays are present in the arguments (indicating modifier key combinations), clicks the element to ensure focus and builds an action chain via the private _send_keys method.

def send_keys(*args)
  return super(*args.map { |arg| arg == :space ? ' ' : arg }) if args.none?(Array)
  native.click unless focused?
  _send_keys(args).perform
end

drop(*args)

Delegates to html5_drop (from the Html5Drag module) to perform HTML5 drag-and-drop file operations.

hover

Moves the mouse over the element. For Firefox versions 65.0 and above, works around Capybara#2156 by performing a two-step move: first to offset (0,0) of the element, then to the element center, ensuring reliable hover behavior after scrolling.

def hover
  return super unless browser_version >= 65.0
  scroll_if_needed { browser_action.move_to(native, 0, 0).move_to(native).perform }
end

select_option

Selects an option element, optimized to perform a single JavaScript check for whether the option is already selected or disabled (matching :disabled, select:disabled *, or :checked). Only clicks if the option is neither selected nor disabled.

def select_option
  selected_or_disabled = driver.evaluate_script(<<~JS, self)
    arguments[0].matches(':disabled, select:disabled *, :checked')
  JS
  click unless selected_or_disabled
end

visible?

Checks element visibility. When native display detection is enabled (and not disabled via environment variable), uses the Selenium :is_element_displayed command directly through the bridge for better performance. Falls back to the parent implementation if the command is unknown, and disables native display detection for future calls in that case.

def visible?
  return super unless native_displayed?
  begin
    bridge.send(:execute, :is_element_displayed, id: native_id)
  rescue Selenium::WebDriver::Error::UnknownCommandError
    driver.options[:native_displayed] = false
    super
  end
end

Private Methods

native_displayed?

Returns true if native element display detection is enabled. Checks both the driver option :native_displayed and the DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS environment variable.

perform_with_options(click_options)

Overrides the parent method to scroll the element to the center of the viewport when click coordinates are specified, working around a Firefox/Marionette issue with clicking near viewport edges.

_send_keys(keys, actions, down_keys)

Recursively processes key arguments to build a Selenium action chain. Handles modifier keys (:control, :alt, :shift, :meta, :command and their left/right variants) by pressing them down and tracking them on a ModifierKeysStack. String arguments are uppercased when Shift is held on Firefox versions below 64.0 (workaround for Mozilla bug 1405370). Arrays are recursively processed, with modifier keys released in reverse order upon array completion.

upload(local_file)

Zips and uploads a local file to the remote Selenium server. Used for file input workarounds on older Firefox versions that lack native multi-file upload support.

browser_version

Returns the browser version as a float by reading from the driver's capabilities.

Key Design Decisions

  • Table row click workaround -- Rather than failing on <tr> clicks (a known geckodriver limitation), the class automatically retargets the click to the first cell with a deprecation-style warning.
  • Version-gated behavior -- Several methods branch based on browser_version, allowing the class to apply workarounds only for Firefox versions that need them (e.g., multi-file upload below 62.0, hover fix at 65.0+, shift-uppercase below 64.0).
  • Native display optimization -- The visible? method attempts a faster native display check before falling back to the standard approach, with graceful degradation if the command is unsupported.
  • Modifier key stack -- The _send_keys method uses a ModifierKeysStack to correctly track nested modifier key states across complex key sequences.

See Also

  • Capybara::Selenium::SafariNode -- Safari-specific node implementation
  • Capybara::Selenium::Node -- Parent class providing base Selenium node functionality
  • Capybara::Selenium::Extensions::Html5Drag -- HTML5 drag-and-drop extension
  • Capybara::Selenium::Extensions::FileInputClickEmulation -- File input click emulation extension

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment