puppeteer-ruby 0.0.3 → 0.0.8

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +30 -0
  3. data/.github/stale.yml +16 -0
  4. data/.rubocop.yml +4 -5
  5. data/README.md +4 -1
  6. data/docs/Puppeteer.html +2020 -0
  7. data/docs/Puppeteer/AsyncAwaitBehavior.html +105 -0
  8. data/docs/Puppeteer/Browser.html +2150 -0
  9. data/docs/Puppeteer/BrowserContext.html +809 -0
  10. data/docs/Puppeteer/BrowserFetcher.html +214 -0
  11. data/docs/Puppeteer/BrowserRunner.html +914 -0
  12. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +477 -0
  13. data/docs/Puppeteer/CDPSession.html +813 -0
  14. data/docs/Puppeteer/CDPSession/Error.html +124 -0
  15. data/docs/Puppeteer/ConcurrentRubyUtils.html +430 -0
  16. data/docs/Puppeteer/Connection.html +960 -0
  17. data/docs/Puppeteer/Connection/MessageCallback.html +434 -0
  18. data/docs/Puppeteer/Connection/ProtocolError.html +216 -0
  19. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +217 -0
  20. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +244 -0
  21. data/docs/Puppeteer/ConsoleMessage.html +565 -0
  22. data/docs/Puppeteer/ConsoleMessage/Location.html +433 -0
  23. data/docs/Puppeteer/DOMWorld.html +2219 -0
  24. data/docs/Puppeteer/DOMWorld/DetachedError.html +124 -0
  25. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +124 -0
  26. data/docs/Puppeteer/DebugPrint.html +233 -0
  27. data/docs/Puppeteer/Device.html +470 -0
  28. data/docs/Puppeteer/Devices.html +139 -0
  29. data/docs/Puppeteer/ElementHandle.html +2224 -0
  30. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +206 -0
  31. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +206 -0
  32. data/docs/Puppeteer/ElementHandle/Point.html +481 -0
  33. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +124 -0
  34. data/docs/Puppeteer/EmulationManager.html +454 -0
  35. data/docs/Puppeteer/EventCallbackable.html +433 -0
  36. data/docs/Puppeteer/EventCallbackable/EventListeners.html +435 -0
  37. data/docs/Puppeteer/ExecutionContext.html +998 -0
  38. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +124 -0
  39. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +357 -0
  40. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +389 -0
  41. data/docs/Puppeteer/FileChooser.html +455 -0
  42. data/docs/Puppeteer/Frame.html +3677 -0
  43. data/docs/Puppeteer/FrameManager.html +2414 -0
  44. data/docs/Puppeteer/FrameManager/NavigationError.html +124 -0
  45. data/docs/Puppeteer/IfPresent.html +222 -0
  46. data/docs/Puppeteer/JSHandle.html +1352 -0
  47. data/docs/Puppeteer/Keyboard.html +1557 -0
  48. data/docs/Puppeteer/Keyboard/KeyDefinition.html +831 -0
  49. data/docs/Puppeteer/Keyboard/KeyDescription.html +603 -0
  50. data/docs/Puppeteer/Launcher.html +237 -0
  51. data/docs/Puppeteer/Launcher/Base.html +385 -0
  52. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +124 -0
  53. data/docs/Puppeteer/Launcher/BrowserOptions.html +441 -0
  54. data/docs/Puppeteer/Launcher/Chrome.html +669 -0
  55. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +382 -0
  56. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +531 -0
  57. data/docs/Puppeteer/Launcher/LaunchOptions.html +893 -0
  58. data/docs/Puppeteer/LifecycleWatcher.html +834 -0
  59. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +363 -0
  60. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +206 -0
  61. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +124 -0
  62. data/docs/Puppeteer/Mouse.html +1105 -0
  63. data/docs/Puppeteer/Mouse/Button.html +136 -0
  64. data/docs/Puppeteer/NetworkManager.html +901 -0
  65. data/docs/Puppeteer/NetworkManager/Credentials.html +385 -0
  66. data/docs/Puppeteer/Page.html +5970 -0
  67. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +206 -0
  68. data/docs/Puppeteer/Page/ScreenshotOptions.html +845 -0
  69. data/docs/Puppeteer/Page/ScriptTag.html +555 -0
  70. data/docs/Puppeteer/Page/StyleTag.html +448 -0
  71. data/docs/Puppeteer/Page/TargetCrashedError.html +124 -0
  72. data/docs/Puppeteer/RemoteObject.html +1016 -0
  73. data/docs/Puppeteer/Target.html +1384 -0
  74. data/docs/Puppeteer/Target/InitializeFailure.html +124 -0
  75. data/docs/Puppeteer/Target/TargetInfo.html +729 -0
  76. data/docs/Puppeteer/TimeoutError.html +135 -0
  77. data/docs/Puppeteer/TimeoutSettings.html +496 -0
  78. data/docs/Puppeteer/TouchScreen.html +464 -0
  79. data/docs/Puppeteer/Viewport.html +757 -0
  80. data/docs/Puppeteer/WaitTask.html +637 -0
  81. data/docs/Puppeteer/WaitTask/TerminatedError.html +124 -0
  82. data/docs/Puppeteer/WaitTask/TimeoutError.html +206 -0
  83. data/docs/Puppeteer/WebSocket.html +673 -0
  84. data/docs/Puppeteer/WebSocket/DriverImpl.html +412 -0
  85. data/docs/Puppeteer/WebSocketTransport.html +600 -0
  86. data/docs/Puppeteer/WebSocktTransportError.html +124 -0
  87. data/docs/_index.html +809 -0
  88. data/docs/class_list.html +51 -0
  89. data/docs/css/common.css +1 -0
  90. data/docs/css/full_list.css +58 -0
  91. data/docs/css/style.css +496 -0
  92. data/docs/file.README.html +123 -0
  93. data/docs/file_list.html +56 -0
  94. data/docs/frames.html +17 -0
  95. data/docs/index.html +123 -0
  96. data/docs/js/app.js +314 -0
  97. data/docs/js/full_list.js +216 -0
  98. data/docs/js/jquery.js +4 -0
  99. data/docs/method_list.html +3979 -0
  100. data/docs/top-level-namespace.html +126 -0
  101. data/lib/puppeteer.rb +16 -8
  102. data/lib/puppeteer/async_await_behavior.rb +6 -0
  103. data/lib/puppeteer/browser.rb +21 -1
  104. data/lib/puppeteer/browser_runner.rb +1 -1
  105. data/lib/puppeteer/cdp_session.rb +33 -11
  106. data/lib/puppeteer/connection.rb +1 -1
  107. data/lib/puppeteer/dom_world.rb +142 -121
  108. data/lib/puppeteer/element_handle.rb +223 -181
  109. data/lib/puppeteer/execution_context.rb +41 -17
  110. data/lib/puppeteer/file_chooser.rb +29 -0
  111. data/lib/puppeteer/frame.rb +23 -15
  112. data/lib/puppeteer/frame_manager.rb +7 -9
  113. data/lib/puppeteer/js_handle.rb +3 -3
  114. data/lib/puppeteer/keyboard.rb +1 -1
  115. data/lib/puppeteer/keyboard/us_keyboard_layout.rb +4 -4
  116. data/lib/puppeteer/launcher.rb +0 -1
  117. data/lib/puppeteer/launcher/chrome.rb +48 -2
  118. data/lib/puppeteer/lifecycle_watcher.rb +9 -4
  119. data/lib/puppeteer/mouse.rb +10 -7
  120. data/lib/puppeteer/page.rb +134 -70
  121. data/lib/puppeteer/remote_object.rb +11 -1
  122. data/lib/puppeteer/version.rb +1 -1
  123. data/lib/puppeteer/wait_task.rb +183 -1
  124. data/puppeteer-ruby.gemspec +4 -1
  125. metadata +143 -4
