puppeteer-ruby 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/docs/Puppeteer.html +2 -2
  3. data/docs/Puppeteer/AsyncAwaitBehavior.html +1 -1
  4. data/docs/Puppeteer/Browser.html +1 -1
  5. data/docs/Puppeteer/BrowserContext.html +1 -1
  6. data/docs/Puppeteer/BrowserFetcher.html +1 -1
  7. data/docs/Puppeteer/BrowserRunner.html +1 -1
  8. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +1 -1
  9. data/docs/Puppeteer/CDPSession.html +86 -57
  10. data/docs/Puppeteer/CDPSession/Error.html +1 -1
  11. data/docs/Puppeteer/ConcurrentRubyUtils.html +1 -1
  12. data/docs/Puppeteer/Connection.html +1 -1
  13. data/docs/Puppeteer/Connection/MessageCallback.html +1 -1
  14. data/docs/Puppeteer/Connection/ProtocolError.html +1 -1
  15. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +1 -1
  16. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +1 -1
  17. data/docs/Puppeteer/ConsoleMessage.html +1 -1
  18. data/docs/Puppeteer/ConsoleMessage/Location.html +1 -1
  19. data/docs/Puppeteer/DOMWorld.html +610 -68
  20. data/docs/Puppeteer/DOMWorld/DetachedError.html +1 -1
  21. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +1 -1
  22. data/docs/Puppeteer/DebugPrint.html +2 -2
  23. data/docs/Puppeteer/Device.html +1 -1
  24. data/docs/Puppeteer/Devices.html +1 -1
  25. data/docs/Puppeteer/ElementHandle.html +306 -109
  26. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +5 -5
  27. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +5 -5
  28. data/docs/Puppeteer/ElementHandle/Point.html +28 -28
  29. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +1 -1
  30. data/docs/Puppeteer/EmulationManager.html +1 -1
  31. data/docs/Puppeteer/EventCallbackable.html +1 -1
  32. data/docs/Puppeteer/EventCallbackable/EventListeners.html +1 -1
  33. data/docs/Puppeteer/ExecutionContext.html +124 -1
  34. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +1 -1
  35. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +1 -1
  36. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +2 -2
  37. data/docs/Puppeteer/Frame.html +117 -65
  38. data/docs/Puppeteer/FrameManager.html +1 -1
  39. data/docs/Puppeteer/FrameManager/NavigationError.html +1 -1
  40. data/docs/Puppeteer/IfPresent.html +1 -1
  41. data/docs/Puppeteer/JSHandle.html +1 -1
  42. data/docs/Puppeteer/Keyboard.html +1 -1
  43. data/docs/Puppeteer/Keyboard/KeyDefinition.html +1 -1
  44. data/docs/Puppeteer/Keyboard/KeyDescription.html +1 -1
  45. data/docs/Puppeteer/Launcher.html +1 -1
  46. data/docs/Puppeteer/Launcher/Base.html +1 -1
  47. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +1 -1
  48. data/docs/Puppeteer/Launcher/BrowserOptions.html +1 -1
  49. data/docs/Puppeteer/Launcher/Chrome.html +1 -1
  50. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +1 -1
  51. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +1 -1
  52. data/docs/Puppeteer/Launcher/LaunchOptions.html +1 -1
  53. data/docs/Puppeteer/LifecycleWatcher.html +1 -1
  54. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +1 -1
  55. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +1 -1
  56. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +1 -1
  57. data/docs/Puppeteer/Mouse.html +1 -1
  58. data/docs/Puppeteer/Mouse/Button.html +1 -1
  59. data/docs/Puppeteer/NetworkManager.html +1 -1
  60. data/docs/Puppeteer/NetworkManager/Credentials.html +1 -1
  61. data/docs/Puppeteer/Page.html +95 -43
  62. data/docs/Puppeteer/Page/ScreenshotOptions.html +1 -1
  63. data/docs/Puppeteer/Page/ScriptTag.html +1 -1
  64. data/docs/Puppeteer/Page/StyleTag.html +1 -1
  65. data/docs/Puppeteer/Page/TargetCrashedError.html +1 -1
  66. data/docs/Puppeteer/RemoteObject.html +1 -1
  67. data/docs/Puppeteer/Target.html +1 -1
  68. data/docs/Puppeteer/Target/InitializeFailure.html +1 -1
  69. data/docs/Puppeteer/Target/TargetInfo.html +1 -1
  70. data/docs/Puppeteer/TimeoutError.html +1 -1
  71. data/docs/Puppeteer/TimeoutSettings.html +1 -1
  72. data/docs/Puppeteer/TouchScreen.html +1 -1
  73. data/docs/Puppeteer/Viewport.html +1 -1
  74. data/docs/Puppeteer/WaitTask.html +434 -8
  75. data/docs/Puppeteer/WaitTask/TerminatedError.html +1 -1
  76. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  77. data/docs/Puppeteer/WebSocket.html +1 -1
  78. data/docs/Puppeteer/WebSocket/DriverImpl.html +1 -1
  79. data/docs/Puppeteer/WebSocketTransport.html +1 -1
  80. data/docs/Puppeteer/WebSocktTransportError.html +1 -1
  81. data/docs/_index.html +8 -1
  82. data/docs/class_list.html +1 -1
  83. data/docs/file.README.html +1 -1
  84. data/docs/index.html +1 -1
  85. data/docs/method_list.html +615 -519
  86. data/docs/top-level-namespace.html +1 -1
  87. data/lib/puppeteer/cdp_session.rb +33 -11
  88. data/lib/puppeteer/dom_world.rb +97 -90
  89. data/lib/puppeteer/element_handle.rb +36 -54
  90. data/lib/puppeteer/execution_context.rb +23 -17
  91. data/lib/puppeteer/frame.rb +13 -11
  92. data/lib/puppeteer/page.rb +29 -11
  93. data/lib/puppeteer/version.rb +1 -1
  94. data/lib/puppeteer/wait_task.rb +183 -1
  95. metadata +3 -2
