puppeteer-ruby 0.0.12 → 0.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -14
  3. data/.github/workflows/docs.yml +45 -0
  4. data/.github/workflows/reviewdog.yml +15 -0
  5. data/README.md +52 -1
  6. data/lib/puppeteer.rb +10 -2
  7. data/lib/puppeteer/browser.rb +53 -34
  8. data/lib/puppeteer/browser_context.rb +35 -5
  9. data/lib/puppeteer/cdp_session.rb +3 -19
  10. data/lib/puppeteer/concurrent_ruby_utils.rb +6 -0
  11. data/lib/puppeteer/connection.rb +9 -16
  12. data/lib/puppeteer/debug_print.rb +1 -1
  13. data/lib/puppeteer/define_async_method.rb +23 -0
  14. data/lib/puppeteer/dom_world.rb +67 -78
  15. data/lib/puppeteer/element_handle.rb +19 -47
  16. data/lib/puppeteer/emulation_manager.rb +2 -6
  17. data/lib/puppeteer/env.rb +18 -0
  18. data/lib/puppeteer/execution_context.rb +1 -6
  19. data/lib/puppeteer/frame.rb +34 -39
  20. data/lib/puppeteer/frame_manager.rb +7 -25
  21. data/lib/puppeteer/js_handle.rb +3 -12
  22. data/lib/puppeteer/keyboard.rb +9 -29
  23. data/lib/puppeteer/launcher/chrome.rb +9 -7
  24. data/lib/puppeteer/mouse.rb +20 -24
  25. data/lib/puppeteer/network_manager.rb +163 -5
  26. data/lib/puppeteer/page.rb +213 -179
  27. data/lib/puppeteer/page/pdf_options.rb +166 -0
  28. data/lib/puppeteer/remote_object.rb +2 -5
  29. data/lib/puppeteer/request.rb +330 -0
  30. data/lib/puppeteer/response.rb +113 -0
  31. data/lib/puppeteer/touch_screen.rb +2 -7
  32. data/lib/puppeteer/version.rb +1 -1
  33. data/lib/puppeteer/wait_task.rb +2 -4
  34. data/lib/puppeteer/web_socket.rb +7 -0
  35. data/puppeteer-ruby.gemspec +2 -1
  36. metadata +25 -103
  37. data/docs/Puppeteer.html +0 -2338
  38. data/docs/Puppeteer/AsyncAwaitBehavior.html +0 -105
  39. data/docs/Puppeteer/Browser.html +0 -2258
  40. data/docs/Puppeteer/BrowserContext.html +0 -809
  41. data/docs/Puppeteer/BrowserFetcher.html +0 -214
  42. data/docs/Puppeteer/BrowserRunner.html +0 -914
  43. data/docs/Puppeteer/BrowserRunner/BrowserProcess.html +0 -477
  44. data/docs/Puppeteer/CDPSession.html +0 -813
  45. data/docs/Puppeteer/CDPSession/Error.html +0 -124
  46. data/docs/Puppeteer/ConcurrentRubyUtils.html +0 -438
  47. data/docs/Puppeteer/Connection.html +0 -964
  48. data/docs/Puppeteer/Connection/MessageCallback.html +0 -434
  49. data/docs/Puppeteer/Connection/ProtocolError.html +0 -216
  50. data/docs/Puppeteer/Connection/RequestDebugPrinter.html +0 -217
  51. data/docs/Puppeteer/Connection/ResponseDebugPrinter.html +0 -244
  52. data/docs/Puppeteer/ConsoleMessage.html +0 -565
  53. data/docs/Puppeteer/ConsoleMessage/Location.html +0 -433
  54. data/docs/Puppeteer/DOMWorld.html +0 -2219
  55. data/docs/Puppeteer/DOMWorld/DetachedError.html +0 -124
  56. data/docs/Puppeteer/DOMWorld/DocumentEvaluationError.html +0 -124
  57. data/docs/Puppeteer/DebugPrint.html +0 -233
  58. data/docs/Puppeteer/Device.html +0 -470
  59. data/docs/Puppeteer/Devices.html +0 -139
  60. data/docs/Puppeteer/ElementHandle.html +0 -2542
  61. data/docs/Puppeteer/ElementHandle/BoundingBox.html +0 -507
  62. data/docs/Puppeteer/ElementHandle/BoxModel.html +0 -404
  63. data/docs/Puppeteer/ElementHandle/ElementNotFoundError.html +0 -206
  64. data/docs/Puppeteer/ElementHandle/ElementNotVisibleError.html +0 -206
  65. data/docs/Puppeteer/ElementHandle/Point.html +0 -492
  66. data/docs/Puppeteer/ElementHandle/ScrollIntoViewError.html +0 -124
  67. data/docs/Puppeteer/EmulationManager.html +0 -454
  68. data/docs/Puppeteer/EventCallbackable.html +0 -499
  69. data/docs/Puppeteer/EventCallbackable/EventListeners.html +0 -435
  70. data/docs/Puppeteer/ExecutionContext.html +0 -998
  71. data/docs/Puppeteer/ExecutionContext/EvaluationError.html +0 -124
  72. data/docs/Puppeteer/ExecutionContext/JavaScriptExpression.html +0 -357
  73. data/docs/Puppeteer/ExecutionContext/JavaScriptFunction.html +0 -389
  74. data/docs/Puppeteer/FileChooser.html +0 -455
  75. data/docs/Puppeteer/Frame.html +0 -3813
  76. data/docs/Puppeteer/FrameManager.html +0 -2410
  77. data/docs/Puppeteer/FrameManager/NavigationError.html +0 -124
  78. data/docs/Puppeteer/IfPresent.html +0 -222
  79. data/docs/Puppeteer/JSHandle.html +0 -1352
  80. data/docs/Puppeteer/Keyboard.html +0 -1557
  81. data/docs/Puppeteer/Keyboard/KeyDefinition.html +0 -831
  82. data/docs/Puppeteer/Keyboard/KeyDescription.html +0 -603
  83. data/docs/Puppeteer/Launcher.html +0 -237
  84. data/docs/Puppeteer/Launcher/Base.html +0 -385
  85. data/docs/Puppeteer/Launcher/Base/ExecutablePathNotFound.html +0 -124
  86. data/docs/Puppeteer/Launcher/BrowserOptions.html +0 -441
  87. data/docs/Puppeteer/Launcher/Chrome.html +0 -669
  88. data/docs/Puppeteer/Launcher/Chrome/DefaultArgs.html +0 -382
  89. data/docs/Puppeteer/Launcher/ChromeArgOptions.html +0 -531
  90. data/docs/Puppeteer/Launcher/LaunchOptions.html +0 -893
  91. data/docs/Puppeteer/LifecycleWatcher.html +0 -834
  92. data/docs/Puppeteer/LifecycleWatcher/ExpectedLifecycle.html +0 -363
  93. data/docs/Puppeteer/LifecycleWatcher/FrameDetachedError.html +0 -206
  94. data/docs/Puppeteer/LifecycleWatcher/TerminatedError.html +0 -124
  95. data/docs/Puppeteer/Mouse.html +0 -1095
  96. data/docs/Puppeteer/Mouse/Button.html +0 -136
  97. data/docs/Puppeteer/NetworkManager.html +0 -901
  98. data/docs/Puppeteer/NetworkManager/Credentials.html +0 -385
  99. data/docs/Puppeteer/Page.html +0 -6173
  100. data/docs/Puppeteer/Page/FileChooserTimeoutError.html +0 -206
  101. data/docs/Puppeteer/Page/ScreenshotOptions.html +0 -845
  102. data/docs/Puppeteer/Page/ScriptTag.html +0 -555
  103. data/docs/Puppeteer/Page/StyleTag.html +0 -448
  104. data/docs/Puppeteer/Page/TargetCrashedError.html +0 -124
  105. data/docs/Puppeteer/RemoteObject.html +0 -1087
  106. data/docs/Puppeteer/Target.html +0 -1336
  107. data/docs/Puppeteer/Target/InitializeFailure.html +0 -124
  108. data/docs/Puppeteer/Target/TargetInfo.html +0 -729
  109. data/docs/Puppeteer/TimeoutError.html +0 -135
  110. data/docs/Puppeteer/TimeoutSettings.html +0 -496
  111. data/docs/Puppeteer/TouchScreen.html +0 -464
  112. data/docs/Puppeteer/Viewport.html +0 -837
  113. data/docs/Puppeteer/WaitTask.html +0 -637
  114. data/docs/Puppeteer/WaitTask/TerminatedError.html +0 -124
  115. data/docs/Puppeteer/WaitTask/TimeoutError.html +0 -206
  116. data/docs/Puppeteer/WebSocket.html +0 -673
  117. data/docs/Puppeteer/WebSocket/DriverImpl.html +0 -412
  118. data/docs/Puppeteer/WebSocket/TransportError.html +0 -124
  119. data/docs/Puppeteer/WebSocketTransport.html +0 -600
  120. data/docs/Puppeteer/WebSocktTransportError.html +0 -124
  121. data/docs/_index.html +0 -816
  122. data/docs/class_list.html +0 -51
  123. data/docs/css/common.css +0 -1
  124. data/docs/css/full_list.css +0 -58
  125. data/docs/css/style.css +0 -496
  126. data/docs/file.README.html +0 -125
  127. data/docs/file_list.html +0 -56
  128. data/docs/frames.html +0 -17
  129. data/docs/index.html +0 -125
  130. data/docs/js/app.js +0 -314
  131. data/docs/js/full_list.js +0 -216
  132. data/docs/js/jquery.js +0 -4
  133. data/docs/method_list.html +0 -4115
  134. data/docs/top-level-namespace.html +0 -126
  135. data/lib/puppeteer/async_await_behavior.rb +0 -38
