puppeteer-ruby 0.0.5 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/docs/Puppeteer.html +53 -43
  4. data/docs/Puppeteer/AsyncAwaitBehavior.html +1 -1
  5. data/docs/Puppeteer/Browser.html +255 -147
  6. data/docs/Puppeteer/BrowserContext.html +1 -1
  7. data/docs/Puppeteer/BrowserFetcher.html +1 -1
  8. data/docs/Puppeteer/BrowserRunner.html +1 -1
  9. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +1 -1
  10. data/docs/Puppeteer/CDPSession.html +86 -57
  11. data/docs/Puppeteer/CDPSession/Error.html +1 -1
  12. data/docs/Puppeteer/ConcurrentRubyUtils.html +1 -1
  13. data/docs/Puppeteer/Connection.html +65 -61
  14. data/docs/Puppeteer/Connection/MessageCallback.html +1 -1
  15. data/docs/Puppeteer/Connection/ProtocolError.html +1 -1
  16. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +5 -5
  17. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +12 -12
  18. data/docs/Puppeteer/ConsoleMessage.html +1 -1
  19. data/docs/Puppeteer/ConsoleMessage/Location.html +1 -1
  20. data/docs/Puppeteer/DOMWorld.html +708 -69
  21. data/docs/Puppeteer/DOMWorld/DetachedError.html +1 -1
  22. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +1 -1
  23. data/docs/Puppeteer/DebugPrint.html +2 -2
  24. data/docs/Puppeteer/Device.html +1 -1
  25. data/docs/Puppeteer/Devices.html +1 -1
  26. data/docs/Puppeteer/ElementHandle.html +1120 -181
  27. data/docs/Puppeteer/ElementHandle/BoundingBox.html +507 -0
  28. data/docs/Puppeteer/ElementHandle/BoxModel.html +404 -0
  29. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +5 -5
  30. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +5 -5
  31. data/docs/Puppeteer/ElementHandle/Point.html +40 -29
  32. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +1 -1
  33. data/docs/Puppeteer/EmulationManager.html +1 -1
  34. data/docs/Puppeteer/EventCallbackable.html +1 -1
  35. data/docs/Puppeteer/EventCallbackable/EventListeners.html +1 -1
  36. data/docs/Puppeteer/ExecutionContext.html +227 -1
  37. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +1 -1
  38. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +1 -1
  39. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +2 -2
  40. data/docs/Puppeteer/FileChooser.html +455 -0
  41. data/docs/Puppeteer/Frame.html +492 -304
  42. data/docs/Puppeteer/FrameManager.html +22 -26
  43. data/docs/Puppeteer/FrameManager/NavigationError.html +1 -1
  44. data/docs/Puppeteer/IfPresent.html +1 -1
  45. data/docs/Puppeteer/JSHandle.html +1 -1
  46. data/docs/Puppeteer/Keyboard.html +1 -1
  47. data/docs/Puppeteer/Keyboard/KeyDefinition.html +1 -1
  48. data/docs/Puppeteer/Keyboard/KeyDescription.html +1 -1
  49. data/docs/Puppeteer/Launcher.html +1 -1
  50. data/docs/Puppeteer/Launcher/Base.html +1 -1
  51. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +1 -1
  52. data/docs/Puppeteer/Launcher/BrowserOptions.html +1 -1
  53. data/docs/Puppeteer/Launcher/Chrome.html +64 -23
  54. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +1 -1
  55. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +1 -1
  56. data/docs/Puppeteer/Launcher/LaunchOptions.html +1 -1
  57. data/docs/Puppeteer/LifecycleWatcher.html +1 -1
  58. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +1 -1
  59. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +1 -1
  60. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +1 -1
  61. data/docs/Puppeteer/Mouse.html +31 -41
  62. data/docs/Puppeteer/Mouse/Button.html +1 -1
  63. data/docs/Puppeteer/NetworkManager.html +1 -1
  64. data/docs/Puppeteer/NetworkManager/Credentials.html +1 -1
  65. data/docs/Puppeteer/Page.html +980 -436
  66. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
  67. data/docs/Puppeteer/Page/ScreenshotOptions.html +1 -1
  68. data/docs/Puppeteer/Page/ScriptTag.html +24 -24
  69. data/docs/Puppeteer/Page/StyleTag.html +19 -19
  70. data/docs/Puppeteer/Page/TargetCrashedError.html +1 -1
  71. data/docs/Puppeteer/RemoteObject.html +173 -37
  72. data/docs/Puppeteer/Target.html +149 -197
  73. data/docs/Puppeteer/Target/InitializeFailure.html +1 -1
  74. data/docs/Puppeteer/Target/TargetInfo.html +1 -1
  75. data/docs/Puppeteer/TimeoutError.html +2 -2
  76. data/docs/Puppeteer/TimeoutSettings.html +1 -1
  77. data/docs/Puppeteer/TouchScreen.html +1 -1
  78. data/docs/Puppeteer/Viewport.html +81 -1
  79. data/docs/Puppeteer/WaitTask.html +434 -8
  80. data/docs/Puppeteer/WaitTask/TerminatedError.html +1 -1
  81. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  82. data/docs/Puppeteer/WebSocket.html +26 -26
  83. data/docs/Puppeteer/WebSocket/DriverImpl.html +1 -1
  84. data/docs/Puppeteer/WebSocket/TransportError.html +124 -0
  85. data/docs/Puppeteer/WebSocketTransport.html +2 -2
  86. data/docs/Puppeteer/WebSocktTransportError.html +1 -1
  87. data/docs/_index.html +47 -19
  88. data/docs/class_list.html +1 -1
  89. data/docs/file.README.html +5 -3
  90. data/docs/index.html +5 -3
  91. data/docs/method_list.html +788 -444
  92. data/docs/top-level-namespace.html +1 -1
  93. data/lib/puppeteer.rb +7 -1
  94. data/lib/puppeteer/browser.rb +21 -6
  95. data/lib/puppeteer/browser_runner.rb +1 -1
  96. data/lib/puppeteer/cdp_session.rb +33 -11
  97. data/lib/puppeteer/connection.rb +13 -1
  98. data/lib/puppeteer/dom_world.rb +113 -108
  99. data/lib/puppeteer/element_handle.rb +183 -222
  100. data/lib/puppeteer/element_handle/bounding_box.rb +12 -0
  101. data/lib/puppeteer/element_handle/box_model.rb +19 -0
  102. data/lib/puppeteer/element_handle/point.rb +26 -0
  103. data/lib/puppeteer/errors.rb +1 -3
  104. data/lib/puppeteer/execution_context.rb +36 -17
  105. data/lib/puppeteer/file_chooser.rb +29 -0
  106. data/lib/puppeteer/frame.rb +32 -12
  107. data/lib/puppeteer/frame_manager.rb +0 -2
  108. data/lib/puppeteer/launcher/chrome.rb +48 -2
  109. data/lib/puppeteer/mouse.rb +3 -8
  110. data/lib/puppeteer/page.rb +116 -65
  111. data/lib/puppeteer/remote_object.rb +15 -1
  112. data/lib/puppeteer/target.rb +24 -24
  113. data/lib/puppeteer/version.rb +1 -1
  114. data/lib/puppeteer/viewport.rb +18 -0
  115. data/lib/puppeteer/wait_task.rb +183 -1
  116. data/lib/puppeteer/web_socket.rb +3 -1
  117. data/lib/puppeteer/web_socket_transport.rb +1 -1
  118. metadata +12 -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 Sat Jun 20 17:15:05 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>
