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


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

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.


322
323
324
# File 'lib/capybara/node/element.rb', line 322

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.


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

def click(*keys, **options)
  perform_click_action(keys, options) do |k, opts|
    base.click(k, opts)
  end
end

#disabled?Boolean

Whether or not the element is disabled.


342
343
344
# File 'lib/capybara/node/element.rb', line 342

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.


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

def double_click(*keys, **options)
  perform_click_action(keys, options) do |k, opts|
    base.double_click(k, opts)
  end
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)

Options Hash (**options):

  • :delay (Numeric) — default: 0.05

    When using Chrome/Firefox with Selenium and HTML5 dragging this is the number of seconds between each stage of the drag.

  • :html5 (Boolean)

    When using Chrome/Firefox with Selenium enables to force the use of HTML5 (true) or legacy (false) dragging. If not specified the driver will attempt to detect the correct method to use.


412
413
414
415
# File 'lib/capybara/node/element.rb', line 412

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')

431
432
433
434
435
436
437
438
439
# File 'lib/capybara/node/element.rb', line 431

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.


516
517
518
519
520
521
522
# File 'lib/capybara/node/element.rb', line 516

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.


499
500
501
502
503
504
505
# File 'lib/capybara/node/element.rb', line 499

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.


482
483
484
485
486
487
488
# File 'lib/capybara/node/element.rb', line 482

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

#flashCapybara::Node::Element

Toggle the elements background color between white and black for a period of time.


529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/capybara/node/element.rb', line 529

def flash
  execute_script(<<~JS, 100)
    async function flash(el, delay){
      var old_bg = el.style.backgroundColor;
      var colors = ["black", "white"];
      for(var i=0; i<20; i++){
        el.style.backgroundColor = colors[i % colors.length];
        await new Promise(resolve => setTimeout(resolve, delay));
      }
      el.style.backgroundColor = old_bg;
    }
    flash(this, arguments[0]);
  JS

  self
end

#hoverCapybara::Node::Element

Hover on the Element.


280
281
282
283
# File 'lib/capybara/node/element.rb', line 280

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.


574
575
576
# File 'lib/capybara/node/element.rb', line 574

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

#inspectString

A human-readable representation of the element.


565
566
567
568
569
570
571
# File 'lib/capybara/node/element.rb', line 565

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.


362
363
364
# File 'lib/capybara/node/element.rb', line 362

def multiple?
  synchronize { base.multiple? }
end

#nativeObject


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.


312
313
314
# File 'lib/capybara/node/element.rb', line 312

def obscured?
  synchronize { base.obscured? }
end

#pathString

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


372
373
374
# File 'lib/capybara/node/element.rb', line 372

def path
  synchronize { base.path }
end

#readonly?Boolean

Whether or not the element is readonly.


352
353
354
# File 'lib/capybara/node/element.rb', line 352

def readonly?
  synchronize { base.readonly? }
end

#rectObject


376
377
378
# File 'lib/capybara/node/element.rb', line 376

def rect
  synchronize { base.rect }
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.


547
548
549
550
551
552
553
554
555
556
557
558
# File 'lib/capybara/node/element.rb', line 547

def reload
  if @allow_reload
    begin
      reloaded = @query.resolve_for(query_scope.reload)&.first

      @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.


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

def right_click(*keys, **options)
  perform_click_action(keys, options) do |k, opts|
    base.right_click(k, opts)
  end
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.

  • #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.


460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/capybara/node/element.rb', line 460

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.


137
138
139
140
# File 'lib/capybara/node/element.rb', line 137

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

#selected?Boolean

Whether or not the element is selected.


332
333
334
# File 'lib/capybara/node/element.rb', line 332

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

270
271
272
273
# File 'lib/capybara/node/element.rb', line 270

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.


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

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

  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

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


289
290
291
292
# File 'lib/capybara/node/element.rb', line 289

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.


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.


390
391
392
393
# File 'lib/capybara/node/element.rb', line 390

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.


148
149
150
151
# File 'lib/capybara/node/element.rb', line 148

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

#valueString


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.


301
302
303
# File 'lib/capybara/node/element.rb', line 301

def visible?
  synchronize { base.visible? }
end