Class: Capybara::Apparition::Node

Inherits:
Driver::Node
  • Object
show all
Extended by:
Forwardable
Includes:
Drag
Defined in:
lib/capybara/apparition/node.rb

Constant Summary collapse

EVENTS =
{
  blur: ['FocusEvent'],
  focus: ['FocusEvent'],
  focusin: ['FocusEvent', { bubbles: true  }],
  focusout: ['FocusEvent', { bubbles: true }],
  click: ['MouseEvent', { bubbles: true, cancelable: true }],
  dblckick: ['MouseEvent', { bubbles: true, cancelable: true }],
  mousedown: ['MouseEvent', { bubbles: true, cancelable: true }],
  mouseup: ['MouseEvent', { bubbles: true, cancelable: true }],
  mouseenter: ['MouseEvent'],
  mouseleave: ['MouseEvent'],
  mousemove: ['MouseEvent', { bubbles: true, cancelable: true }],
  mouseover: ['MouseEvent', { bubbles: true, cancelable: true }],
  mouseout: ['MouseEvent', { bubbles: true, cancelable: true }],
  context_menu: ['MouseEvent', { bubble: true, cancelable: true }],
  submit: ['Event', { bubbles: true, cancelable: true }],
  change: ['Event', { bubbles: true, cacnelable: false }],
  input: ['InputEvent', { bubbles: true, cacnelable: false }],
  wheel: ['WheelEvent', { bubbles: true, cancelable: true }]
}.freeze

Constants included from Drag

Drag::ATTACH_FILE, Drag::DROP_FILE, Drag::DROP_STRING, Drag::HTML5_DRAG_DROP_SCRIPT, Drag::LEGACY_DRAG_CHECK, Drag::MOUSEDOWN_TRACKER

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Drag

#drag_by, #drag_to, #drop

Constructor Details

#initialize(driver, page, remote_object, initial_cache) ⇒ Node

Returns a new instance of Node.



14
15
16
17
18
# File 'lib/capybara/apparition/node.rb', line 14

def initialize(driver, page, remote_object, initial_cache)
  super(driver, self, initial_cache)
  @page = page
  @remote_object = remote_object
end

Instance Attribute Details

#page_idObject (readonly)

Returns the value of attribute page_id.



12
13
14
# File 'lib/capybara/apparition/node.rb', line 12

def page_id
  @page_id
end

Instance Method Details

#==(other) ⇒ Object



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

def ==(other)
  evaluate_on('el => this == el', objectId: other.id)
rescue ObsoleteNode
  false
end

#[](name) ⇒ Object



99
100
101
102
103
# File 'lib/capybara/apparition/node.rb', line 99

def [](name)
  # Although the attribute matters, the property is consistent. Return that in
  # preference to the attribute for links and images.
  evaluate_on ELEMENT_PROP_OR_ATTR_JS, value: name
end

#all_textObject



50
51
52
53
54
55
56
57
# File 'lib/capybara/apparition/node.rb', line 50

def all_text
  text = evaluate_on('() => this.textContent')
  text.to_s.gsub(/[\u200b\u200e\u200f]/, '')
      .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
      .gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
      .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
      .tr("\u00a0", ' ')
end

#attribute(name) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/capybara/apparition/node.rb', line 91

def attribute(name)
  if %w[checked selected].include?(name.to_s)
    property(name)
  else
    evaluate_on('name => this.getAttribute(name)', value: name)
  end
end

#attributesObject



105
106
107
# File 'lib/capybara/apparition/node.rb', line 105

def attributes
  evaluate_on GET_ATTRIBUTES_JS
end

#checked?Boolean

Returns:

  • (Boolean)


188
189
190
# File 'lib/capybara/apparition/node.rb', line 188

def checked?
  self[:checked]
end

#click(keys = [], button: 'left', count: 1, **options) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/capybara/apparition/node.rb', line 200

