Class: Capybara::Selenium::Node

Inherits:
Driver::Node show all
Includes:
Node::WhitespaceNormalizer, Find, Scroll
Defined in:
lib/capybara/selenium/node.rb,
lib/capybara/selenium/extensions/html5_drag.rb,
lib/capybara/selenium/extensions/modifier_keys_stack.rb,
lib/capybara/selenium/extensions/file_input_click_emulation.rb

Direct Known Subclasses

ChromeNode, EdgeNode, FirefoxNode, IENode, SafariNode

Defined Under Namespace

Modules: FileInputClickEmulation, Html5Drag Classes: ModifierKeysStack

Constant Summary

Constants included from Node::WhitespaceNormalizer

Node::WhitespaceNormalizer::BREAKING_SPACES, Node::WhitespaceNormalizer::EMPTY_LINES, Node::WhitespaceNormalizer::LEADING_SPACES, Node::WhitespaceNormalizer::LEFT_TO_RIGHT_MARK, Node::WhitespaceNormalizer::LINE_SEPERATOR, Node::WhitespaceNormalizer::NON_BREAKING_SPACE, Node::WhitespaceNormalizer::PARAGRAPH_SEPERATOR, Node::WhitespaceNormalizer::REMOVED_CHARACTERS, Node::WhitespaceNormalizer::RIGHT_TO_LEFT_MARK, Node::WhitespaceNormalizer::SQUEEZED_SPACES, Node::WhitespaceNormalizer::TRAILING_SPACES, Node::WhitespaceNormalizer::ZERO_WIDTH_SPACE

Instance Attribute Summary

Attributes inherited from Driver::Node

#driver, #initial_cache, #native

Instance Method Summary collapse

Methods included from Scroll

#scroll_by, #scroll_to

Methods included from Find

#find_css, #find_xpath

Methods included from Node::WhitespaceNormalizer

#normalize_spacing, #normalize_visible_spacing

Methods inherited from Driver::Node

#==, #initialize, #inspect, #scroll_by, #scroll_to, #trigger

Constructor Details

This class inherits a constructor from Capybara::Driver::Node

Instance Method Details

#[](name) ⇒ Object



25
26
27
28
29
# File 'lib/capybara/selenium/node.rb', line 25

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

#all_textObject



20
21
22
23
# File 'lib/capybara/selenium/node.rb', line 20

def all_text
  text = driver.evaluate_script('arguments[0].textContent', self) || ''
  normalize_spacing(text)
end

#click(keys = [], **options) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/capybara/selenium/node.rb', line 106

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?Boolean

Returns:

  • (Boolean)


202
203
204
# File 'lib/capybara/selenium/node.rb', line 202

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

#disabled?Boolean

Returns:

  • (Boolean)


195
196
197
198
199
200
# File 'lib/capybara/selenium/node.rb', line 195

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) ⇒ Object

Raises:

  • (ArgumentError)


146
147
148
149
150
151
152
153
# File 'lib/capybara/selenium/node.rb', line 146

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: []) ⇒ Object



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/capybara/selenium/node.rb', line 163

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(*_) ⇒ Object

Raises:

  • (NotImplementedError)


176
177
178
# File 'lib/capybara/selenium/node.rb', line 176

def drop(*_)
  raise NotImplementedError, 'Out of browser drop emulation is not implemented for the current browser'
end

#hoverObject



159
160
161
# File 'lib/capybara/selenium/node.rb', line 159

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

#multiple?Boolean

Returns:

  • (Boolean)


191
# File 'lib/capybara/selenium/node.rb', line 191

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

#obscured?(x: nil, y: nil) ⇒ Boolean

Returns:

  • (Boolean)


210
211
212
213
214
215
# 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

#pathObject



206
207
208
# File 'lib/capybara/selenium/node.rb', line 206

def path
  driver.evaluate_script GET_XPATH_SCRIPT, self
end

#readonly?Boolean

Returns:

  • (Boolean)


190
# File 'lib/capybara/selenium/node.rb', line 190

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

#rectObject



217
218
219
# File 'lib/capybara/selenium/node.rb', line 217

def rect
  native.rect
end

#right_click(keys = [], **options) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/capybara/selenium/node.rb', line 129

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_optionObject



96
97
98
# File 'lib/capybara/selenium/node.rb', line 96

def select_option
  click unless selected? || disabled?
end

#selected?Boolean Also known as: checked?

Returns:

  • (Boolean)


192
# File 'lib/capybara/selenium/node.rb', line 192

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

#send_keys(*args) ⇒ Object



155
156
157
# File 'lib/capybara/selenium/node.rb', line 155

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

#set(value, **options) ⇒ Object

Set the value of the form element to the given value.

Parameters:

  • value (String)

    The new value

  • options (Hash{})

    Driver specific options for how to set the value

Options Hash (**options):

  • :clear (Symbol, Array) — default: nil

    The method used to clear the previous value
    nil => clear via javascript
    :none => append the new value to the existing value
    :backspace => send backspace keystrokes to clear the field
    Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]

  • :rapid (Boolean) — default: nil

    Whether setting text inputs should use a faster "rapid" mode
    nil => Text inputs with length greater than 30 characters will be set using a faster driver script mode
    true => Rapid mode will be used regardless of input length
    false => Sends keys via conventional mode. This may be required to avoid losing key-presses if you have certain Javascript interactions on form inputs



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/capybara/selenium/node.rb', line 59

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_rootObject



221
222
223
224
225
226
# File 'lib/capybara/selenium/node.rb', line 221

def shadow_root
  raise '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) ⇒ Object



39
40
41
# File 'lib/capybara/selenium/node.rb', line 39

def style(styles)
  styles.to_h { |style| [style, native.css_value(style)] }
end

#tag_nameObject



180
181
182
183
184
185
186
187
# File 'lib/capybara/selenium/node.rb', line 180

def tag_name
  @tag_name ||=
    if native.respond_to? :tag_name
      native.tag_name.downcase
    else
      shadow_root? ? 'ShadowRoot' : 'Unknown'
    end
end

#unselect_optionObject



100
101
102
103
104
# 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

#valueObject



31
32
33
34
35
36
37
# 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?Boolean

Returns:

  • (Boolean)


189
# File 'lib/capybara/selenium/node.rb', line 189

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

#visible_textObject

Raises:

  • (NotImplementedError)


14
15
16
17
18
# 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