Class: Capybara::Node::Element

Inherits:
Base
  • Object
show all
Defined in:
lib/capybara/node/element.rb

Overview

A Element represents a single element on the page. It is possible to interact with the contents of this element the same as with a document:

session = Capybara::Session.new(:rack_test, my_app)

bar = session.find('#bar')              # from Capybara::Node::Finders
bar.select('Baz', from: 'Quox')      # from Capybara::Node::Actions

Element also has access to HTML attributes and other properties of the element:

 bar.value
 bar.text
 bar[:title]

See Also:

Constant Summary collapse

STYLE_SCRIPT =
<<~JS
  (function(){
    var s = window.getComputedStyle(this);
    var result = {};
    for (var i = arguments.length; i--; ) {
      var property_name = arguments[i];
      result[property_name] = s.getPropertyValue(property_name);
    }
    return result;
  }).apply(this, arguments)
JS

Instance Attribute Summary

Attributes inherited from Base

#base, #query_scope, #session

Instance Method Summary collapse

Methods inherited from Base

#find_css, #find_xpath, #session_options, #synchronize, #to_capybara_node

Methods included from Minitest::Expectations

#must_have_style

Methods included from Matchers

#==, #assert_all_of_selectors, #assert_ancestor, #assert_any_of_selectors, #assert_matches_selector, #assert_matches_style, #assert_no_ancestor, #assert_no_selector, #assert_no_sibling, #assert_no_text, #assert_none_of_selectors, #assert_not_matches_selector, #assert_selector, #assert_sibling, #assert_style, #assert_text, #has_ancestor?, #has_button?, #has_checked_field?, #has_css?, #has_field?, #has_link?, #has_no_ancestor?, #has_no_button?, #has_no_checked_field?, #has_no_css?, #has_no_field?, #has_no_link?, #has_no_select?, #has_no_selector?, #has_no_sibling?, #has_no_table?, #has_no_text?, #has_no_unchecked_field?, #has_no_xpath?, #has_select?, #has_selector?, #has_sibling?, #has_style?, #has_table?, #has_text?, #has_unchecked_field?, #has_xpath?, #matches_css?, #matches_selector?, #matches_style?, #matches_xpath?, #not_matches_css?, #not_matches_selector?, #not_matches_xpath?

Methods included from Actions

#attach_file, #check, #choose, #click_button, #click_link, #click_link_or_button, #fill_in, #select, #uncheck, #unselect

Methods included from Finders

#all, #ancestor, #find, #find_button, #find_by_id, #find_field, #find_link, #first, #sibling

Constructor Details

#initialize(session, base, query_scope, query) ⇒ Element

Returns a new instance of Element.



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

def initialize(session, base, query_scope, query)
  super(session, base)
  @query_scope = query_scope
  @query = query
  @allow_reload = false
end

Instance Method Details

#[](attribute) ⇒ String

Retrieve the given attribute.

element[:title] # => HTML title attribute

Parameters:

  • attribute (Symbol)

    The attribute to retrieve

Returns:

  • (String)

    The value of the attribute



71
72
73
# File 'lib/capybara/node/element.rb', line 71

def [](attribute)
  synchronize { base[attribute] }
end

#allow_reload!Object



32
33
34
# File 'lib/capybara/node/element.rb', line 32

def allow_reload!
  @allow_reload = true
end

#checked?Boolean

Whether or not the element is checked.

Returns:

  • (Boolean)

    Whether the element is checked



324
325
326
# File 'lib/capybara/node/element.rb', line 324

def checked?
  synchronize { base.checked? }
end

#click(*modifier_keys, wait: nil, **offset) ⇒ Capybara::Node::Element

Click the Element.

If the driver dynamic pages (JS) and the element is currently non-interactable, this method will continuously retry the action until either the element becomes interactable or the maximum wait time expires.

Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element.

Parameters:

  • *modifier_keys (:alt, :control, :meta, :shift)

    ([]) Keys to be held down when clicking

Parameters:

  • wait (false, Numeric) (defaults to: nil)

    Maximum time to wait for the action to succeed. Defaults to default_max_wait_time.