@@ -116,7 +116,7 @@
116
116
  </div>
117
117
 
118
118
  <div id="footer">
119
- Generated on Sun Apr 26 15:52:55 2020 by
119
+ Generated on Mon May 25 23:22:48 2020 by
120
120
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
121
121
  0.9.24 (ruby-2.6.3).
122
122
  </div>
@@ -1,4 +1,5 @@
1
1
  class Puppeteer::CDPSession
2
+ include Puppeteer::DebugPrint
2
3
  include Puppeteer::EventCallbackable
3
4
  using Puppeteer::AsyncAwaitBehavior
4
5
 
@@ -12,6 +13,7 @@ class Puppeteer::CDPSession
12
13
  @connection = connection
13
14
  @target_type = target_type
14
15
  @session_id = session_id
16
+ @pending_messages = {}
15
17
  end
16
18
 
17
19
  attr_reader :connection
@@ -32,7 +34,13 @@ class Puppeteer::CDPSession
32
34
  end
33
35
  id = @connection.raw_send(message: { sessionId: @session_id, method: method, params: params })
34
36
  promise = resolvable_future
35
- @callbacks[id] = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
37
+ callback = Puppeteer::Connection::MessageCallback.new(method: method, promise: promise)
38
+ if pending_message = @pending_messages.delete(id)
39
+ debug_puts "Pending message (id: #{id}) is handled"
40
+ callback_with_message(callback, pending_message)
41
+ else
42
+ @callbacks[id] = callback
43
+ end
36
44
  promise
37
45
  end
38
46
 
@@ -40,23 +48,37 @@ class Puppeteer::CDPSession
40
48
  def handle_message(message)
41
49
  if message['id']
42
50
  if callback = @callbacks.delete(message['id'])
43
- if message['error']
44
- callback.reject(
45
- Puppeteer::Connection::ProtocolError.new(
46
- method: callback.method,
47
- error_message: message['error']['message'],
48
- error_data: message['error']['data']))
49
- else
50
- callback.resolve(message['result'])
51
- end
51
+ callback_with_message(callback, message)
52
52
  else