@@ -1,5 +1,5 @@
1
1
  class Puppeteer::Mouse
2
- using Puppeteer::AsyncAwaitBehavior
2
+ using Puppeteer::DefineAsyncMethod
3
3
 
4
4
  module Button
5
5
  NONE = 'none'
@@ -44,13 +44,7 @@ class Puppeteer::Mouse
44
44
  end
45
45
  end
46
46
 
47
- # @param x [number]
48
- # @param y [number]
49
- # @param steps [number]
50
- # @return [Future]
51
- async def async_move(x, y, steps: nil)
52
- move(x, y, steps: steps)
53
- end
47
+ define_async_method :async_move
54
48
 
55
49
  # @param x [number]
56
50
  # @param y [number]
@@ -70,13 +64,7 @@ class Puppeteer::Mouse
70
64
  up(button: button, click_count: click_count)
71
65
  end
72
66
 
73
- # @param x [number]
74
- # @param y [number]
75
- # @param {!{delay?: number, button?: "left"|"right"|"middle", clickCount?: number}=} options
76
- # @return [Future]
77
- async def async_click(x, y, delay: nil, button: nil, click_count: nil)
78
- click(x, y, delay: delay, button: button, click_count: click_count)
79
- end
67
+ define_async_method :async_click
80
68
 