Returns:

Raises:

  • (ArgumentError)


165
166
167
168
169
170
171
# File 'lib/capybara/node/element.rb', line 165

def click(*keys, wait: nil, **options)
  raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ options[:x] ^ options[:y]

  options[:offset] = :center if session_options.w3c_click_offset
  synchronize(wait) { base.click(Array(keys), options) }
  self
end

#disabled?Boolean

Whether or not the element is disabled.

Returns:

  • (Boolean)

    Whether the element is disabled



344
345
346
# File 'lib/capybara/node/element.rb', line 344

def disabled?
  synchronize { base.disabled? }
end

#double_click(*modifier_keys, wait: nil, **offset) ⇒ Capybara::Node::Element

Double Click the Element.

If the driver dynamic pages (JS) and the element is currently non-interactable, this method will continuously retry the action until either the element becomes interactable or the maximum wait time expires.

Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element.

Parameters:

  • *modifier_keys (:alt, :control, :meta, :shift)

    ([]) Keys to be held down when clicking

Parameters:

  • wait (false, Numeric) (defaults to: nil)

    Maximum time to wait for the action to succeed. Defaults to default_max_wait_time.

Returns:

Raises:

  • (ArgumentError)


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

def double_click(*keys, wait: nil, **offset)
  raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]

  synchronize(wait) { base.double_click(keys, offset) }
  self
end

#drag_to(node, **options) ⇒ Capybara::Node::Element

Drag the element to the given other element.

source = page.find('#foo')
target = page.find('#bar')
source.drag_to(target)

Parameters:

  • node (Capybara::Node::Element)

    The element to drag to

  • options (Hash)

    Driver specific options for dragging. May not be supported by all drivers.

Returns:



405
406
407
408
# File 'lib/capybara/node/element.rb', line 405

def drag_to(node, **options)
  synchronize { base.drag_to(node.base, **options) }
  self
end

#drop(path, ...) ⇒ Capybara::Node::Element #drop(strings, ...) ⇒ Capybara::Node::Element

Drop items on the current element.

target = page.find('#foo')
target.drop('/some/path/file.csv')