@@ -16,6 +16,11 @@ class Puppeteer::ExecutionContext
16
16
 
17
17
  attr_reader :client, :world
18
18
 
19
+ # only used in DomWorld#delete_context
20
+ def _context_id
21
+ @context_id
22
+ end
23
+
19
24
  # @return [Puppeteer::Frame]
20
25
  def frame
21
26
  if_present(@world) do |world|
@@ -117,7 +122,7 @@ class Puppeteer::ExecutionContext
117
122
  remote_object = Puppeteer::RemoteObject.new(result['result'])
118
123
 
119
124
  if exception_details
120
- raise EvaluationError.new("Evaluation failed: #{exceptionDetails}")
125
+ raise EvaluationError.new("Evaluation failed: #{exception_details}")
121
126
  end
122
127
 
123
128
  if @return_by_value
@@ -211,20 +216,39 @@ class Puppeteer::ExecutionContext
211
216
  # return createJSHandle(this, response.objects);
212
217
  # }
213
218
 
214
- # /**
215
- # * @param {Puppeteer.ElementHandle} elementHandle
216
- # * @return {Promise<Puppeteer.ElementHandle>}
217
- # */
218
- # async _adoptElementHandle(elementHandle) {
219
- # assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
220
- # assert(this._world, 'Cannot adopt handle without DOMWorld');
221
- # const nodeInfo = await this._client.send('DOM.describeNode', {
222
- # objectId: elementHandle._remoteObject.objectId,
223
- # });
224
- # const {object} = await this._client.send('DOM.resolveNode', {
225
- # backendNodeId: nodeInfo.node.backendNodeId,
226
- # executionContextId: this._contextId,
227
- # });
228
- # return /** @type {Puppeteer.ElementHandle}*/(createJSHandle(this, object));
229
- # }
219
+ # @param backend_node_id [Integer]
220
+ # @return [Puppeteer::ElementHandle]
221
+ def adopt_backend_node_id(backend_node_id)
222
+ response = @client.send_message('DOM.resolveNode',
223
+ backendNodeId: backend_node_id,
224
+ executionContextId: @context_id,
225
+ )
226
+ Puppeteer::JSHandle.create(
227
+ context: self,
228
+ remote_object: Puppeteer::RemoteObject.new(response["object"]),
229
+ )
230
+ end
231
+
232
+ # @param element_handle [Puppeteer::ElementHandle]
233
+ # @return [Puppeteer::ElementHandle]
234
+ def adopt_element_handle(element_handle)
235
+ if element_handle.execution_context == self
236
+ raise ArgumentError.new('Cannot adopt handle that already belongs to this execution context')
237
+ end
238
+
239
+ unless @world
240
+ raise 'Cannot adopt handle without DOMWorld'
241
+ end
242
+
243
+ node_info = element_handle.remote_object.node_info(@client)
244
+ response = @client.send_message('DOM.resolveNode',
245
+ backendNodeId: node_info["node"]["backendNodeId"],
246
+ executionContextId: @context_id,
247
+ )
248
+
249
+ Puppeteer::JSHandle.create(
250
+ context: self,
251
+ remote_object: Puppeteer::RemoteObject.new(response["object"]),
252
+ )
253
+ end
230
254
  end