81
69
  # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
82
70
  def down(button: nil, click_count: nil)
@@ -91,11 +79,7 @@ class Puppeteer::Mouse
91
79
  )
92
80
  end
93
81
 
94
- # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
95
- # @return [Future]
96
- async def async_down(button: nil, click_count: nil)
97
- down(button: button, click_count: click_count)
98
- end
82
+ define_async_method :async_down
99
83
 
100
84
  # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
101
85
  def up(button: nil, click_count: nil)
@@ -110,9 +94,21 @@ class Puppeteer::Mouse
110
94
  )
111
95
  end
112
96
 
113
- # @param {!{button?: "left"|"right"|"middle", clickCount?: number}=} options
114
- # @return [Future]
115
- async def async_up(button: nil, click_count: nil)
116
- up(button: button, click_count: click_count)
97
+ # Dispatches a `mousewheel` event.
98
+ #
99
+ # @param delta_x [Integer]
100
+ # @param delta_y [Integer]
101
+ def wheel(delta_x: 0, delta_y: 0)
102
+ @client.send_message('Input.dispatchMouseEvent',
103
+ type: 'mouseWheel',
104
+ x: @x,
105
+ y: @y,
106
+ deltaX: delta_x,
107
+ deltaY: delta_y,
108
+ modifiers: @keyboard.modifiers,
109
+ pointerType: 'mouse',
110
+ )
117
111
  end
112
+
113
+ define_async_method :async_up
118
114
  end
@@ -1,5 +1,7 @@
1
1
  class Puppeteer::NetworkManager
2
+ include Puppeteer::DebugPrint
2
3
  include Puppeteer::EventCallbackable
4
+ include Puppeteer::IfPresent
3
5
 
4
6
  class Credentials
5
7
  # @param username [String|NilClass]
@@ -23,19 +25,39 @@ class Puppeteer::NetworkManager
23
25
  @request_id_to_request = {}
24
26
 
25
27
  # @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>}
26
- @request_id_to_request_with_be_sent_event
28
+ @request_id_to_request_with_be_sent_event = {}
27
29
 
28
30
  @extra_http_headers = {}
29
31
 
30
32
  @offline = false
31
33
 
32
- # /** @type {!Set<string>} */
33
- # this._attemptedAuthentications = new Set();
34
+ @attempted_authentications = Set.new
34
35
  @user_request_interception_enabled = false
35
36
  @protocol_request_interception_enabled = false
36
37
  @user_cache_disabled = false
37
- # /** @type {!Map<string, string>} */
38
- # this._requestIdToInterceptionId = new Map();
38
+ @request_id_to_interception_id = {}
39
+
40
+ @client.on_event('Fetch.requestPaused') do |event|
41
+ handle_request_paused(event)
42
+ end
43
+ @client.on_event('Fetch.authRequired') do |event|
44
+ handle_auth_required(event)
45
+ end
46
+ @client.on_event('Network.requestWillBeSent') do |event|
47
+ handle_request_will_be_sent(event)
48
+ end
49
+ @client.on_event('Network.requestServedFromCache') do |event|
50
+ handle_request_served_from_cache(event)
51
+ end
52
+ @client.on_event('Network.responseReceived') do |event|
53
+ handle_response_received(event)
54
+ end
55
+ @client.on_event('Network.loadingFinished') do |event|
56
+ handle_loading_finished(event)
57
+ end
58
+ @client.on_event('Network.loadingFailed') do |event|
59
+ handle_loading_failed(event)
60
+ end
39
61
  end
40
62
 
41
63
  def init
@@ -119,4 +141,140 @@ class Puppeteer::NetworkManager
119
141
  cache_disabled = @user_cache_disabled || @protocol_request_interception_enabled
120
142
  @client.send_message('Network.setCacheDisabled', cacheDisabled: cache_disabled)
121
143
  end