53
- raise Error.new("unknown id: #{message['id']}")
53
+ debug_puts "unknown id: #{id}. Store it into pending message"
54
+
55
+ # RECV is often notified before SEND.
56
+ # Wait about 10 frames before throwing an error.
57
+ message_id = message['id']
58
+ @pending_messages[message_id] = message
59
+ Concurrent::Promises.schedule(0.16, message_id) do |id|
60
+ if @pending_messages.delete(id)
61
+ raise Error.new("unknown id: #{id}")
62
+ end
63
+ end
54
64
  end
55
65
  else
56
66
  emit_event message['method'], message['params']
57
67
  end
58
68
  end
59
69
 
70
+ private def callback_with_message(callback, message)
71
+ if message['error']
72
+ callback.reject(
73
+ Puppeteer::Connection::ProtocolError.new(
74
+ method: callback.method,
75
+ error_message: message['error']['message'],
76
+ error_data: message['error']['data']))
77
+ else
78
+ callback.resolve(message['result'])
79
+ end
80
+ end
81
+
60
82
  def detach
61
83
  if !@connection
62
84
  raise Error.new("Session already detarched. Most likely the #{@target_type} has been closed.")
@@ -19,6 +19,11 @@ class Puppeteer::DOMWorld
19
19
 
20
20
  attr_reader :frame
21
21
 
22
+ # only used in Puppeteer::WaitTask#initialize
23
+ def _wait_tasks
24
+ @wait_tasks
25
+ end
26
+
22
27
  # @param {?Puppeteer.ExecutionContext} context
23
28
  def context=(context)
24
29
  # D, [2020-04-12T22:45:03.938754 #46154] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>3, "origin"=>"https://github.com", "name"=>"", "auxData"=>{"isDefault"=>true, "type"=>"default", "frameId"=>"3AD7F1E82BCBA88BFE31D03BC49FF6CB"}}}, "sessionId"=>"636CEF0C4FEAFC4FE815E9E7B5F7BA68"}
@@ -35,8 +40,7 @@ class Puppeteer::DOMWorld
35
40
  @context_promise = resolvable_future
36
41
  end
37
42
  @context_promise.fulfill(context)
38
- # for (const waitTask of this._waitTasks)
39
- # waitTask.rerun();
43
+ @wait_tasks.each(&:async_rerun)
40
44
  else
41
45
  raise ArgumentError.new("context should now be nil. Use #delete_context for clearing document.")
42
46
  end
@@ -364,45 +368,37 @@ class Puppeteer::DOMWorld
364
368
  # return result;
365
369
  # }
366
370
 
367
- # /**
368
- # * @param {string} selector
369
- # */
370
- # async tap(selector) {
371
- # const handle = await this.$(selector);
372
- # assert(handle, 'No node found for selector: ' + selector);
373
- # await handle.tap();
374
- # await handle.dispose();
375
- # }
371
+ # @param selector [String]
372
+ def tap(selector)
373
+ handle = S(selector)
374
+ handle.tap
375
+ handle.dispose
376
+ end
376
377
 
377
- # /**
378
- # * @param {string} selector
379
- # * @param {string} text
380
- # * @param {{delay: (number|undefined)}=} options
381
- # */
382
- # async type(selector, text, options) {
383
- # const handle = await this.$(selector);
384
- # assert(handle, 'No node found for selector: ' + selector);
385
- # await handle.type(text, options);
386
- # await handle.dispose();
387
- # }
378
+ # @param selector [String]
379
+ # @param text [String]
380
+ # @param delay [Number]
381
+ def type_text(selector, text, delay: nil)
382
+ handle = S(selector)
383
+ handle.type_text(text, delay: delay)
384
+ handle.dispose
385
+ end
388
386
 