@@ -0,0 +1,29 @@
1
+ class Puppeteer::FileChooser
2
+ # @param element [Puppeteer::ElementHandle]
3
+ # @param event [Hash]
4
+ def initialize(element, event)
5
+ @element = element
6
+ @multiple = event['mode'] != 'selectSingle'
7
+ @handled = false
8
+ end
9
+
10
+ def multiple?
11
+ @multiple
12
+ end
13
+
14
+ # @param file_paths [Array<String>]
15
+ def accept(file_paths)
16
+ if @handled
17
+ raise 'Cannot accept FileChooser which is already handled!'
18
+ end
19
+ @handled = true
20
+ @element.upload_file(*file_paths)
21
+ end
22
+
23
+ def cancel
24
+ if @handled
25
+ raise 'Cannot cancel FileChooser which is already handled!'
26
+ end
27
+ @handled = true
28
+ end
29
+ end
@@ -143,8 +143,10 @@ class Puppeteer::Frame
143
143
  @main_world.add_style_tag(style_tag)
144
144
  end
145
145
 
146
- # @param {string} selector
147
- # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
146
+ # @param selector [String]
147
+ # @param delay [Number]
148
+ # @param button [String] "left"|"right"|"middle"
149
+ # @param click_count [Number]
148
150
  def click(selector, delay: nil, button: nil, click_count: nil)
149
151
  @secondary_world.click(selector, delay: delay, button: button, click_count: click_count)
150
152
  end
@@ -171,11 +173,11 @@ class Puppeteer::Frame
171
173
  @secondary_world.tap(selector)
172
174
  end
173
175
 
174
- # @param {string} selector
175
- # @param {string} text
176
- # @param {{delay: (number|undefined)}=} options
177
- def type(selector, text, delay: nil)
178
- @main_world.type(selector, text, delay: delay)
176
+ # @param selector [String]
177
+ # @param text [String]
178
+ # @param delay [Number]
179
+ def type_text(selector, text, delay: nil)
180
+ @main_world.type_text(selector, text, delay: delay)
179
181
  end
180
182
 
181
183
  # /**
@@ -200,9 +202,10 @@ class Puppeteer::Frame
200
202
  # return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
201
203
  # }
202
204
 
203
- # @param {string} selector
204
- # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
205
- # @return {!Promise<?Puppeteer.ElementHandle>}
205
+ # @param selector [String]
206
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
207
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
208
+ # @param timeout [Integer]
206
209
  def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
207
210
  handle = @secondary_world.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
208
211
  if !handle
@@ -214,9 +217,10 @@ class Puppeteer::Frame
214
217
  result
215
218
  end
216
219
 
217
- # @param {string} xpath
218
- # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
219
- # @return {!Promise<?Puppeteer.ElementHandle>}
220
+ # @param xpath [String]
221
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
222
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
223
+ # @param timeout [Integer]
220
224
  def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
221
225
  handle = @secondary_world.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
222
226
  if !handle
@@ -246,6 +250,10 @@ class Puppeteer::Frame
246
250
  # TODO(lushnikov): remove this once requestInterception has loaderId exposed.
247
251
  @navigation_url = frame_payload['url']
248
252
  @url = frame_payload['url']
253
+
254
+ # Ensure loaderId updated.
255
+ # The order of [Page.lifecycleEvent name="init"] and [Page.frameNavigated] is random... for some reason...
256
+ @loader_id = frame_payload['loaderId']
249
257
  end
250
258
 
251
259
  # @param url [String]
@@ -268,8 +276,8 @@ class Puppeteer::Frame
268
276
 
269
277
  def detach
270
278
  @detached = true
271
- # this._mainWorld._detach();
272
- # this._secondaryWorld._detach();
279
+ @main_world.detach
280
+ @secondary_world.detach
273
281
  if @parent_frame
274
282
  @parent_frame._child_frames.delete(self)
275
283
  end
@@ -206,12 +206,12 @@ class Puppeteer::FrameManager
206
206
  # @param {string} frameId
207
207
  # @param {?string} parentFrameId
208
208
  def handle_frame_attached(frame_id, parent_frame_id)
209
- return if @frames.has_key?[frame_id]
209
+ return if @frames.has_key?(frame_id)
210
210
  if !parent_frame_id
211
211
  raise ArgymentError.new('parent_frame_id must not be nil')
212
212
  end
213
213
  parent_frame = @frames[parent_frame_id]
214
- frame = Frame.new(self, @client, parent_frame, frame_id)
214
+ frame = Puppeteer::Frame.new(self, @client, parent_frame, frame_id)
215
215
  @frames[frame_id] = frame
216
216
 
217
217
  emit_event 'Events.FrameManager.FrameAttached', frame