144
+
145
+ private def handle_request_will_be_sent(event)
146
+ # Request interception doesn't happen for data URLs with Network Service.
147
+ if @protocol_request_interception_enabled && !event['request']['url'].start_with?('data:')
148
+ request_id = event['requestId']
149
+ interception_id = @request_id_to_interception_id.delete(request_id)
150
+ if interception_id
151
+ handle_request(event, interception_id)
152
+ else
153
+ @request_id_to_request_with_be_sent_event[request_id] = event
154
+ end
155
+ return
156
+ end
157
+ handle_request(event, nil)
158
+ end
159
+
160
+ private def handle_auth_required(event)
161
+ response = 'Default'
162
+ if @attempted_authentications.include?(event['requestId'])
163
+ response = 'CancelAuth'
164
+ elsif @credentials
165
+ response = 'ProvideCredentials'
166
+ @attempted_authentications << event['requestId']
167
+ end
168
+
169
+ username = @credentials&.username
170
+ password = @credentials&.password
171
+
172
+ begin
173
+ @client.send_message('Fetch.continueWithAuth',
174
+ requestId: event['requestId'],
175
+ authChallengeResponse: {
176
+ response: response,
177
+ username: username,
178
+ password: password,
179
+ },
180
+ )
181
+ rescue => err
182
+ debug_puts(err)
183
+ end
184
+ end
185
+
186
+ private def handle_request_paused(event)
187
+ if !@user_request_interception_enabled && @protocol_request_interception_enabled
188
+ begin
189
+ @client.send_message('Fetch.continueRequest', requestId: event['requestId'])
190
+ rescue => err
191
+ debug_puts(err)
192
+ end
193
+ end
194
+
195
+ request_id = event['networkId']
196
+ interception_id = event['requestId']
197
+ if request_id && (request_will_be_sent_event = @request_id_to_request_with_be_sent_event.delete(request_id))
198
+ handle_request(request_will_be_sent_event, interception_id)
199
+ else
200
+ @request_id_to_interception_id[request_id] = interception_id
201
+ end
202
+ end
203
+
204
+ private def handle_request(event, interception_id)
205
+ redirect_chain = []
206
+ if event['redirectResponse']
207
+ if_present(@request_id_to_request[event['requestId']]) do |request|
208
+ handle_request_redirect(request, event['redirectResponse'])
209
+ redirect_chain = request.internal.redirect_chain
210
+ end
211
+ end
212
+ frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
213
+ request = Puppeteer::Request.new(@client, frame, interception_id, @user_request_interception_enabled, event, redirect_chain)
214
+ @request_id_to_request[event['requestId']] = request
215
+ emit_event 'Events.NetworkManager.Request', request
216
+ end
217
+
218
+ private def handle_request_served_from_cache(event)
219
+ if_present(@request_id_to_request[event['requestId']]) do |request|
220
+ request.internal.from_memory_cache = true
221
+ end
222
+ end
223
+
224
+ # @param request [Puppeteer::Request]
225
+ # @param response_payload [Hash]
226
+ private def handle_request_redirect(request, response_payload)
227
+ response = Puppeteer::Response.new(@client, request, response_payload)
228
+ request.internal.response = response
229
+ request.internal.redirect_chain << request
230
+ response.internal.body_loaded_promise.reject(Puppeteer::Response::Redirected.new)
231
+ @request_id_to_request.delete(request.internal.request_id)
232
+ @attempted_authentications.delete(request.internal.interception_id)
233
+ emit_event 'Events.NetworkManager.Response', response
234
+ emit_event 'Events.NetworkManager.RequestFinished', request
235
+ end
236
+
237
+ # @param event [Hash]
238
+ private def handle_response_received(event)
239
+ request = @request_id_to_request[event['requestId']]
240
+ # FileUpload sends a response without a matching request.
241
+ return unless request
242
+
243
+ response = Puppeteer::Response.new(@client, request, event['response'])
244
+ request.internal.response = response
245
+ emit_event 'Events.NetworkManager.Response', response
246
+ end
247
+
248
+ private def handle_loading_finished(event)
249
+ request = @request_id_to_request[event['requestId']]
250
+ # For certain requestIds we never receive requestWillBeSent event.
251
+ # @see https://crbug.com/750469
252
+ return unless request
253
+
254
+
255
+ # Under certain conditions we never get the Network.responseReceived
256
+ # event from protocol. @see https://crbug.com/883475
257
+ if_present(request.response) do |response|
258
+ response.internal.body_loaded_promise.fulfill(nil)
259
+ end
260
+
261
+ @request_id_to_request.delete(request.internal.request_id)
262
+ @attempted_authentications.delete(request.internal.interception_id)
263
+ emit_event 'Events.NetworkManager.RequestFinished', request
264
+ end
265
+
266
+ private def handle_loading_failed(event)
267
+ request = @request_id_to_request[event['requestId']]
268
+ # For certain requestIds we never receive requestWillBeSent event.
269
+ # @see https://crbug.com/750469
270
+ return unless request
271
+
272
+ request.internal.failure_text = event['errorText']
273
+ if_present(request.response) do |response|
274
+ response.internal.body_loaded_promise.fulfill(nil)
275
+ end
276
+ @request_id_to_request.delete(request.internal.request_id)
277
+ @attempted_authentications.delete(request.internal.interception_id)
278
+ emit_event 'Events.NetworkManager.RequestFailed', request
279
+ end
122
280
  end
@@ -1,11 +1,13 @@
1
1
  require 'base64'
2
+ require "stringio"
2
3
 
4
+ require_relative './page/pdf_options'
3
5
  require_relative './page/screenshot_options'
4
6
 
5
7
  class Puppeteer::Page
6
8
  include Puppeteer::EventCallbackable
7
9
  include Puppeteer::IfPresent
8
- using Puppeteer::AsyncAwaitBehavior
10
+ using Puppeteer::DefineAsyncMethod
9
11
 
10
12
  # @param {!Puppeteer.CDPSession} client
11
13
  # @param {!Puppeteer.Target} target
@@ -205,12 +207,7 @@ class Puppeteer::Page
205
207
  end
206
208
  end
207
209
 
208
- # @param timeout [Integer]
209
- # @return [Future<Puppeteer::FileChooser>]
210
- async def async_wait_for_file_chooser(timeout: nil)
211
- wait_for_file_chooser(timeout: timeout)
212
- end
213
-
210
+ define_async_method :async_wait_for_file_chooser
214
211
 
215
212
  # /**