def click(keys = [], button: 'left', count: 1, **options)
  pos = element_click_pos(**options)
  raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['click']) if pos.nil?

  test = mouse_event_test(**pos)
  raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['click']) if test.nil?

  unless options[:x] && options[:y]
    raise ::Capybara::Apparition::MouseEventFailed.new(self, 'args' => ['click', test.selector, pos]) unless test.success
  end

  @page.mouse.click_at(**pos.merge(button: button, count: count, modifiers: keys))
  if ENV['DEBUG']
    begin
      new_pos = element_click_pos(**options)
      puts "Element moved from #{pos} to #{new_pos}" unless pos == new_pos
    rescue WrongWorld # rubocop:disable Lint/SuppressedException
    end
  end
  # Wait a short time to see if click triggers page load
  sleep 0.05
  @page.wait_for_loaded(allow_obsolete: true)
end

#disabled?Boolean

Returns:

  • (Boolean)


196
197
198
# File 'lib/capybara/apparition/node.rb', line 196

def disabled?
  evaluate_on ELEMENT_DISABLED_JS
end

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



228
229
230
# File 'lib/capybara/apparition/node.rb', line 228

def double_click(keys = [], **options)
  click(keys, count: 2, **options)
end

#element_click_pos(x: nil, y: nil, offset: nil) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/capybara/apparition/node.rb', line 288

def element_click_pos(x: nil, y: nil, offset: nil, **)
  if x && y
    if offset == :center
      visible_center
    else
      visible_top_left
    end.tap do |p|
      p[:x] += x
      p[:y] += y
    end
  else
    visible_center
  end
end

#find(method, selector) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/capybara/apparition/node.rb', line 30

