Class: Puppeteer::ElementHandle

Inherits:
JSHandle
  • Object
show all
Includes:
IfPresent
Defined in:
lib/puppeteer/element_handle.rb

Defined Under Namespace

Classes: ElementNotFoundError, ElementNotVisibleError, Point, ScrollIntoViewError

Instance Attribute Summary

Attributes inherited from JSHandle

#context, #remote_object

Instance Method Summary collapse

Methods included from IfPresent

#if_present

Methods inherited from JSHandle

#async_evaluate, #async_evaluate_handle, create, #dispose, #disposed?, #evaluate, #evaluate_handle, #execution_context, #json_value, #properties

Constructor Details

#initialize(context:, client:, remote_object:, page:, frame_manager:) ⇒ ElementHandle

Returns a new instance of ElementHandle.

Parameters:



12
13
14
15
16
17
# File 'lib/puppeteer/element_handle.rb', line 12

def initialize(context:, client:, remote_object:, page:, frame_manager:)
  super(context: context, client: client, remote_object: remote_object)
  @page = page
  @frame_manager = frame_manager
  @disposed = false
end

Instance Method Details

#as_elementObject



19
20
21
# File 'lib/puppeteer/element_handle.rb', line 19

def as_element
  self
end

#async_pressFuture

Parameters:

  • key (String)
  • delay (number|nil)

Returns:

  • (Future)


264
265
266
# File 'lib/puppeteer/element_handle.rb', line 264

async def async_press(key, delay: nil)
  press(key, delay: delay)
end

#async_SevalObject

`$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • selector (String)
  • page_function (String)

Returns:

  • (Object)


405
406
407
# File 'lib/puppeteer/element_handle.rb', line 405

async def async_Seval(selector, page_function, *args)
  Seval(selector, page_function, *args)
end

#async_SSevalObject

`$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • selector (String)
  • page_function (String)

Returns:

  • (Object)


428
429
430
# File 'lib/puppeteer/element_handle.rb', line 428

async def async_SSeval(selector, page_function, *args)
  SSeval(selector, page_function, *args)
end

#async_type_textFuture

Parameters:

  • text (String)
  • delay (number|nil)

Returns:

  • (Future)


250
251
252
# File 'lib/puppeteer/element_handle.rb', line 250

async def async_type_text(text, delay: nil)
  type_text(text, delay: delay)
end

#click(delay: nil, button: nil, click_count: nil) ⇒ Object

Parameters:

  • delay (Number) (defaults to: nil)
  • button (String) (defaults to: nil)

    “left”|“right”|“middle”

  • click_count (Number) (defaults to: nil)


147
148
149
150
151
# File 'lib/puppeteer/element_handle.rb', line 147

def click(delay: nil, button: nil, click_count: nil)
  scroll_into_view_if_needed
  point = clickable_point
  @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
end

#clickable_pointObject



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/puppeteer/element_handle.rb', line 85

def clickable_point
  result = @remote_object.content_quads(@client)
  if !result || result["quads"].empty?
    raise ElementNotVisibleError.new
  end

  # Filter out quads that have too small area to click into.
  layout_metrics = @client.send_message('Page.getLayoutMetrics')
  client_width = layout_metrics["layoutViewport"]["clientWidth"]
  client_height = layout_metrics["layoutViewport"]["clientHeight"]

  quads = result["quads"].
            map { |quad| from_protocol_quad(quad) }.
            map { |quad| intersect_quad_with_viewport(quad, client_width, client_height) }.
            select { |quad| compute_quad_area(quad) > 1 }
  if quads.empty?
    raise ElementNotVisibleError.new
  end

  # Return the middle point of the first quad.
  quads.first.reduce(:+) / 4
end

#content_frameObject



23
24
25
26
27
28
29
30
31
# File 'lib/puppeteer/element_handle.rb', line 23

def content_frame
  node_info = @remote_object.node_info
  frame_id = node_info['node']['frameId']
  if frame_id.is_a?(String)
    @frame_manager.frame(frame_id)
  else
    nil
  end
end

#focusObject



232
233
234
# File 'lib/puppeteer/element_handle.rb', line 232

def focus
  evaluate('element => element.focus()')
end

#press(key, delay: nil) ⇒ Object

Parameters:

  • key (String)
  • delay (number|nil) (defaults to: nil)


256
257
258
259
# File 'lib/puppeteer/element_handle.rb', line 256

def press(key, delay: nil)
  focus
  @page.keyboard.press(key, delay: delay)
end

#S(selector) ⇒ Object