216
213
  # * @param {!{longitude: number, latitude: number, accuracy: (number|undefined)}} options
@@ -305,6 +302,8 @@ class Puppeteer::Page
305
302
  main_frame.S(selector)
306
303
  end
307
304
 
305
+ define_async_method :async_S
306
+
308
307
  # `$$()` in JavaScript. $ is not allowed to use as a method name in Ruby.
309
308
  # @param {string} selector
310
309
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
@@ -312,6 +311,8 @@ class Puppeteer::Page
312
311
  main_frame.SS(selector)
313
312
  end
314
313
 
314
+ define_async_method :async_SS
315
+
315
316
  # @param {Function|string} pageFunction
316
317
  # @param {!Array<*>} args
317
318
  # @return {!Promise<!Puppeteer.JSHandle>}
@@ -320,6 +321,8 @@ class Puppeteer::Page
320
321
  context.evaluate_handle(page_function, *args)
321
322
  end
322
323
 
324
+ define_async_method :async_evaluate_handle
325
+
323
326
  # @param {!Puppeteer.JSHandle} prototypeHandle
324
327
  # @return {!Promise<!Puppeteer.JSHandle>}
325
328
  def query_objects(prototype_handle)
@@ -335,13 +338,7 @@ class Puppeteer::Page
335
338
  main_frame.Seval(selector, page_function, *args)
336
339
  end
337
340
 
338
- # `$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
339
- # @param selector [String]
340
- # @param page_function [String]
341
- # @return [Future]
342
- async def async_Seval(selector, page_function, *args)
343
- Seval(selector, page_function, *args)
344
- end
341
+ define_async_method :async_Seval
345
342
 
346
343
  # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
347
344
  # @param selector [String]
@@ -351,13 +348,7 @@ class Puppeteer::Page
351
348
  main_frame.SSeval(selector, page_function, *args)
352
349
  end
353
350
 
354
- # `$$eval()` in JavaScript. $ is not allowed to use as a method name in Ruby.
355
- # @param selector [String]
356
- # @param page_function [String]
357
- # @return [Future]
358
- async def async_SSeval(selector, page_function, *args)
359
- SSeval(selector, page_function, *args)
360
- end
351
+ define_async_method :async_SSeval
361
352
 
362
353
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
363
354
  # @param {string} expression
@@ -366,6 +357,8 @@ class Puppeteer::Page
366
357
  main_frame.Sx(expression)
367
358
  end
368
359
 
360
+ define_async_method :async_Sx
361
+
369
362
  # /**
370
363
  # * @param {!Array<string>} urls
371
364
  # * @return {!Promise<!Array<Network.Cookie>>}
@@ -658,13 +651,14 @@ class Puppeteer::Page
658
651
  main_frame.content
659
652
  end
660
653
 
661
- # @param {string} html
662
- # @param {!{timeout?: number, waitUntil?: string|!Array<string>}=} options
654
+ # @param html [String]
655
+ # @param timeout [Integer]
656
+ # @param wait_until [String|Array<String>]
663
657
  def set_content(html, timeout: nil, wait_until: nil)
664
658
  main_frame.set_content(html, timeout: timeout, wait_until: wait_until)
665
659
  end
666
660
 
667
- # @param {string} html
661
+ # @param html [String]
668
662
  def content=(html)
669
663
  main_frame.set_content(html)
670
664
  end
@@ -687,54 +681,111 @@ class Puppeteer::Page
687
681
  ).first
688
682
  end
689
683
 
690
- # @param timeout [number|nil]
691
- # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
692
684
  private def wait_for_navigation(timeout: nil, wait_until: nil)
693
685
  main_frame.send(:wait_for_navigation, timeout: timeout, wait_until: wait_until)
694
686
  end
695
687
 
688
+ # @!method async_wait_for_navigation(timeout: nil, wait_until: nil)
689
+ #
696
690
  # @param timeout [number|nil]
697
691
  # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
698
- # @return [Future]
699
- async def async_wait_for_navigation(timeout: nil, wait_until: nil)
700
- wait_for_navigation(timeout: timeout, wait_until: wait_until)
692
+ define_async_method :async_wait_for_navigation
693
+
694
+ private def wait_for_network_manager_event(event_name, predicate:, timeout:)
695
+ option_timeout = timeout || @timeout_settings.timeout
696
+
697
+ @wait_for_network_manager_event_listener_ids ||= {}
698
+ if_present(@wait_for_network_manager_event_listener_ids[event_name]) do |listener_id|
699
+ @frame_manager.network_manager.remove_event_listener(listener_id)
700
+ end
701
+
702
+ promise = resolvable_future
703
+
704
+ @wait_for_network_manager_event_listener_ids[event_name] =
705
+ @frame_manager.network_manager.add_event_listener(event_name) do |event_target|
706
+ if predicate.call(event_target)
707
+ promise.fulfill(nil)
708
+ end
709
+ end
710
+
711
+ begin
712
+ Timeout.timeout(option_timeout / 1000.0) do
713
+ await_any(promise, session_close_promise)
714
+ end
715
+ rescue Timeout::Error
716
+ raise Puppeteer::TimeoutError.new("waiting for #{event_name} failed: timeout #{timeout}ms exceeded")
717
+ ensure
718
+ @frame_manager.network_manager.remove_event_listener(@wait_for_network_manager_event_listener_ids[event_name])
719
+ end
701
720
  end
702
721
 