@@ -219,7 +219,7 @@ class Puppeteer::FrameManager
219
219
 
220
220
  # @param frame_payload [Hash]
221
221
  def handle_frame_navigated(frame_payload)
222
- is_main_frame = !frame_payload['parent_id']
222
+ is_main_frame = !frame_payload['parentId']
223
223
  frame =
224
224
  if is_main_frame
225
225
  @main_frame
@@ -285,8 +285,6 @@ class Puppeteer::FrameManager
285
285
  frame.navigated_within_document(url)
286
286
  emit_event 'Events.FrameManager.FrameNavigatedWithinDocument', frame
287
287
  emit_event 'Events.FrameManager.FrameNavigated', frame
288
- handle_frame_manager_frame_navigated_within_document(frame)
289
- handle_frame_manager_frame_navigated(frame)
290
288
  end
291
289
 
292
290
  # @param frame_id [String]
@@ -332,12 +330,12 @@ class Puppeteer::FrameManager
332
330
  @context_id_to_context.delete(execution_context_id)
333
331
  @context_id_created.delete(execution_context_id)
334
332
  if context.world
335
- context.world.context = nil
333
+ context.world.delete_context(execution_context_id)
336
334
  end
337
335
  end
338
336
 
339
337
  def handle_execution_contexts_cleared
340
- # executionContextCleared is often notified after executionContextCreated.
338
+ # executionContextsCleared is often notified after executionContextCreated.
341
339
  # D, [2020-04-06T01:47:03.101227 #13823] DEBUG -- : RECV << {"method"=>"Runtime.executionContextCreated", "params"=>{"context"=>{"id"=>5, "origin"=>"https://github.com", "name"=>"", "auxData"=>{"isDefault"=>true, "type"=>"default", "frameId"=>"71C347B70848B89DDDEFAA8AB5B0BC92"}}}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
342
340
  # D, [2020-04-06T01:47:03.101439 #13823] DEBUG -- : RECV << {"method"=>"Page.frameNavigated", "params"=>{"frame"=>{"id"=>"71C347B70848B89DDDEFAA8AB5B0BC92", "loaderId"=>"80338225D035AC96BAE8F6D4E81C7D51", "url"=>"https://github.com/search?q=puppeteer", "securityOrigin"=>"https://github.com", "mimeType"=>"text/html"}}, "sessionId"=>"53F088EED260C28001D26A019F95D9E3"}
343
341
  # D, [2020-04-06T01:47:03.101325 #13823] DEBUG -- : RECV << {"method"=>"Target.targetInfoChanged", "params"=>{"targetInfo"=>{"targetId"=>"71C347B70848B89DDDEFAA8AB5B0BC92", "type"=>"page", "title"=>"https://github.com/search?q=puppeteer", "url"=>"https://github.com/search?q=puppeteer", "attached"=>true, "browserContextId"=>"AF37BC660284CE1552B4ECB147BE9305"}}}
@@ -346,9 +344,9 @@ class Puppeteer::FrameManager
346
344
  # To avoid the problem, just skip recent created ids.
347
345
  now = Time.now
348
346
  context_ids_to_skip = @context_id_created.select { |k, v| now - v < 1 }.keys
349
- @context_id_to_context.reject { |k, v| context_ids_to_skip.include?(k) }.each_value do |context|
347
+ @context_id_to_context.reject { |k, v| context_ids_to_skip.include?(k) }.each do |execution_context_id, context|
350
348
  if context.world
351
- context.world.context = nil
349
+ context.world.delete_context(execution_context_id)
352
350
  end
353
351
  end
354
352
  @context_id_to_context.select! { |k, v| context_ids_to_skip.include?(k) }
@@ -89,8 +89,9 @@ class Puppeteer::JSHandle
89
89
  response['result'].each_with_object({}) do |prop, h|
90
90
  next unless prop['enumerable']
91
91
  h[prop['name']] = Puppeteer::JSHandle.create(
92
- context: @context,
93
- remote_object: Puppeteer::RemoteObject.new(prop['value']))
92
+ context: @context,
93
+ remote_object: Puppeteer::RemoteObject.new(prop['value']),
94
+ )
94
95
  end
95
96
  end
96
97
 
@@ -116,7 +117,6 @@ class Puppeteer::JSHandle
116
117
  nil
117
118
  end
118
119
 
119
- # @return [Future]
120
120
  def dispose
121
121
  return if @disposed
122
122
 
@@ -178,6 +178,6 @@ class Puppeteer::Keyboard
178
178
  # @param key [String]
179
179
  # @return [Future]
180
180
  async def async_press(key, delay: nil)
181
- press(key, delay)
181
+ press(key, delay: delay)
182
182
  end
183
183
  end
@@ -41,10 +41,10 @@ class Puppeteer::Keyboard
41
41
  'Backspace': KeyDefinition.new({ 'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace' }),
42
42
  'Tab': KeyDefinition.new({ 'keyCode': 9, 'code': 'Tab', 'key': 'Tab' }),