389
- # /**
390
- # * @param {string} selector
391
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
392
- # * @return {!Promise<?Puppeteer.ElementHandle>}
393
- # */
394
- # waitForSelector(selector, options) {
395
- # return this._waitForSelectorOrXPath(selector, false, options);
396
- # }
387
+ # @param selector [String]
388
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
389
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
390
+ # @param timeout [Integer]
391
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
392
+ wait_for_selector_or_xpath(selector, false, visible: visible, hidden: hidden, timeout: timeout)
393
+ end
397
394
 
398
- # /**
399
- # * @param {string} xpath
400
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
401
- # * @return {!Promise<?Puppeteer.ElementHandle>}
402
- # */
403
- # waitForXPath(xpath, options) {
404
- # return this._waitForSelectorOrXPath(xpath, true, options);
405
- # }
395
+ # @param xpath [String]
396
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
397
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
398
+ # @param timeout [Integer]
399
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
400
+ wait_for_selector_or_xpath(xpath, true, visible: visible, hidden: hidden, timeout: timeout)
401
+ end
406
402
 
407
403
  # /**
408
404
  # * @param {Function|string} pageFunction
@@ -424,57 +420,68 @@ class Puppeteer::DOMWorld
424
420
  # return this.evaluate(() => document.title);
425
421
  # }
426
422
 
427
- # /**
428
- # * @param {string} selectorOrXPath
429
- # * @param {boolean} isXPath
430
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
431
- # * @return {!Promise<?Puppeteer.ElementHandle>}
432
- # */
433
- # async _waitForSelectorOrXPath(selectorOrXPath, isXPath, options = {}) {
434
- # const {
435
- # visible: waitForVisible = false,
436
- # hidden: waitForHidden = false,
437
- # timeout = this._timeoutSettings.timeout(),
438
- # } = options;
439
- # const polling = waitForVisible || waitForHidden ? 'raf' : 'mutation';
440
- # const title = `${isXPath ? 'XPath' : 'selector'} "${selectorOrXPath}"${waitForHidden ? ' to be hidden' : ''}`;
441
- # const waitTask = new WaitTask(this, predicate, title, polling, timeout, selectorOrXPath, isXPath, waitForVisible, waitForHidden);
442
- # const handle = await waitTask.promise;
443
- # if (!handle.asElement()) {
444
- # await handle.dispose();
445
- # return null;
446
- # }
447
- # return handle.asElement();
423
+ # @param selector_or_xpath [String]
424
+ # @param is_xpath [Boolean]
425
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
426
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
427
+ # @param timeout [Integer]
428
+ private def wait_for_selector_or_xpath(selector_or_xpath, is_xpath, visible: nil, hidden: nil, timeout: nil)
429
+ option_wait_for_visible = visible || false
430
+ option_wait_for_hidden = hidden || false
431
+ option_timeout = timeout || @timeout_settings.timeout
432
+
433
+ polling =
434
+ if option_wait_for_visible || option_wait_for_hidden
435
+ 'raf'
436
+ else
437
+ 'mutation'
438
+ end
439
+ title = "#{is_xpath ? :XPath : :selector} #{selector_or_xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
440
+
441
+ wait_task = Puppeteer::WaitTask.new(
442
+ dom_world: self,
443
+ predicate_body: "return (#{PREDICATE})(...args)",
444
+ title: title,
445
+ polling: polling,
446
+ timeout: option_timeout,
447
+ args: [selector_or_xpath, is_xpath, option_wait_for_visible, option_wait_for_hidden],
448
+ )
449
+ handle = wait_task.await_promise
450
+ unless handle.as_element
451
+ handle.dispose
452
+ return nil
453
+ end
454
+ handle.as_element
455
+ end
448
456
 