703
- # /**
704
- # * @param {(string|Function)} urlOrPredicate
705
- # * @param {!{timeout?: number}=} options
706
- # * @return {!Promise<!Puppeteer.Request>}
707
- # */
708
- # async waitForRequest(urlOrPredicate, options = {}) {
709
- # const {
710
- # timeout = this._timeoutSettings.timeout(),
711
- # } = options;
712
- # return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Request, request => {
713
- # if (helper.isString(urlOrPredicate))
714
- # return (urlOrPredicate === request.url());
715
- # if (typeof urlOrPredicate === 'function')
716
- # return !!(urlOrPredicate(request));
717
- # return false;
718
- # }, timeout, this._sessionClosePromise());
719
- # }
722
+ private def session_close_promise
723
+ @disconnect_promise ||= resolvable_future do |future|
724
+ @client.observe_first('Events.CDPSession.Disconnected') do
725
+ future.reject(Puppeteer::CDPSession::Error.new('Target Closed'))
726
+ end
727
+ end
728
+ end
720
729
 
721
- # /**
722
- # * @param {(string|Function)} urlOrPredicate
723
- # * @param {!{timeout?: number}=} options
724
- # * @return {!Promise<!Puppeteer.Response>}
725
- # */
726
- # async waitForResponse(urlOrPredicate, options = {}) {
727
- # const {
728
- # timeout = this._timeoutSettings.timeout(),
729
- # } = options;
730
- # return helper.waitForEvent(this._frameManager.networkManager(), Events.NetworkManager.Response, response => {
731
- # if (helper.isString(urlOrPredicate))
732
- # return (urlOrPredicate === response.url());
733
- # if (typeof urlOrPredicate === 'function')
734
- # return !!(urlOrPredicate(response));
735
- # return false;
736
- # }, timeout, this._sessionClosePromise());
737
- # }
730
+ private def wait_for_request(url: nil, predicate: nil, timeout: nil)
731
+ if !url && !predicate
732
+ raise ArgumentError.new('url or predicate must be specified')
733
+ end
734
+ if predicate && !predicate.is_a?(Proc)
735
+ raise ArgumentError.new('predicate must be a proc.')
736
+ end
737
+ request_predicate =
738
+ if url
739
+ -> (request) { request.url == url }
740
+ else
741
+ -> (request) { predicate.call(request) }
742
+ end
743
+
744
+ wait_for_network_manager_event('Events.NetworkManager.Request',
745
+ predicate: request_predicate,
746
+ timeout: timeout,
747
+ )
748
+ end
749
+
750
+ # @!method async_wait_for_request(url: nil, predicate: nil, timeout: nil)
751
+ #
752
+ # Waits until request URL matches or request matches the given predicate.
753
+ #
754
+ # Waits until request URL matches
755
+ # wait_for_request(url: 'https://example.com/awesome')
756
+ #
757
+ # Waits until request matches the given predicate
758
+ # wait_for_request(predicate: -> (req){ req.url.start_with?('https://example.com/search') })
759
+ #
760
+ # @param url [String]
761
+ # @param predicate [Proc(Puppeteer::Request -> Boolean)]
762
+ define_async_method :async_wait_for_request
763
+
764
+ private def wait_for_response(url: nil, predicate: nil, timeout: nil)
765
+ if !url && !predicate
766
+ raise ArgumentError.new('url or predicate must be specified')
767
+ end
768
+ if predicate && !predicate.is_a?(Proc)
769
+ raise ArgumentError.new('predicate must be a proc.')
770
+ end
771
+ response_predicate =
772
+ if url
773
+ -> (response) { response.url == url }
774
+ else
775
+ -> (response) { predicate.call(response) }
776
+ end
777
+
778
+ wait_for_network_manager_event('Events.NetworkManager.Response',
779
+ predicate: response_predicate,
780
+ timeout: timeout,
781
+ )
782
+ end
783
+
784
+ # @!method async_wait_for_response(url: nil, predicate: nil, timeout: nil)
785
+ #
786
+ # @param url [String]
787
+ # @param predicate [Proc(Puppeteer::Request -> Boolean)]
788
+ define_async_method :async_wait_for_response
738
789
 
739
790
  # @param timeout [number|nil]
740
791
  # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
@@ -773,20 +824,19 @@ class Puppeteer::Page
773
824
  @client.send_message('Emulation.setScriptExecutionDisabled', value: !enabled)
774
825
  end
775
826
 
776
- # /**
777
- # * @param {boolean} enabled
778
- # */
779
- # async setBypassCSP(enabled) {
780
- # await this._client.send('Page.setBypassCSP', { enabled });
781
- # }
827
+ # @param enabled [Boolean]
828
+ def bypass_csp=(enabled)
829
+ @client.send_message('Page.setBypassCSP', enabled: enabled)
830
+ end
782
831
 
783
- # /**
784
- # * @param {?string} type
785
- # */
786
- # async emulateMediaType(type) {
787
- # assert(type === 'screen' || type === 'print' || type === null, 'Unsupported media type: ' + type);
788
- # await this._client.send('Emulation.setEmulatedMedia', {media: type || ''});
789
- # }
832
+ # @param media_type [String|Symbol|nil] either of (media, print, nil)
833
+ def emulate_media_type(media_type)
834
+ media_type_str = media_type.to_s
835
+ unless ['screen', 'print', ''].include?(media_type_str)
836
+ raise ArgumentError.new("Unsupported media type: #{media_type}")
837
+ end
838
+ @client.send_message('Emulation.setEmulatedMedia', media: media_type_str)
839
+ end
790
840
 