`$()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • selector (String)


354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/puppeteer/element_handle.rb', line 354

def S(selector)
  handle = evaluate_handle(
    '(element, selector) => element.querySelector(selector)',
    selector,
  )
  element = handle.as_element

  if element
    return element
  end
  handle.dispose
  nil
end

#scroll_into_view_if_neededObject



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/puppeteer/element_handle.rb', line 35

def scroll_into_view_if_needed
  js = <<~JAVASCRIPT
    async(element, pageJavascriptEnabled) => {
      if (!element.isConnected)
        return 'Node is detached from document';
      if (element.nodeType !== Node.ELEMENT_NODE)
        return 'Node is not of type HTMLElement';

      element.scrollIntoViewIfNeeded({block: 'center', inline: 'center', behavior: 'instant'});
      return false;
    }
  JAVASCRIPT
  error = evaluate(js, @page.javascript_enabled) # returns String or false
  if error
    raise ScrollIntoViewError.new(error)
  end
  # clickpoint is often calculated before scrolling is completed.
  # So, just sleep about 10 frames
  sleep 0.16
end

#select(*values) ⇒ Array<String>

Returns:

  • (Array<String>)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/puppeteer/element_handle.rb', line 161

def select(*values)
  if nonstring = values.find { |value| !value.is_a?(String) }
    raise ArgumentError.new("Values must be strings. Found value \"#{nonstring}\" of type \"#{nonstring.class}\"")
  end

  fn = <<~JAVASCRIPT
  (element, values) => {
    if (element.nodeName.toLowerCase() !== 'select') {
      throw new Error('Element is not a <select> element.');
    }

    const options = Array.from(element.options);
    element.value = undefined;
    for (const option of options) {
      option.selected = values.includes(option.value);
      if (option.selected && !element.multiple) {
        break;
      }
    }
    element.dispatchEvent(new Event('input', { bubbles: true }));
    element.dispatchEvent(new Event('change', { bubbles: true }));
    return options.filter(option => option.selected).map(option => option.value);
  }
  JAVASCRIPT
  evaluate(fn, values)
end

#Seval(selector, page_function, *args) ⇒ Object

`$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • selector (String)
  • page_function (String)

Returns:

  • (Object)


390
391
392
393
394
395
396
397
398
399
# File 'lib/puppeteer/element_handle.rb', line 390

def Seval(selector, page_function, *args)
  element_handle = S(selector)
  unless element_handle
    raise ElementNotFoundError.new(selector)
  end
  result = element_handle.evaluate(page_function, *args)
  element_handle.dispose

  result
end

#SS(selector) ⇒ Object

`$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • selector (String)


370
371
372
373
374
375
376
377
378
# File 'lib/puppeteer/element_handle.rb', line 370

def SS(selector)
  handles = evaluate_handle(
    '(element, selector) => element.querySelectorAll(selector)',
    selector,
  )
  properties = handles.properties
  handles.dispose
  properties.values.map(&:as_element).compact
end

#SSeval(selector, page_function, *args) ⇒ Object

`$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • selector (String)
  • page_function (String)

Returns:

  • (Object)


413
414
415
416
417
418
419
420
421
422
# File 'lib/puppeteer/element_handle.rb', line 413

def SSeval(selector, page_function, *args)
  handles = evaluate_handle(
    '(element, selector) => Array.from(element.querySelectorAll(selector))',
    selector,
  )
  result = handles.evaluate(page_function, *args)
  handles.dispose

  result
end

#Sx(expression) ⇒ Array<ElementHandle>

`$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.

Parameters:

  • expression (String)

Returns:



435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/puppeteer/element_handle.rb', line 435

def Sx(expression)
  fn = <<~JAVASCRIPT
  (element, expression) => {
    const document = element.ownerDocument || element;
    const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
    const array = [];
    let item;
    while ((item = iterator.iterateNext()))
      array.push(item);
    return array;
  }
  JAVASCRIPT
  handles = evaluate_handle(fn, expression)
  properties = handles.properties
  handles.dispose
  properties.values.map(&:as_element).compact
end

#tap(&block) ⇒ Object



220
221
222
223
224
225
226
# File 'lib/puppeteer/element_handle.rb', line 220

def tap(&block)
  return super(&block) if block

  scroll_into_view_if_needed
  point = clickable_point
  @page.touchscreen.tap(point.x, point.y)
end

#type_text(text, delay: nil) ⇒ Object

Parameters:

  • text (String)
  • delay (number|nil) (defaults to: nil)


242
243
244
245
# File 'lib/puppeteer/element_handle.rb', line 242

def type_text(text, delay: nil)
  focus
  @page.keyboard.type_text(text, delay: delay)
end

#upload_file(*file_paths) ⇒ Object

Parameters:

  • file_paths (Array<String>)


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/puppeteer/element_handle.rb', line 189

def upload_file(*file_paths)
  is_multiple = evaluate("el => el.multiple")
  if !is_multiple && file_paths.length >= 2
    raise ArgumentError.new('Multiple file uploads only work with <input type=file multiple>')
  end

  if error_path = file_paths.find { |file_path| !File.exist?(file_path) }
    raise ArgmentError.new("#{error_path} does not exist or is not readable")
  end

  backend_node_id = @remote_object.node_info(@client)["node"]["backendNodeId"]

  # The zero-length array is a special case, it seems that DOM.setFileInputFiles does
  # not actually update the files in that case, so the solution is to eval the element
  # value to a new FileList directly.
  if file_paths.empty?
    fn = <<~JAVASCRIPT
    (element) => {
      element.files = new DataTransfer().files;

      // Dispatch events for this case because it should behave akin to a user action.
      element.dispatchEvent(new Event('input', { bubbles: true }));
      element.dispatchEvent(new Event('change', { bubbles: true }));
    }
    JAVASCRIPT
    await this.evaluate(fn)
  else
    @remote_object.set_file_input_files(@client, file_paths, backend_node_id)
  end
end