449
- # /**
450
- # * @param {string} selectorOrXPath
451
- # * @param {boolean} isXPath
452
- # * @param {boolean} waitForVisible
453
- # * @param {boolean} waitForHidden
454
- # * @return {?Node|boolean}
455
- # */
456
- # function predicate(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
457
- # const node = isXPath
458
- # ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
459
- # : document.querySelector(selectorOrXPath);
460
- # if (!node)
461
- # return waitForHidden;
462
- # if (!waitForVisible && !waitForHidden)
463
- # return node;
464
- # const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
465
-
466
- # const style = window.getComputedStyle(element);
467
- # const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
468
- # const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
469
- # return success ? node : null;
470
-
471
- # /**
472
- # * @return {boolean}
473
- # */
474
- # function hasVisibleBoundingBox() {
475
- # const rect = element.getBoundingClientRect();
476
- # return !!(rect.top || rect.bottom || rect.width || rect.height);
477
- # }
478
- # }
479
- # }
457
+ PREDICATE = <<~JAVASCRIPT
458
+ /**
459
+ * @param {string} selectorOrXPath
460
+ * @param {boolean} isXPath
461
+ * @param {boolean} waitForVisible
462
+ * @param {boolean} waitForHidden
463
+ * @return {?Node|boolean}
464
+ */
465
+ function _(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
466
+ const node = isXPath
467
+ ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
468
+ : document.querySelector(selectorOrXPath);
469
+ if (!node)
470
+ return waitForHidden;
471
+ if (!waitForVisible && !waitForHidden)
472
+ return node;
473
+ const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
474
+ const style = window.getComputedStyle(element);
475
+ const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
476
+ const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
477
+ return success ? node : null;
478
+ /**
479
+ * @return {boolean}
480
+ */
481
+ function hasVisibleBoundingBox() {
482
+ const rect = element.getBoundingClientRect();
483
+ return !!(rect.top || rect.bottom || rect.width || rect.height);
484
+ }
485
+ }
486
+ JAVASCRIPT
480
487
  end
@@ -37,20 +37,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
37
37
  return 'Node is detached from document';
38
38
  if (element.nodeType !== Node.ELEMENT_NODE)
39
39
  return 'Node is not of type HTMLElement';
40
- // force-scroll if page's javascript is disabled.
41
- if (!pageJavascriptEnabled) {
42
- element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
43
- return false;
44
- }
45
- const visibleRatio = await new Promise(resolve => {
46
- const observer = new IntersectionObserver(entries => {
47
- resolve(entries[0].intersectionRatio);
48
- observer.disconnect();
49
- });
50
- observer.observe(element);
51
- });
52
- if (visibleRatio !== 1.0)
53
- element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
40
+
41
+ element.scrollIntoViewIfNeeded({block: 'center', inline: 'center', behavior: 'instant'});
54
42
  return false;
55
43
  }
56
44
  JAVASCRIPT
@@ -58,6 +46,9 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
58
46
  if error
59
47
  raise ScrollIntoViewError.new(error)
60
48
  end
49
+ # clickpoint is often calculated before scrolling is completed.
50
+ # So, just sleep about 10 frames
51
+ sleep 0.16
61
52
  end
62
53
 
63
54
  class Point
@@ -157,14 +148,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
157
148
  @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
158
149
  end
159
150
 
160
- # /**
161
- # * @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
162
- # */
163
- # async click(options) {
164
- # await this._scrollIntoViewIfNeeded();
165
- # const {x, y} = await this._clickablePoint();
166
- # await this._page.mouse.click(x, y, options);
167
- # }
151
+ # @param delay [Number]
152
+ # @param button [String] "left"|"right"|"middle"
153
+ # @param click_count [Number]
154
+ async def async_click(delay: nil, button: nil, click_count: nil)
155
+ click(delay: delay, button: button, click_count: click_count)
156
+ end
168
157
 
169
158
  # /**
170
159
  # * @param {!Array<string>} values
@@ -228,11 +217,11 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
228
217
  # }, files);
229
218
  # }
230
219
 
231
- # async tap() {
232
- # await this._scrollIntoViewIfNeeded();
233
- # const {x, y} = await this._clickablePoint();
234
- # await this._page.touchscreen.tap(x, y);
235
- # }
220
+ def tap
221
+ scroll_into_view_if_needed
222
+ point = clickable_point
223
+ @page.touchscreen.tap(point.x, point.y)
224
+ end
236
225
 