791
841
  # /**
792
842
  # * @param {?Array<MediaFeature>} features
@@ -806,7 +856,7 @@ class Puppeteer::Page
806
856
 
807
857
  # @param timezone_id [String?]
808
858
  def emulate_timezone(timezone_id)
809
- @client.send_message('Emulation.setTimezoneOverride', timezoneId: timezoneId || '')
859
+ @client.send_message('Emulation.setTimezoneOverride', timezoneId: timezone_id || '')
810
860
  rescue => err
811
861
  if err.message.include?('Invalid timezone')
812
862
  raise ArgumentError.new("Invalid timezone ID: #{timezone_id}")
@@ -831,6 +881,8 @@ class Puppeteer::Page
831
881
  main_frame.evaluate(page_function, *args)
832
882
  end
833
883
 
884
+ define_async_method :async_evaluate
885
+
834
886
  # /**
835
887
  # * @param {Function|string} pageFunction
836
888
  # * @param {!Array<*>} args
@@ -845,9 +897,9 @@ class Puppeteer::Page
845
897
  @frame_manager.network_manager.cache_enabled = enabled
846
898
  end
847
899
 
848
- # @return {!Promise<string>}
900
+ # @return [String]
849
901
  def title
850
- @title
902
+ main_frame.title
851
903
  end
852
904
 
853
905
  # /**
@@ -927,71 +979,66 @@ class Puppeteer::Page
927
979
  buffer
928
980
  end
929
981
 
930
- # /**
931
- # * @param {!PDFOptions=} options
932
- # * @return {!Promise<!Buffer>}
933
- # */
934
- # async pdf(options = {}) {
935
- # const {
936
- # scale = 1,
937
- # displayHeaderFooter = false,
938
- # headerTemplate = '',
939
- # footerTemplate = '',
940
- # printBackground = false,
941
- # landscape = false,
942
- # pageRanges = '',
943
- # preferCSSPageSize = false,
944
- # margin = {},
945
- # path = null
946
- # } = options;
947
-
948
- # let paperWidth = 8.5;
949
- # let paperHeight = 11;
950
- # if (options.format) {
951
- # const format = Page.PaperFormats[options.format.toLowerCase()];
952
- # assert(format, 'Unknown paper format: ' + options.format);
953
- # paperWidth = format.width;
954
- # paperHeight = format.height;
955
- # } else {
956
- # paperWidth = convertPrintParameterToInches(options.width) || paperWidth;
957
- # paperHeight = convertPrintParameterToInches(options.height) || paperHeight;
958
- # }
982
+ class ProtocolStreamReader
983
+ def initialize(client:, handle:, path:)
984
+ @client = client
985
+ @handle = handle
986
+ @path = path
987
+ end
959
988
 
960
- # const marginTop = convertPrintParameterToInches(margin.top) || 0;
961
- # const marginLeft = convertPrintParameterToInches(margin.left) || 0;
962
- # const marginBottom = convertPrintParameterToInches(margin.bottom) || 0;
963
- # const marginRight = convertPrintParameterToInches(margin.right) || 0;
964
-
965
- # const result = await this._client.send('Page.printToPDF', {
966
- # transferMode: 'ReturnAsStream',
967
- # landscape,
968
- # displayHeaderFooter,
969
- # headerTemplate,
970
- # footerTemplate,
971
- # printBackground,
972
- # scale,
973
- # paperWidth,
974
- # paperHeight,
975
- # marginTop,
976
- # marginBottom,
977
- # marginLeft,
978
- # marginRight,
979
- # pageRanges,
980
- # preferCSSPageSize
981
- # });
982
- # return await helper.readProtocolStream(this._client, result.stream, path);
983
- # }
989
+ def read
990
+ out = StringIO.new
991
+ File.open(@path, 'w') do |file|
992
+ eof = false
993
+ until eof
994
+ response = @client.send_message('IO.read', handle: @handle)
995
+ eof = response['eof']
996
+ data =
997
+ if response['base64Encoded']
998
+ Base64.decode64(response['data'])
999
+ else
1000
+ response['data']
1001
+ end
1002
+ out.write(data)
1003
+ file.write(data)
1004
+ end
1005
+ end
1006
+ @client.send_message('IO.close', handle: @handle)
1007
+ out.read
1008
+ end
1009
+ end
984
1010
 