43
43
  'Numpad5': KeyDefinition.new({ 'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3 }),
44
- 'NumpadEnter': KeyDefinition.new({ 'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3 }),
45
- 'Enter': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' }),
46
- '\r': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' }),
47
- '\n': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' }),
44
+ 'NumpadEnter': KeyDefinition.new({ 'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': "\r", 'location': 3 }),
45
+ 'Enter': KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': "\r" }),
46
+ "\r": KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': "\r" }),
47
+ "\n": KeyDefinition.new({ 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': "\r" }),
48
48
  'ShiftLeft': KeyDefinition.new({ 'keyCode': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1 }),
49
49
  'ShiftRight': KeyDefinition.new({ 'keyCode': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2 }),
50
50
  'ControlLeft': KeyDefinition.new({ 'keyCode': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1 }),
@@ -6,7 +6,6 @@ require_relative './launcher/launch_options'
6
6
 
7
7
  # https://github.com/puppeteer/puppeteer/blob/master/lib/Launcher.js
8
8
  module Puppeteer::Launcher
9
-
10
9
  # @param {string} projectRoot
11
10
  # @param {string} preferredRevision
12
11
  # @param {boolean} isPuppeteerCore
@@ -149,8 +149,54 @@ module Puppeteer::Launcher
149
149
 
150
150
  # @param {!(Launcher.BrowserOptions & {browserWSEndpoint?: string, browserURL?: string, transport?: !Puppeteer.ConnectionTransport})} options
151
151
  # @return {!Promise<!Browser>}
152
- def connect(options)
153
- raise NotImplementedError.new('Puppeteer.connect is not implemented yet')
152
+ def connect(options = {})
153
+ @browser_options = BrowserOptions.new(options)
154
+ browser_ws_endpoint = options[:browser_ws_endpoint]
155
+ browser_url = options[:browser_url]
156
+ transport = options[:transport]
157
+
158
+ connection =
159
+ if browser_ws_endpoint && browser_url.nil? && transport.nil?
160
+ connect_with_browser_ws_endpoint(browser_ws_endpoint)
161
+ elsif browser_ws_endpoint.nil? && browser_url && transport.nil?
162
+ connect_with_browser_url(browser_url)
163
+ elsif browser_ws_endpoint.nil? && browser_url.nil? && transport
164
+ connect_with_transport(transport)
165
+ else
166
+ raise ArgumentError.new("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect")
167
+ end
168
+
169
+ result = connection.send_message('Target.getBrowserContexts')
170
+ browser_context_ids = result['browserContextIds']
171
+
172
+ Puppeteer::Browser.create(
173
+ connection: connection,
174
+ context_ids: browser_context_ids,
175
+ ignore_https_errors: @browser_options.ignore_https_errors?,
176
+ default_viewport: @browser_options.default_viewport,
177
+ process: nil,
178
+ close_callback: -> { connection.send_message('Browser.close') },
179
+ )
180
+ end
181
+
182
+ # @return [Puppeteer::Connection]
183
+ private def connect_with_browser_ws_endpoint(browser_ws_endpoint)
184
+ transport = Puppeteer::WebSocketTransport.create(browser_ws_endpoint)
185
+ Puppeteer::Connection.new(browser_ws_endpoint, transport, @browser_options.slow_mo)
186
+ end
187
+
188
+ # @return [Puppeteer::Connection]
189
+ private def connect_with_browser_url(browser_url)
190
+ raise NotImplementedError.new('Puppeteer#connect with browserUrl is not implemented yet.')
191
+ # const connectionURL = await getWSEndpoint(browserURL);
192
+ # const connectionTransport = await WebSocketTransport.create(
193
+ # connectionURL
194
+ # );
195
+ end
196
+
197
+ # @return [Puppeteer::Connection]
198
+ private def connect_with_transport(transport)
199
+ Puppeteer::Connection.new('', transport, @browser_options.slow_mo)
154
200
  end
155
201
 
156
202
  # @return {string}
@@ -50,6 +50,11 @@ class Puppeteer::LifecycleWatcher
50
50
  end
51
51
  end
52
52
 
53
+ class FrameDetachedError < StandardError
54
+ def initialize
55
+ super('Navigating frame was detached')
56
+ end
57
+ end
53
58
  class TerminatedError < StandardError; end
54
59
 
55
60
  # * @param {!Puppeteer.FrameManager} frameManager
@@ -68,7 +73,7 @@ class Puppeteer::LifecycleWatcher
68
73
  terminate(TerminatedError.new('Navigation failed because browser has disconnected!'))
69
74
  end
70
75
  @listener_ids['frame_manager'] = [
71
- @frame_manager.add_event_listener('Events.FrameManager.LifecycleEvent') do |frame|
76
+ @frame_manager.add_event_listener('Events.FrameManager.LifecycleEvent') do |_|
72
77
  check_lifecycle_complete
73
78
  end,
74
79
  @frame_manager.add_event_listener('Events.FrameManager.FrameNavigatedWithinDocument', &method(:navigated_within_document)),
@@ -92,7 +97,7 @@ class Puppeteer::LifecycleWatcher
92
97
  # @param frame [Puppeteer::Frame]
93
98
  def handle_frame_detached(frame)
94
99
  if @frame == frame
95
- # this._terminationCallback.call(null, new Error('Navigating frame was detached'));
100
+ @termination_promise.reject(FrameDetachedError.new)
96
101
  return
97
102
  end
98
103
  check_lifecycle_complete
@@ -144,10 +149,10 @@ class Puppeteer::LifecycleWatcher
144
149
  if @frame.loader_id == @initial_loader_id && !@has_same_document_navigation
145
150
  return
146
151
  end
147
- if @has_same_document_navigation
152
+ if @has_same_document_navigation && @same_document_navigation_promise.pending?
148
153
  @same_document_navigation_promise.fulfill(true)
149
154
  end
150
- if @frame.loader_id != @initial_loader_id
155
+ if @frame.loader_id != @initial_loader_id && @new_document_navigation_promise.pending?
151
156
  @new_document_navigation_promise.fulfill(true)
152
157
  end
153
158
  end
@@ -37,8 +37,8 @@ class Puppeteer::Mouse
37
37
  @client.send_message('Input.dispatchMouseEvent',
38
38
  type: 'mouseMoved',
39
39
  button: @button,
40
- x: from_x + (@x - from_x) * n / steps,
41
- y: from_y + (@y - from_y) * n / steps,
40
+ x: from_x + (@x - from_x) * n / move_steps,
41
+ y: from_y + (@y - from_y) * n / move_steps,
42
42
  modifiers: @keyboard.modifiers,
43
43
  )
44
44
  end
@@ -56,16 +56,19 @@ class Puppeteer::Mouse
56
56
  # @param y [number]
57
57
  # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
58
58
  def click(x, y, delay: nil, button: nil, click_count: nil)
59
+ # await_all(async_move, async_down, async_up) often breaks the order of CDP commands.
60
+ # D, [2020-04-15T17:09:47.895895 #88683] DEBUG -- : RECV << {"id"=>23, "result"=>{"layoutViewport"=>{"pageX"=>0, "pageY"=>1, "clientWidth"=>375, "clientHeight"=>667}, "visualViewport"=>{"offsetX"=>0, "offsetY"=>0, "pageX"=>0, "pageY"=>1, "clientWidth"=>375, "clientHeight"=>667, "scale"=>1, "zoom"=>1}, "contentSize"=>{"x"=>0, "y"=>0, "width"=>375, "height"=>2007}}, "sessionId"=>"0B09EA5E18DEE403E525B3E7FCD7E225"}
61
+ # D, [2020-04-15T17:09:47.898422 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mouseReleased","button":"left","x":0,"y":0,"modifiers":0,"clickCount":1},"id":24}
62
+ # D, [2020-04-15T17:09:47.899711 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mousePressed","button":"left","x":0,"y":0,"modifiers":0,"clickCount":1},"id":25}
63
+ # D, [2020-04-15T17:09:47.900237 #88683] DEBUG -- : SEND >> {"sessionId":"0B09EA5E18DEE403E525B3E7FCD7E225","method":"Input.dispatchMouseEvent","params":{"type":"mouseMoved","button":"left","x":187,"y":283,"modifiers":0},"id":26}
64
+ # So we execute move in advance.
65
+ move(x, y)
59
66
  if delay
60
- await_all(
61
- async_move(x, y),
62
- async_down(button: button, click_count: click_count),
63
- )
67
+ down(button: button, click_count: click_count)
64
68
  sleep(delay / 1000.0)
65
69
  up(button: button, click_count: click_count)
66
70
  else
67
71
  await_all(
68
- async_move(x, y),
69
72
  async_down(button: button, click_count: click_count),
70
73
  async_up(button: button, click_count: click_count),
71
74
  )
@@ -48,10 +48,10 @@ class Puppeteer::Page
48
48
  if event['targetInfo']['type'] != 'worker'
49
49
  # If we don't detach from service workers, they will never die.
50
50
  await @client.send_message('Target.detachFromTarget', sessionId: event['sessionId'])
51
- return
51
+ next
52
52
  end
53
53
 
54
- session = Puppeteer::Connection.from_session(@client).session(event['sessionId'])
54
+ session = Puppeteer::Connection.from_session(@client).session(event['sessionId']) # rubocop:disable Lint/UselessAssignment
55
55
  # const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
56
56
  # this._workers.set(event.sessionId, worker);
57
57
  # this.emit(Events.Page.WorkerCreated, worker);
@@ -59,7 +59,7 @@ class Puppeteer::Page
59
59
  @client.on_event 'Target.detachedFromTarget' do |event|
60
60
  session_id = event['sessionId']
61
61
  worker = @workers[session_id]
62
- return unless worker
62
+ next unless worker
63
63
 
64
64
  emit_event('Events.Page.WorkerDestroyed', worker)
65
65
  @workers.delete(session_id)
@@ -88,8 +88,8 @@ class Puppeteer::Page
88
88
  network_manager.on_event 'Events.NetworkManager.RequestFinished' do |event|
89
89
  emit_event 'Events.Page.RequestFinished', event
90
90
  end
91
- # this._fileChooserInterceptionIsDisabled = false;
92
- # this._fileChooserInterceptors = new Set();
91
+ @file_chooser_interception_is_disabled = false
92
+ @file_chooser_interceptors = Set.new
93
93
 
94
94
  @client.on_event 'Page.domContentEventFired' do |event|
95
95
  emit_event 'Events.Page.DOMContentLoaded'
@@ -106,7 +106,9 @@ class Puppeteer::Page
106
106
  @client.on_event 'Log.entryAdded' do |event|
107
107
  handle_log_entry_added(event)
108
108
  end
109
- # client.on('Page.fileChooserOpened', event => this._onFileChooser(event));
109
+ @client.on_event 'Page.fileChooserOpened' do |event|
110
+ handle_file_chooser(event)
111
+ end
110
112
  @target.on_close do
111
113
  emit_event 'Events.Page.Close'
112
114
  @closed = true
@@ -122,41 +124,53 @@ class Puppeteer::Page
122
124
  )
123
125
  end
124
126
 
125
- # /**
126
- # * @param {!Protocol.Page.fileChooserOpenedPayload} event
127
- # */
128
- # async _onFileChooser(event) {
129
- # if (!this._fileChooserInterceptors.size)
130
- # return;
131
- # const frame = this._frameManager.frame(event.frameId);
132
- # const context = await frame.executionContext();
133
- # const element = await context._adoptBackendNodeId(event.backendNodeId);
134
- # const interceptors = Array.from(this._fileChooserInterceptors);
135
- # this._fileChooserInterceptors.clear();
136
- # const fileChooser = new FileChooser(this._client, element, event);
137
- # for (const interceptor of interceptors)
138
- # interceptor.call(null, fileChooser);
139
- # }
127
+ def handle_file_chooser(event)
128
+ return if @file_chooser_interceptors.empty?
129
+
130
+ frame = @frame_manager.frame(event['frameId'])
131
+ context = frame.execution_context
132
+ element = context.adopt_backend_node_id(event['backendNodeId'])
133
+ interceptors = @file_chooser_interceptors.to_a
134
+ @file_chooser_interceptors.clear
135
+ file_chooser = Puppeteer::FileChooser.new(element, event)
136
+ interceptors.each do |promise|
137
+ promise.fulfill(file_chooser)
138
+ end
139
+ end
140
140
 
141
- # /**
142
- # * @param {!{timeout?: number}=} options
143
- # * @return !Promise<!FileChooser>}
144
- # */
145
- # async waitForFileChooser(options = {}) {
146
- # if (!this._fileChooserInterceptors.size)
147
- # await this._client.send('Page.setInterceptFileChooserDialog', {enabled: true});
141
+ class FileChooserTimeoutError < StandardError
142
+ def initialize(timeout:)
143
+ super("waiting for filechooser failed: timeout #{timeout}ms exceeded")
144
+ end
145
+ end
148
146
 
149
- # const {
150
- # timeout = this._timeoutSettings.timeout(),
151
- # } = options;
152
- # let callback;
153
- # const promise = new Promise(x => callback = x);
154
- # this._fileChooserInterceptors.add(callback);
155
- # return helper.waitWithTimeout(promise, 'waiting for file chooser', timeout).catch(e => {
156
- # this._fileChooserInterceptors.delete(callback);
157
- # throw e;
158
- # });
159
- # }
147
+ # @param timeout [Integer]
148
+ # @return [Puppeteer::FileChooser]
149
+ def wait_for_file_chooser(timeout: nil)
150
+ if @file_chooser_interceptors.empty?
151
+ @client.send_message('Page.setInterceptFileChooserDialog', enabled: true)
152
+ end
153
+
154
+ option_timeout = timeout || @timeout_settings.timeout
155
+ promise = resolvable_future
156
+ @file_chooser_interceptors << promise
157
+
158
+ begin
159
+ Timeout.timeout(option_timeout / 1000.0) do
160
+ promise.value!
161
+ end
162
+ rescue Timeout::Error
163
+ raise FileChooserTimeoutError.new(timeout: option_timeout)
164
+ ensure
165
+ @file_chooser_interceptors.delete(promise)
166
+ end
167
+ end
168
+
169
+ # @param timeout [Integer]
170
+ # @return [Future<Puppeteer::FileChooser>]
171
+ async def async_wait_for_file_chooser(timeout: nil)
172
+ wait_for_file_chooser(timeout: timeout)
173
+ end
160
174
 
161
175
 
162
176
  # /**
@@ -173,7 +187,7 @@ class Puppeteer::Page
173
187
  # await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy});
174
188
  # }
175
189
 
176
- attr_reader :target
190
+ attr_reader :javascript_enabled, :target
177
191
 
178
192
  def browser
179
193
  @target.browser
@@ -204,7 +218,9 @@ class Puppeteer::Page
204
218
  end
205
219
  if source != 'worker'
206
220
  console_message_location = Puppeteer::ConsoleMessage::Location.new(
207
- url: url, line_number: line_number)
221
+ url: url,
222
+ line_number: line_number,
223
+ )
208
224
  emit_event('Events.Page.Console',
209
225
  Puppeteer::ConsoleMessage.new(level, text, [], console_message_location))
210
226
  end
@@ -273,23 +289,37 @@ class Puppeteer::Page
273
289
  end
274
290
 
275
291
  # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
276
- # @param {string} selector
277
- # @param {Function|string} pageFunction
278
- # @param {!Array<*>} args
279
- # @return {!Promise<(!Object|undefined)>}
292
+ # @param selector [String]
293
+ # @param page_function [String]
294
+ # @return [Object]
280
295
  def Seval(selector, page_function, *args)
281
296
  main_frame.Seval(selector, page_function, *args)
282
297
  end
283
298
 
299
+ # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
300
+ # @param selector [String]
301
+ # @param page_function [String]
302
+ # @return [Future]
303
+ async def async_Seval(selector, page_function, *args)
304
+ Seval(selector, page_function, *args)
305
+ end
306
+
284
307
  # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
285
- # @param {string} selector
286
- # @param {Function|string} pageFunction
287
- # @param {!Array<*>} args
288
- # @return {!Promise<(!Object|undefined)>}
308
+ # @param selector [String]
309
+ # @param page_function [String]
310
+ # @return [Object]
289
311
  def SSeval(selector, page_function, *args)
290
312
  main_frame.SSeval(selector, page_function, *args)
291
313
  end
292
314
 
315
+ # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
316
+ # @param selector [String]
317
+ # @param page_function [String]
318
+ # @return [Future]
319
+ async def async_SSeval(selector, page_function, *args)
320
+ SSeval(selector, page_function, *args)
321
+ end
322
+
293
323
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
294
324
  # @param {string} expression
295
325
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
@@ -608,14 +638,14 @@ class Puppeteer::Page
608
638
  main_frame.goto(url, referer: referer, timeout: timeout, wait_until: wait_until)
609
639
  end
610
640
 
611
- # @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
612
- # @return {!Promise<?Puppeteer.Response>}
641
+ # @param timeout [number|nil]
642
+ # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
643
+ # @return [Puppeteer::Response]
613
644
  def reload(timeout: nil, wait_until: nil)
614
- # const [response] = await Promise.all([
615
- # this.waitForNavigation(options),
616
- # this._client.send('Page.reload')
617
- # ]);
618
- # return response;
645
+ await_all(
646
+ async_wait_for_navigation(timeout: timeout, wait_until: wait_until),
647
+ @client.async_send_message('Page.reload'),
648
+ ).first
619
649
  end
620
650
 
621
651
  # @param timeout [number|nil]
@@ -932,12 +962,23 @@ class Puppeteer::Page
932
962
 
933
963
  attr_reader :mouse
934
964
 
935
- # @param {string} selector
936
- # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
965
+ # @param selector [String]
966
+ # @param delay [Number]
967
+ # @param button [String] "left"|"right"|"middle"
968
+ # @param click_count [Number]
937
969
  def click(selector, delay: nil, button: nil, click_count: nil)
938
970
  main_frame.click(selector, delay: delay, button: button, click_count: click_count)
939
971
  end
940
972
 
973
+ # @param selector [String]
974
+ # @param delay [Number]
975
+ # @param button [String] "left"|"right"|"middle"
976
+ # @param click_count [Number]
977
+ # @return [Future]
978
+ async def async_click(selector, delay: nil, button: nil, click_count: nil)
979
+ click(selector, delay: delay, button: button, click_count: click_count)
980
+ end
981
+
941
982
  # @param {string} selector
942
983
  def focus(selector)
943
984
  main_frame.focus(selector)
@@ -955,16 +996,21 @@ class Puppeteer::Page
955
996
  main_frame.select(selector, *values)
956
997
  end
957
998
 
958
- # @param {string} selector
999
+ # @param selector [String]
959
1000
  def tap(selector)
960
1001
  main_frame.tap(selector)
961
1002
  end
962
1003
 
963
- # @param {string} selector
964
- # @param {string} text
965
- # @param {{delay: (number|undefined)}=} options
966
- def type(selector, text, delay: nil)
967
- main_frame.type(selector, text, delay: delay)
1004
+ # @param selector [String]
1005
+ async def async_tap(selector)
1006
+ tap(selector)
1007
+ end
1008
+
1009
+ # @param selector [String]
1010
+ # @param text [String]
1011
+ # @param delay [Number]
1012
+ def type_text(selector, text, delay: nil)
1013
+ main_frame.type_text(selector, text, delay: delay)
968
1014
  end
969
1015
 
970
1016
  # /**
@@ -977,20 +1023,38 @@ class Puppeteer::Page
977
1023
  # return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
978
1024
  # }
979
1025
 
980
- # @param {string} selector
981
- # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
982
- # @return {!Promise<?Puppeteer.ElementHandle>}
1026
+ # @param selector [String]
1027
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
1028
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
1029
+ # @param timeout [Integer]
983
1030
  def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
984
1031
  main_frame.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
985
1032
  end
986
1033
 
987
- # @param {string} xpath
988
- # @param {!{visible?: boolean, hidden?: boolean, timeout?: number}=} options
989
- # @return {!Promise<?Puppeteer.ElementHandle>}
1034
+ # @param selector [String]
1035
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
1036
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
1037
+ # @param timeout [Integer]
1038
+ async def async_wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
1039
+ wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
1040
+ end
1041
+
1042
+ # @param xpath [String]
1043
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
1044
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
1045
+ # @param timeout [Integer]
990
1046
  def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
991
1047
  main_frame.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
992
1048
  end
993
1049
 
1050
+ # @param xpath [String]
1051
+ # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
1052
+ # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
1053
+ # @param timeout [Integer]
1054
+ async def async_wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
1055
+ wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
1056
+ end
1057
+
994
1058
  # @param {Function|string} pageFunction
995
1059
  # @param {!{polling?: string|number, timeout?: number}=} options
996
1060
  # @param {!Array<*>} args