@@ -25,6 +25,7 @@ require 'puppeteer/devices'
25
25
  require 'puppeteer/dom_world'
26
26
  require 'puppeteer/emulation_manager'
27
27
  require 'puppeteer/execution_context'
28
+ require 'puppeteer/file_chooser'
28
29
  require 'puppeteer/frame'
29
30
  require 'puppeteer/frame_manager'
30
31
  require 'puppeteer/js_handle'
@@ -142,7 +143,12 @@ class Puppeteer
142
143
  default_viewport: default_viewport,
143
144
  slow_mo: slow_mo,
144
145
  }.compact
145
- launcher.connect(options)
146
+ browser = launcher.connect(options)
147
+ if block_given?
148
+ yield(browser)
149
+ else
150
+ browser
151
+ end
146
152
  end
147
153
 
148
154
  # @return {string}
@@ -46,7 +46,7 @@ class Puppeteer::Browser
46
46
  @contexts[context_id] = Puppeteer::BrowserContext.new(@connection, self. context_id)
47
47
  end
48
48
  @targets = {}
49
- @connection.on_event 'Events.CDPSession.Disconnected' do
49
+ @connection.on_event 'Events.Connection.Disconnected' do
50
50
  emit_event 'Events.Browser.Disconnected'
51
51
  end
52
52
  @connection.on_event 'Target.targetCreated', &method(:handle_target_created)
@@ -54,6 +54,22 @@ class Puppeteer::Browser
54
54
  @connection.on_event 'Target.targetInfoChanged', &method(:handle_target_info_changed)
55
55
  end
56
56
 