985
- # @param {!{runBeforeUnload: (boolean|undefined)}=} options
986
- def close
987
- # assert(!!this._client._connection, 'Protocol error: Connection closed. Most likely the page has been closed.');
988
- # const runBeforeUnload = !!options.runBeforeUnload;
989
- # if (runBeforeUnload) {
990
- # await this._client.send('Page.close');
991
- # } else {
992
- # await this._client._connection.send('Target.closeTarget', { targetId: this._target._targetId });
993
- # await this._target._isClosedPromise;
994
- # }
1011
+ class PrintToPdfIsNotImplementedError < StandardError
1012
+ def initialize
1013
+ super('pdf() is only available in headless mode. See https://github.com/puppeteer/puppeteer/issues/1829')
1014
+ end
1015
+ end
1016
+
1017
+ # @return [String]
1018
+ def pdf(options = {})
1019
+ pdf_options = PDFOptions.new(options)
1020
+ result = @client.send_message('Page.printToPDF', pdf_options.page_print_args)
1021
+ ProtocolStreamReader.new(client: @client, handle: result['stream'], path: pdf_options.path).read
1022
+ rescue => err
1023
+ if err.message.include?('PrintToPDF is not implemented')
1024
+ raise PrintToPdfIsNotImplementedError.new
1025
+ else
1026
+ raise
1027
+ end
1028
+ end
1029
+
1030
+ # @param run_before_unload [Boolean]
1031
+ def close(run_before_unload: false)
1032
+ unless @client.connection
1033
+ raise 'Protocol error: Connection closed. Most likely the page has been closed.'
1034
+ end
1035
+
1036
+ if run_before_unload
1037
+ @client.send_message('Page.close')
1038
+ else
1039
+ @client.connection.send_message('Target.closeTarget', targetId: @target.target_id)
1040
+ await @target.is_closed_promise
1041
+ end
995
1042
  end
996
1043
 
997
1044
  # @return [boolean]
@@ -1009,20 +1056,15 @@ class Puppeteer::Page
1009
1056
  main_frame.click(selector, delay: delay, button: button, click_count: click_count)
1010
1057
  end
1011
1058
 
1012
- # @param selector [String]
1013
- # @param delay [Number]
1014
- # @param button [String] "left"|"right"|"middle"
1015
- # @param click_count [Number]
1016
- # @return [Future]
1017
- async def async_click(selector, delay: nil, button: nil, click_count: nil)
1018
- click(selector, delay: delay, button: button, click_count: click_count)
1019
- end
1059
+ define_async_method :async_click
1020
1060
 
1021
1061
  # @param {string} selector
1022
1062
  def focus(selector)
1023
1063
  main_frame.focus(selector)
1024
1064
  end
1025
1065
 
1066
+ define_async_method :async_focus
1067
+
1026
1068
  # @param {string} selector
1027
1069
  def hover(selector)
1028
1070
  main_frame.hover(selector)
@@ -1035,16 +1077,26 @@ class Puppeteer::Page
1035
1077
  main_frame.select(selector, *values)
1036
1078
  end
1037
1079
 
1038
- # @param selector [String]
1039
- def tap(selector)
1040
- main_frame.tap(selector)
1041
- end
1080
+ define_async_method :async_select
1042
1081
 
1043
1082
  # @param selector [String]
1044
- async def async_tap(selector)
1045
- tap(selector)
1083
+ def tap(selector: nil, &block)
1084
+ # resolves double meaning of tap.
1085
+ if selector.nil? && block
1086
+ # Original usage of Object#tap.
1087
+ #
1088
+ # browser.new_page.tap do |page|
1089
+ # ...
1090
+ # end
1091
+ super(&block)
1092
+ else
1093
+ # Puppeteer's Page#tap.
1094
+ main_frame.tap(selector)
1095
+ end
1046
1096
  end
1047
1097
 
1098
+ define_async_method :async_tap
1099
+
1048
1100
  # @param selector [String]
1049
1101
  # @param text [String]
1050
1102
  # @param delay [Number]
@@ -1052,15 +1104,7 @@ class Puppeteer::Page
1052
1104
  main_frame.type_text(selector, text, delay: delay)
1053
1105
  end
1054
1106
 
1055
- # /**
1056
- # * @param {(string|number|Function)} selectorOrFunctionOrTimeout
1057
- # * @param {!{visible?: boolean, hidden?: boolean, timeout?: number, polling?: string|number}=} options
1058
- # * @param {!Array<*>} args
1059
- # * @return {!Promise<!Puppeteer.JSHandle>}
1060
- # */
1061
- # waitFor(selectorOrFunctionOrTimeout, options = {}, ...args) {
1062
- # return this.mainFrame().waitFor(selectorOrFunctionOrTimeout, options, ...args);
1063
- # }
1107
+ define_async_method :async_type_text
1064
1108
 
1065
1109
  # @param selector [String]
1066
1110
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
@@ -1070,13 +1114,7 @@ class Puppeteer::Page
1070
1114
  main_frame.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
1071
1115
  end
1072
1116
 
1073
- # @param selector [String]
1074
- # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
1075
- # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
1076
- # @param timeout [Integer]
1077
- async def async_wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
1078
- wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout)
1079
- end
1117
+ define_async_method :async_wait_for_selector
1080
1118
 
1081
1119
  # @param xpath [String]
1082
1120
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
@@ -1086,13 +1124,7 @@ class Puppeteer::Page
1086
1124
  main_frame.wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
1087
1125
  end
1088
1126
 
1089
- # @param xpath [String]
1090
- # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
1091
- # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
1092
- # @param timeout [Integer]
1093
- async def async_wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
1094
- wait_for_xpath(xpath, visible: visible, hidden: hidden, timeout: timeout)
1095
- end
1127
+ define_async_method :async_wait_for_xpath
1096
1128
 
1097
1129
  # @param {Function|string} pageFunction
1098
1130
  # @param {!{polling?: string|number, timeout?: number}=} options
@@ -1101,4 +1133,6 @@ class Puppeteer::Page
1101
1133
  def wait_for_function(page_function, options = {}, *args)
1102
1134
  main_frame.wait_for_function(page_function, options, *args)
1103
1135
  end
1136
+
1137
+ define_async_method :async_wait_for_function
1104
1138
  end