def find(method, selector)
  js = method == :css ? FIND_CSS_JS : FIND_XPATH_JS
  evaluate_on(js, value: selector).map do |r_o|
    tag_name = r_o['description'].split(/[\.#]/, 2)[0]
    Capybara::Apparition::Node.new(driver, @page, r_o['objectId'], tag_name: tag_name)
  end
rescue ::Capybara::Apparition::BrowserError => e
  raise unless /is not a valid (XPath expression|selector)/.match? e.name

  raise Capybara::Apparition::InvalidSelector, 'args' => [method, selector]
end

#find_css(selector) ⇒ Object



46
47
48
# File 'lib/capybara/apparition/node.rb', line 46

def find_css(selector)
  find :css, selector
end

#find_xpath(selector) ⇒ Object



42
43
44
# File 'lib/capybara/apparition/node.rb', line 42

def find_xpath(selector)
  find :xpath, selector
end

#hoverObject



232
233
234
235
236
237
# File 'lib/capybara/apparition/node.rb', line 232

def hover
  pos = visible_center
  raise ::Capybara::Apparition::MouseEventImpossible.new(self, 'args' => ['hover']) if pos.nil?

  @page.mouse.move_to(**pos)
end

#idObject



22
23
24
# File 'lib/capybara/apparition/node.rb', line 22

def id
  @remote_object
end

#inner_htmlObject

capybara-webkit method



76
77
78
# File 'lib/capybara/apparition/node.rb', line 76

def inner_html
  self[:innerHTML]
end

#inner_html=(value) ⇒ Object

capybara-webkit method



81
82
83
84
85
# File 'lib/capybara/apparition/node.rb', line 81

def inner_html=(value)
  driver.execute_script <<~JS, self, value
    arguments[0].innerHTML = arguments[1]
  JS
end

#obscured?Boolean

Returns:

  • (Boolean)


173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/capybara/apparition/node.rb', line 173

def obscured?(**)
  pos = visible_center(allow_scroll: false)
  return true if pos.nil?

  hit_node = @page.element_from_point(**pos)
  return true if hit_node.nil?

  begin
    return evaluate_on('el => !this.contains(el)', objectId: hit_node['objectId'])
  rescue WrongWorld # rubocop:disable Lint/SuppressedException
  end

  true
end

#parentsObject



26
27
28
# File 'lib/capybara/apparition/node.rb', line 26

def parents
  find('xpath', 'ancestor::*').reverse
end

#pathObject



284
285
286
# File 'lib/capybara/apparition/node.rb', line 284

def path
  evaluate_on GET_PATH_JS
end

#property(name) ⇒ Object



87
88
89
# File 'lib/capybara/apparition/node.rb', line 87

def property(name)
  evaluate_on('name => this[name]', value: name)
end

#rectObject



387
388
389
# File 'lib/capybara/apparition/node.rb', line 387

def rect
  evaluate_on GET_CLIENT_RECT_JS
end

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



224
225
226
# File 'lib/capybara/apparition/node.rb', line 224

def right_click(keys = [], **options)
  click(keys, button: 'right', **options)
end

#scroll_by(x, y) ⇒ Object



391
392
393
394
395
396
# File 'lib/capybara/apparition/node.rb', line 391

def scroll_by(x, y)
  evaluate_on <<~JS, { value: x }, value: y
    (x, y) => this.scrollBy(x,y)
  JS
  self
end

#scroll_to(element, location, position = nil) ⇒ Object



398
399
400
401
402
403
404
405
406
407
# File 'lib/capybara/apparition/node.rb', line 398

def scroll_to(element, location, position = nil)
  if element.is_a? Capybara::Apparition::Node
    scroll_element_to_location(element, location)
  elsif location.is_a? Symbol
    scroll_to_location(location)
  else
    scroll_to_coords(*position)
  end
  self
end

#select_optionObject



151
152
153
154
155
156
# File 'lib/capybara/apparition/node.rb', line 151

def select_option
  return false if disabled?

  evaluate_on SELECT_OPTION_JS
  true
end

#selected?Boolean

Returns:

  • (Boolean)


192
193
194
# File 'lib/capybara/apparition/node.rb', line 192

def selected?
  !!self[:selected]
end

#send_keys(*keys, delay: 0, **opts) ⇒ Object Also known as: send_key



278
279
280
281
# File 'lib/capybara/apparition/node.rb', line 278

def send_keys(*keys, delay: 0, **opts)
  click unless evaluate_on CURRENT_NODE_SELECTED_JS
  _send_keys(*keys, delay: delay, **opts)
end

#set(value, **options) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/capybara/apparition/node.rb', line 117

def set(value, **options)
  if tag_name == 'input'
    case self[:type]
    when 'radio'
      click
    when 'checkbox'
      click if value != checked?
    when 'file'
      files = value.respond_to?(:to_ary) ? value.to_ary.map(&:to_s) : value.to_s
      set_files(files)
    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.to_s, **{ delay: 0 }.merge(options))
    end
  elsif tag_name == 'textarea'
    set_text(value.to_s)
  elsif tag_name == 'select'
    warn "Setting the value of a select element via 'set' is deprecated, please use 'select' or 'select_option'."
    evaluate_on '()=>{ this.value = arguments[0] }', value: value.to_s
  elsif self[:isContentEditable]
    delete_text
    send_keys(value.to_s, delay: options.fetch(:delay, 0))
  end
end

#style(styles) ⇒ Object



113
114
115
# File 'lib/capybara/apparition/node.rb', line 113

def style(styles)
  evaluate_on GET_STYLES_JS, value: styles
end

#submitObject



268
269
270
# File 'lib/capybara/apparition/node.rb', line 268

def submit
  evaluate_on '()=>{ this.submit() }'
end

#tag_nameObject



165
166
167
# File 'lib/capybara/apparition/node.rb', line 165

def tag_name
  @tag_name ||= evaluate_on('() => this.tagName').downcase
end

#textObject

capybara-webkit method



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

def text
  warn 'Node#text is deprecated, please use Node#visible_text instead'
  visible_text
end

#top_leftObject



380
381
382
383
384
385
# File 'lib/capybara/apparition/node.rb', line 380

def top_left
  result = evaluate_on GET_CLIENT_RECT_JS
  return nil if result.nil?

  { x: result['x'], y: result['y'] }
end

#trigger(name, event_type = nil, **options) ⇒ Object

Raises:

  • (ArgumentError)


260
261
262
263
264
265
266
# File 'lib/capybara/apparition/node.rb', line 260

def trigger(name, event_type = nil, **options)
  raise ArgumentError, 'Unknown event' unless EVENTS.key?(name.to_sym) || event_type

  event_type, opts = *EVENTS[name.to_sym], {} if event_type.nil?

  evaluate_on DISPATCH_EVENT_JS, { value: event_type }, { value: name }, value: opts.merge(options)
end

#unselect_optionObject



158
159
160
161
162
163
# File 'lib/capybara/apparition/node.rb', line 158

def unselect_option
  return false if disabled?

  evaluate_on(UNSELECT_OPTION_JS) ||
    raise(Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.')
end

#valueObject



109
110
111
# File 'lib/capybara/apparition/node.rb', line 109

def value
  evaluate_on GET_VALUE_JS
end

#visible?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/capybara/apparition/node.rb', line 169

def visible?
  evaluate_on VISIBLE_JS
end

#visible_center(allow_scroll: true) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/capybara/apparition/node.rb', line 337

def visible_center(allow_scroll: true)
  rect = in_view_client_rect(allow_scroll: allow_scroll)
  return nil if rect.nil?

  frame_offset = @page.current_frame_offset

  if rect['width'].zero? || rect['height'].zero?
    return nil unless tag_name == 'area'

    map = find('xpath', 'ancestor::map').first
    img = find('xpath', "//img[@usemap='##{map[:name]}']").first
    return nil unless img.visible?

    img_pos = img.top_left
    coords = self[:coords].split(',').map(&:to_i)

    offset_pos = case self[:shape]
    when 'rect'
      { x: (coords[0] + coords[2]) / 2,
        y: (coords[1] + coords[2]) / 2 }
    when 'circle'
      { x: coords[0], y: coords[1] }
    when 'poly'
      raise 'TODO: Poly not implemented'
    else
      raise 'Unknown Shape'
    end

    { x: img_pos[:x] + offset_pos[:x] + frame_offset[:x],
      y: img_pos[:y] + offset_pos[:y] + frame_offset[:y] }
  else
    lm = @page.command('Page.getLayoutMetrics')
    x_extents = [rect['left'], rect['right']].minmax
    y_extents = [rect['top'], rect['bottom']].minmax

    x_extents[1] = [x_extents[1], lm['layoutViewport']['clientWidth']].min
    y_extents[1] = [y_extents[1], lm['layoutViewport']['clientHeight']].min

    { x: (x_extents.sum / 2) + frame_offset[:x],
      y: (y_extents.sum / 2) + frame_offset[:y] }
  end
end

#visible_textObject



59
60
61
62
63
64
65
66
67
# File 'lib/capybara/apparition/node.rb', line 59

def visible_text
  return '' unless visible?

  text = evaluate_on ELEMENT_VISIBLE_TEXT_JS
  text.to_s.gsub(/\A[[:space:]&&[^\u00a0]]+/, '')
      .gsub(/[[:space:]&&[^\u00a0]]+\z/, '')
      .gsub(/\n+/, "\n")
      .tr("\u00a0", ' ')
end

#visible_top_leftObject



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/capybara/apparition/node.rb', line 303

def visible_top_left
  rect = in_view_client_rect
  return nil if rect.nil?

  frame_offset = @page.current_frame_offset

  if rect['width'].zero? || rect['height'].zero?
    return nil unless tag_name == 'area'

    map = find('xpath', 'ancestor::map').first
    img = find('xpath', "//img[@usemap='##{map[:name]}']").first
    return nil unless img.visible?

    img_pos = img.top_left
    coords = self[:coords].split(',').map(&:to_i)

    offset_pos = case self[:shape]
    when 'rect'
      { x: coords[0], y: coords[1] }
    when 'circle'
      { x: coords[0], y: coords[1] }
    when 'poly'
      raise 'TODO: Poly not implemented'
    else
      raise 'Unknown Shape'
    end

    { x: img_pos[:x] + offset_pos[:x] + frame_offset[:x],
      y: img_pos[:y] + offset_pos[:y] + frame_offset[:y] }
  else
    { x: rect['left'] + frame_offset[:x], y: rect['top'] + frame_offset[:y] }
  end
end