57
+ EVENT_MAPPINGS = {
58
+ disconnected: 'Events.Browser.Disconnected',
59
+ targetcreated: 'Events.Browser.TargetCreated',
60
+ targetchanged: 'Events.Browser.TargetChanged',
61
+ targetdestroyed: 'Events.Browser.TargetDestroyed',
62
+ }
63
+
64
+ # @param event_name [Symbol] either of :disconnected, :targetcreated, :targetchanged, :targetdestroyed
65
+ def on(event_name, &block)
66
+ unless EVENT_MAPPINGS.has_key?(event_name.to_sym)
67
+ raise ArgumentError.new("Unknown event name: #{event_name}. Known events are #{EVENT_MAPPINGS.keys.join(", ")}")
68
+ end
69
+
70
+ add_event_listener(EVENT_MAPPINGS[event_name.to_sym], &block)
71
+ end
72
+
57
73
  # @return [Puppeteer::BrowserRunner::BrowserProcess]
58
74
  def process
59
75
  @process
@@ -102,8 +118,7 @@ class Puppeteer::Browser
102
118
  )
103
119
  # assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
104
120
  @targets[target_info.target_id] = target
105
-
106
- target.on_initialize_succeeded do
121
+ if await target.initialized_promise
107
122
  emit_event 'Events.Browser.TargetCreated', target
108
123
  context.emit_event 'Events.BrowserContext.TargetCreated', target
109
124
  end
@@ -118,10 +133,10 @@ class Puppeteer::Browser
118
133
  def handle_target_destroyed(event)
119
134
  target_id = event['targetId']
120
135
  target = @targets[target_id]
121
- target.handle_initialized(false)
136
+ target.ignore_initialize_callback_promise
122
137
  @targets.delete(target_id)
123
- target.handle_closed
124
- target.on_initialize_succeeded do
138
+ target.closed_callback
139
+ if await target.initialized_promise
125
140
  emit_event 'Events.Browser.TargetDestroyed', target
126
141
  target.browser_context.emit_event 'Events.BrowserContext.TargetDestroyed', target
127
142
  end
@@ -147,7 +147,7 @@ class Puppeteer::BrowserRunner
147
147
  end
148
148
 
149
149
  private def wait_for_ws_endpoint(browser_process, timeout, preferred_revision)
150
- Timeout.timeout(timeout / 1000) do
150
+ Timeout.timeout(timeout / 1000.0) do
151
151
  loop do
152
152
  line = browser_process.stderr.readline
153
153
  /^DevTools listening on (ws:\/\/.*)$/.match(line) do |m|
@@ -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.")
@@ -44,7 +44,9 @@ class Puppeteer::Connection
44
44
 
45
45
  @transport = transport
46
46
  @transport.on_message do |data|
47
- async_handle_message(JSON.parse(data))
47
+ message = JSON.parse(data)
48
+ sleep_before_handling_message(message)
49
+ async_handle_message(message)
48
50
  end
49
51
  @transport.on_close do |reason, code|
50
52
  handle_close(reason, code)
@@ -54,6 +56,16 @@ class Puppeteer::Connection
54
56
  @closed = false
55
57
  end
56
58
 
59
+ private def sleep_before_handling_message(message)
60
+ # Puppeteer doesn't handle any Network monitoring responses.
61
+ # So we don't have to sleep.
62
+ return if message['method']&.start_with?('Network.')
63
+
64
+ # For some reasons, sleeping a bit reduces trivial errors...
65
+ # 4ms is an interval of internal shared timer of WebKit.
66
+ sleep 0.004
67
+ end
68
+
57
69
  def self.from_session(session)
58
70
  session.connection
59
71
  end
@@ -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
@@ -93,17 +97,18 @@ class Puppeteer::DOMWorld
93
97
  document.S(selector)
94
98
  end
95
99
 
96
- class DocumentEvaluationError < StandardError; end
97
-
98
100
  private def evaluate_document
99
101
  # sometimes execution_context.evaluate_handle('document') returns null object.
100
102
  # D, [2020-04-24T02:17:51.023631 #220] DEBUG -- : RECV << {"id"=>20, "result"=>{"result"=>{"type"=>"object", "subtype"=>"null", "value"=>nil}}, "sessionId"=>"78E9CF1E14D81294E320E7C20E5CDE06"}
101
103
  # retry if so.
102
- 5.times do
103
- handle = execution_context.evaluate_handle('document')
104
- return handle if handle.is_a?(Puppeteer::ElementHandle)
104
+ Timeout.timeout(3) do
105
+ loop do
106
+ handle = execution_context.evaluate_handle('document')
107
+ return handle if handle.is_a?(Puppeteer::ElementHandle)
108
+ end
105
109
  end
106
- raise DocumentEvaluationError.new("'document' object cannot be evaluated as an Element")
110
+ rescue Timeout::Error
111
+ raise 'Bug of puppeteer-ruby...'
107
112
  end
108
113
 
109
114
  private def document
@@ -351,58 +356,47 @@ class Puppeteer::DOMWorld
351
356
  # await handle.dispose();