Overloads:

  • #drop(path, ...) ⇒ Capybara::Node::Element

    Parameters:

    • path (String, #to_path)

      Location of the file to drop on the element

  • #drop(strings, ...) ⇒ Capybara::Node::Element

    Parameters:

    • strings (Hash)

      A hash of type to data to be dropped - { "text/url" => "https://www.google.com" }

Returns:



424
425
426
427
428
429
430
431
432
# File 'lib/capybara/node/element.rb', line 424

def drop(*args)
  options = args.map do |arg|
    return arg.to_path if arg.respond_to?(:to_path)

    arg
  end
  synchronize { base.drop(*options) }
  self
end

#evaluate_async_script(script, *args) ⇒ Object

Evaluate the given JavaScript in the context of the element and obtain the result from a callback function which will be passed as the last argument to the script. this in the script will refer to the element this is called on.

Parameters:

  • script (String)

    A string of JavaScript to evaluate

Returns:

  • (Object)

    The result of the evaluated JavaScript (may be driver specific)



509
510
511
512
513
514
515
# File 'lib/capybara/node/element.rb', line 509

def evaluate_async_script(script, *args)
  session.evaluate_async_script(<<~JS, self, *args)
    (function (){
      #{script}
    }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
  JS
end

#evaluate_script(script, *args) ⇒ Object

Evaluate the given JS in the context of the element and return the result. Be careful when using this with scripts that return complex objects, such as jQuery statements. #execute_script might be a better alternative. this in the script will refer to the element this is called on.

Parameters:

  • script (String)

    A string of JavaScript to evaluate

Returns:

  • (Object)

    The result of the evaluated JavaScript (may be driver specific)



492
493
494
495
496
497
498
# File 'lib/capybara/node/element.rb', line 492

def evaluate_script(script, *args)
  session.evaluate_script(<<~JS, self, *args)
    (function(){
      return #{script.strip}
    }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
  JS
end

#execute_script(script, *args) ⇒ Object

Execute the given JS in the context of the element not returning a result. This is useful for scripts that return complex objects, such as jQuery statements. #execute_script should be used over #evaluate_script whenever a result is not expected or needed. this in the script will refer to the element this is called on.

Parameters:

  • script (String)

    A string of JavaScript to execute

  • args

    Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers



475
476
477
478
479
480
481
# File 'lib/capybara/node/element.rb', line 475

def execute_script(script, *args)
  session.execute_script(<<~JS, self, *args)
    (function (){
      #{script}
    }).apply(arguments[0], Array.prototype.slice.call(arguments,1));
  JS
end

#hoverCapybara::Node::Element

Hover on the Element.

Returns:



282
283
284
285
# File 'lib/capybara/node/element.rb', line 282

def hover
  synchronize { base.hover }
  self
end

#initial_cacheObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



544
545
546
# File 'lib/capybara/node/element.rb', line 544

def initial_cache
  base.respond_to?(:initial_cache) ? base.initial_cache : {}
end

#inspectString

A human-readable representation of the element.

Returns:

  • (String)

    A string representation



535
536
537
538
539
540
541
# File 'lib/capybara/node/element.rb', line 535

def inspect
  %(#<Capybara::Node::Element tag="#{base.tag_name}" path="#{base.path}">)
rescue NotSupportedByDriverError
  %(#<Capybara::Node::Element tag="#{base.tag_name}">)
rescue *session.driver.invalid_element_errors
  %(Obsolete #<Capybara::Node::Element>)
end

#multiple?Boolean

Whether or not the element supports multiple results.

Returns:

  • (Boolean)

    Whether the element supports multiple results.



364
365
366
# File 'lib/capybara/node/element.rb', line 364

def multiple?
  synchronize { base.multiple? }
end

#nativeObject

Returns The native element from the driver, this allows access to driver specific methods.

Returns:

  • (Object)

    The native element from the driver, this allows access to driver specific methods



40
41
42
# File 'lib/capybara/node/element.rb', line 40

def native
  synchronize { base.native }
end

#obscured?Boolean

Whether or not the element is currently in the viewport and it (or descendants) would be considered clickable at the elements center point.

Returns:

  • (Boolean)

    Whether the elements center is obscured.



314
315
316
# File 'lib/capybara/node/element.rb', line 314

def obscured?
  synchronize { base.obscured? }
end

#pathString

An XPath expression describing where on the page the element can be found.

Returns:

  • (String)

    An XPath expression



374
375
376
# File 'lib/capybara/node/element.rb', line 374

def path
  synchronize { base.path }
end

#readonly?Boolean

Whether or not the element is readonly.

Returns:

  • (Boolean)

    Whether the element is readonly



354
355
356
# File 'lib/capybara/node/element.rb', line 354

def readonly?
  synchronize { base.readonly? }
end

#reloadObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



518
519
520
521
522
523
524
525
526
527
528
# File 'lib/capybara/node/element.rb', line 518

def reload
  if @allow_reload
    begin
      reloaded = query_scope.reload.first(@query.name, @query.locator, @query.options)
      @base = reloaded.base if reloaded
    rescue StandardError => e
      raise e unless catch_error?(e)
    end
  end
  self
end

#right_click(*modifier_keys, wait: nil, **offset) ⇒ Capybara::Node::Element

Right Click the Element.

If the driver dynamic pages (JS) and the element is currently non-interactable, this method will continuously retry the action until either the element becomes interactable or the maximum wait time expires.

Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element.

Parameters:

  • *modifier_keys (:alt, :control, :meta, :shift)

    ([]) Keys to be held down when clicking

Parameters:

  • wait (false, Numeric) (defaults to: nil)

    Maximum time to wait for the action to succeed. Defaults to default_max_wait_time.

Returns:

Raises:

  • (ArgumentError)


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

def right_click(*keys, wait: nil, **offset)
  raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]

  synchronize(wait) { base.right_click(keys, offset) }
  self
end

#scroll_to(position, offset: [0,0]) ⇒ Capybara::Node::Element #scroll_to(element, align: :top) ⇒ Capybara::Node::Element #scroll_to(x, y) ⇒ Capybara::Node::Element

Scroll the page or element.

Overloads:

  • #scroll_to(position, offset: [0,0]) ⇒ Capybara::Node::Element

    Scroll the page or element to its top, bottom or middle.

    Parameters:

    • position (:top, :bottom, :center, :current)
    • offset ([Integer, Integer]) (defaults to: [0,0])
  • #scroll_to(element, align: :top) ⇒ Capybara::Node::Element

    Scroll the page or current element until the given element is aligned at the top, bottom, or center of it.

    Parameters:

    • element (Capybara::Node::Element)

      The element to be scrolled into view

    • align (:top, :bottom, :center) (defaults to: :top)

      Where to align the element being scrolled into view with relation to the current page/element if possible

  • #scroll_to(x, y) ⇒ Capybara::Node::Element

    Parameters:

    • x (Integer)

      Horizontal scroll offset

    • y (Integer)

      Vertical scroll offset

Returns:



453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/capybara/node/element.rb', line 453

def scroll_to(pos_or_el_or_x, y = nil, align: :top, offset: nil)
  case pos_or_el_or_x
  when Symbol
    synchronize { base.scroll_to(nil, pos_or_el_or_x) } unless pos_or_el_or_x == :current
  when Capybara::Node::Element
    synchronize { base.scroll_to(pos_or_el_or_x.base, align) }
  else
    synchronize { base.scroll_to(nil, nil, [pos_or_el_or_x, y]) }
  end
  synchronize { base.scroll_by(*offset) } unless offset.nil?
  self
end

#select_option(wait: nil) ⇒ Capybara::Node::Element

Select this node if it is an option element inside a select tag.

If the driver dynamic pages (JS) and the element is currently non-interactable, this method will continuously retry the action until either the element becomes interactable or the maximum wait time expires.

Parameters:

  • wait (false, Numeric) (defaults to: nil)

    Maximum time to wait for the action to succeed. Defaults to default_max_wait_time.

Returns:



135
136
137
138
# File 'lib/capybara/node/element.rb', line 135

def select_option(wait: nil)
  synchronize(wait) { base.select_option }
  self
end

#selected?Boolean

Whether or not the element is selected.

Returns:

  • (Boolean)

    Whether the element is selected



334
335
336
# File 'lib/capybara/node/element.rb', line 334

def selected?
  synchronize { base.selected? }
end

#send_keys(keys, ...) ⇒ Capybara::Node::Element

Send Keystrokes to the Element.

Examples:

element.send_keys "foo"                     #=> value: 'foo'
element.send_keys "tet", :left, "s"         #=> value: 'test'
element.send_keys [:control, 'a'], :space   #=> value: ' ' - assuming ctrl-a selects all contents

Symbols supported for keys:

  • :cancel
  • :help
  • :backspace
  • :tab
  • :clear
  • :return
  • :enter
  • :shift
  • :control
  • :alt
  • :pause
  • :escape
  • :space
  • :page_up
  • :page_down
  • :end
  • :home
  • :left
  • :up
  • :right
  • :down
  • :insert
  • :delete
  • :semicolon
  • :equals
  • :numpad0
  • :numpad1
  • :numpad2
  • :numpad3
  • :numpad4
  • :numpad5
  • :numpad6
  • :numpad7
  • :numpad8
  • :numpad9
  • :multiply - numeric keypad *
  • :add - numeric keypad +
  • :separator - numeric keypad 'separator' key ??
  • :subtract - numeric keypad -
  • :decimal - numeric keypad .
  • :divide - numeric keypad /
  • :f1
  • :f2
  • :f3
  • :f4
  • :f5
  • :f6
  • :f7
  • :f8
  • :f9
  • :f10
  • :f11
  • :f12
  • :meta
  • :command - alias of :meta

Parameters:

  • keys (String, Symbol, Array<String,Symbol>)

Returns:



272
273
274
275
# File 'lib/capybara/node/element.rb', line 272

def send_keys(*args)
  synchronize { base.send_keys(*args) }
  self
end

#set(value, **options) ⇒ Capybara::Node::Element

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. Take default values from default_set_options.

Returns:

Raises:



115
116
117
118
119
120
121
# File 'lib/capybara/node/element.rb', line 115

def set(value, **options)
  raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}" if ENV['CAPYBARA_THOROUGH'] && readonly?

  options = session_options.default_set_options.to_h.merge(options)
  synchronize { base.set(value, options) }
  self
end

#style(*styles) ⇒ Hash

Retrieve the given CSS styles.

element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles

Parameters:

  • styles (Array<String>)

    Names of the desired CSS properties

Returns:

  • (Hash)

    Hash of the CSS property names to computed values

Raises:

  • (ArgumentError)


84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/capybara/node/element.rb', line 84

def style(*styles)
  styles = styles.flatten.map(&:to_s)
  raise ArgumentError, 'You must specify at least one CSS style' if styles.empty?

  begin
    synchronize { base.style(styles) }
  rescue NotImplementedError => e
    begin
      evaluate_script(STYLE_SCRIPT, *styles)
    rescue Capybara::NotSupportedByDriverError
      raise e
    end
  end
end

#tag_nameString

Returns The tag name of the element.

Returns:

  • (String)

    The tag name of the element



291
292
293
294
# File 'lib/capybara/node/element.rb', line 291

def tag_name
  # Element type is immutable so cache it
  @tag_name ||= initial_cache[:tag_name] || synchronize { base.tag_name }
end

#text(type = nil, normalize_ws: false) ⇒ String

Retrieve the text of the element. If ignore_hidden_elements is true, which it is by default, then this will return only text which is visible. The exact semantics of this may differ between drivers, but generally any text within elements with display:none is ignored. This behaviour can be overridden by passing :all to this method.

Parameters:

  • type (:all, :visible) (defaults to: nil)

    Whether to return only visible or all text

Returns:

  • (String)

    The text of the element



56
57
58
59
60
# File 'lib/capybara/node/element.rb', line 56

def text(type = nil, normalize_ws: false)
  type ||= :all unless session_options.ignore_hidden_elements || session_options.visible_text_only
  txt = synchronize { type == :all ? base.all_text : base.visible_text }
  normalize_ws ? txt.gsub(/[[:space:]]+/, ' ').strip : txt
end

#trigger(event) ⇒ Capybara::Node::Element

Trigger any event on the current element, for example mouseover or focus events. Not supported with the Selenium driver, and SHOULDN'T BE USED IN TESTING unless you fully understand why you're using it, that it can allow actions a user could never perform, and that it may completely invalidate your test.

Parameters:

  • event (String)

    The name of the event to trigger

Returns:



388
389
390
391
# File 'lib/capybara/node/element.rb', line 388

def trigger(event)
  synchronize { base.trigger(event) }
  self
end

#unselect_option(wait: nil) ⇒ Capybara::Node::Element

Unselect this node if it is an option element inside a multiple select tag.

If the driver dynamic pages (JS) and the element is currently non-interactable, this method will continuously retry the action until either the element becomes interactable or the maximum wait time expires.

Parameters:

  • wait (false, Numeric) (defaults to: nil)

    Maximum time to wait for the action to succeed. Defaults to default_max_wait_time.

Returns:



146
147
148
149
# File 'lib/capybara/node/element.rb', line 146

def unselect_option(wait: nil)
  synchronize(wait) { base.unselect_option }
  self
end

#valueString

Returns The value of the form element.

Returns:

  • (String)

    The value of the form element



103
104
105
# File 'lib/capybara/node/element.rb', line 103

def value
  synchronize { base.value }
end

#visible?Boolean

Whether or not the element is visible. Not all drivers support CSS, so the result may be inaccurate.

Returns:

  • (Boolean)

    Whether the element is visible



303
304
305
# File 'lib/capybara/node/element.rb', line 303

def visible?
  synchronize { base.visible? }
end