237
226
 
238
227
  def focus
@@ -419,33 +408,26 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
419
408
  result
420
409
  end
421
410
 
422
- # /**
423
- # * @param {string} expression
424
- # * @return {!Promise<!Array<!ElementHandle>>}
425
- # */
426
- # async $x(expression) {
427
- # const arrayHandle = await this.evaluateHandle(
428
- # (element, expression) => {
429
- # const document = element.ownerDocument || element;
430
- # const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
431
- # const array = [];
432
- # let item;
433
- # while ((item = iterator.iterateNext()))
434
- # array.push(item);
435
- # return array;
436
- # },
437
- # expression
438
- # );
439
- # const properties = await arrayHandle.getProperties();
440
- # await arrayHandle.dispose();
441
- # const result = [];
442
- # for (const property of properties.values()) {
443
- # const elementHandle = property.asElement();
444
- # if (elementHandle)
445
- # result.push(elementHandle);
446
- # }
447
- # return result;
448
- # }
411
+ # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
412
+ # @param expression [String]
413
+ # @return [Array<ElementHandle>]
414
+ def Sx(expression)
415
+ fn = <<~JAVASCRIPT
416
+ (element, expression) => {
417
+ const document = element.ownerDocument || element;
418
+ const iterator = document.evaluate(expression, element, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
419
+ const array = [];
420
+ let item;
421
+ while ((item = iterator.iterateNext()))
422
+ array.push(item);
423
+ return array;
424
+ }
425
+ JAVASCRIPT
426
+ handles = evaluate_handle(fn, expression)
427
+ properties = handles.properties
428
+ handles.dispose
429
+ properties.values.map(&:as_element).compact
430
+ end
449
431
 
450
432
  # /**
451
433
  # * @returns {!Promise<boolean>}
@@ -122,7 +122,7 @@ class Puppeteer::ExecutionContext
122
122
  remote_object = Puppeteer::RemoteObject.new(result['result'])
123
123
 
124
124
  if exception_details
125
- raise EvaluationError.new("Evaluation failed: #{exceptionDetails}")
125
+ raise EvaluationError.new("Evaluation failed: #{exception_details}")
126
126
  end
127
127
 
128
128
  if @return_by_value
@@ -216,20 +216,26 @@ class Puppeteer::ExecutionContext
216
216
  # return createJSHandle(this, response.objects);
217
217
  # }
218
218
 
219
- # /**
220
- # * @param {Puppeteer.ElementHandle} elementHandle
221
- # * @return {Promise<Puppeteer.ElementHandle>}
222
- # */
223
- # async _adoptElementHandle(elementHandle) {
224
- # assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
225
- # assert(this._world, 'Cannot adopt handle without DOMWorld');
226
- # const nodeInfo = await this._client.send('DOM.describeNode', {
227
- # objectId: elementHandle._remoteObject.objectId,
228
- # });
229
- # const {object} = await this._client.send('DOM.resolveNode', {
230
- # backendNodeId: nodeInfo.node.backendNodeId,
231
- # executionContextId: this._contextId,
232
- # });
233
- # return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
234
- # }
219
+ # @param element_handle [Puppeteer::ElementHandle]
220
+ # @return [Puppeteer::ElementHandle]
221
+ def adopt_element_handle(element_handle)
222
+ if element_handle.execution_context == self
223
+ raise ArgumentError.new('Cannot adopt handle that already belongs to this execution context')
224
+ end
225
+
226
+ unless @world
227
+ raise 'Cannot adopt handle without DOMWorld'
228
+ end
229
+
230
+ node_info = element_handle.remote_object.node_info(@client)
231
+ response = @client.send_message('DOM.resolveNode',
232
+ backendNodeId: node_info["node"]["backendNodeId"],
233
+ executionContextId: @context_id,
234
+ )
235
+
236
+ Puppeteer::JSHandle.create(
237
+ context: self,
238
+ remote_object: Puppeteer::RemoteObject.new(response["object"]),
239
+ )
240
+ end
235
241
  end