352
357
  # }
353
358
 
354
- # /**
355
- # * @param {string} selector
356
- # * @param {!Array<string>} values
357
- # * @return {!Promise<!Array<string>>}
358
- # */
359
- # async select(selector, ...values) {
360
- # const handle = await this.$(selector);
361
- # assert(handle, 'No node found for selector: ' + selector);
362
- # const result = await handle.select(...values);
363
- # await handle.dispose();
364
- # return result;
365
- # }
359
+ # @param selector [String]
360
+ # @return [Array<String>]
361
+ def select(selector, *values)
362
+ handle = S(selector)
363
+ result = handle.select(*values)
364
+ handle.dispose
366
365
 
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
- # }
366
+ result
367
+ end
376
368
 
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
- # }
369
+ # @param selector [String]
370
+ def tap(selector)
371
+ handle = S(selector)
372
+ handle.tap
373
+ handle.dispose
374
+ end
388
375
 
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
- # }
376
+ # @param selector [String]
377
+ # @param text [String]
378
+ # @param delay [Number]
379
+ def type_text(selector, text, delay: nil)
380
+ handle = S(selector)
381
+ handle.type_text(text, delay: delay)
382
+ handle.dispose
383
+ end
397
384
 
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
- # }
385
+ # @param selector [String]
386
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
387
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
388
+ # @param timeout [Integer]
389
+ def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
390
+ wait_for_selector_or_xpath(selector, false, visible: visible, hidden: hidden, timeout: timeout)
391
+ end
392
+
393
+ # @param xpath [String]
394
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
395
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
396
+ # @param timeout [Integer]
397
+ def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
398
+ wait_for_selector_or_xpath(xpath, true, visible: visible, hidden: hidden, timeout: timeout)
399
+ end
406
400
 
407
401
  # /**
408
402
  # * @param {Function|string} pageFunction
@@ -424,57 +418,68 @@ class Puppeteer::DOMWorld
424
418
  # return this.evaluate(() => document.title);
425
419
  # }
426
420
 
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();
421
+ # @param selector_or_xpath [String]
422
+ # @param is_xpath [Boolean]
423
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
424
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
425
+ # @param timeout [Integer]
426
+ private def wait_for_selector_or_xpath(selector_or_xpath, is_xpath, visible: nil, hidden: nil, timeout: nil)
427
+ option_wait_for_visible = visible || false
428
+ option_wait_for_hidden = hidden || false
429
+ option_timeout = timeout || @timeout_settings.timeout
430
+
431
+ polling =
432
+ if option_wait_for_visible || option_wait_for_hidden
433
+ 'raf'
434
+ else
435
+ 'mutation'
436
+ end
437
+ title = "#{is_xpath ? :XPath : :selector} #{selector_or_xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
438
+
439
+ wait_task = Puppeteer::WaitTask.new(
440
+ dom_world: self,
441
+ predicate_body: "return (#{PREDICATE})(...args)",
442
+ title: title,
443
+ polling: polling,
444
+ timeout: option_timeout,
445
+ args: [selector_or_xpath, is_xpath, option_wait_for_visible, option_wait_for_hidden],
446
+ )
447
+ handle = wait_task.await_promise
448
+ unless handle.as_element
449
+ handle.dispose
450
+ return nil
451
+ end
452
+ handle.as_element
453
+ end
448
454
 
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
- # }
455
+ PREDICATE = <<~JAVASCRIPT
456
+ /**
457
+ * @param {string} selectorOrXPath
458
+ * @param {boolean} isXPath
459
+ * @param {boolean} waitForVisible
460
+ * @param {boolean} waitForHidden
461
+ * @return {?Node|boolean}
462
+ */
463
+ function _(selectorOrXPath, isXPath, waitForVisible, waitForHidden) {
464
+ const node = isXPath
465
+ ? document.evaluate(selectorOrXPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
466
+ : document.querySelector(selectorOrXPath);
467
+ if (!node)
468
+ return waitForHidden;
469
+ if (!waitForVisible && !waitForHidden)
470
+ return node;
471
+ const element = /** @type {Element} */ (node.nodeType === Node.TEXT_NODE ? node.parentElement : node);
472
+ const style = window.getComputedStyle(element);
473
+ const isVisible = style && style.visibility !== 'hidden' && hasVisibleBoundingBox();
474
+ const success = (waitForVisible === isVisible || waitForHidden === !isVisible);
475
+ return success ? node : null;
476
+ /**
477
+ * @return {boolean}
478
+ */
479
+ function hasVisibleBoundingBox() {
480
+ const rect = element.getBoundingClientRect();
481
+ return !!(rect.top || rect.bottom || rect.width || rect.height);
482
+ }
483
+ }
484
+ JAVASCRIPT
480
485
  end