puppeteer-ruby 0.37.4 → 0.40.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +35 -1
- data/docs/api_coverage.md +9 -6
- data/lib/puppeteer/aria_query_handler.rb +8 -5
- data/lib/puppeteer/browser_runner.rb +21 -10
- data/lib/puppeteer/cdp_session.rb +5 -0
- data/lib/puppeteer/custom_query_handler.rb +2 -2
- data/lib/puppeteer/dom_world.rb +21 -13
- data/lib/puppeteer/element_handle.rb +54 -0
- data/lib/puppeteer/events.rb +1 -0
- data/lib/puppeteer/frame.rb +30 -8
- data/lib/puppeteer/frame_manager.rb +136 -59
- data/lib/puppeteer/http_request.rb +9 -1
- data/lib/puppeteer/http_response.rb +25 -4
- data/lib/puppeteer/js_handle.rb +8 -0
- data/lib/puppeteer/launcher/chrome.rb +51 -32
- data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
- data/lib/puppeteer/launcher/firefox.rb +259 -226
- data/lib/puppeteer/launcher/launch_options.rb +2 -1
- data/lib/puppeteer/network_event_manager.rb +122 -0
- data/lib/puppeteer/network_manager.rb +180 -40
- data/lib/puppeteer/page/screenshot_options.rb +1 -1
- data/lib/puppeteer/page.rb +75 -19
- data/lib/puppeteer/puppeteer.rb +2 -0
- data/lib/puppeteer/query_handler_manager.rb +2 -2
- data/lib/puppeteer/target.rb +5 -0
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +10 -7
- data/lib/puppeteer.rb +1 -0
- data/puppeteer-ruby.gemspec +2 -2
- metadata +8 -7
@@ -0,0 +1,122 @@
|
|
1
|
+
# Helper class to track network events by request ID
|
2
|
+
class Puppeteer::NetworkEventManager
|
3
|
+
def initialize
|
4
|
+
#
|
5
|
+
# There are four possible orders of events:
|
6
|
+
# A. `_onRequestWillBeSent`
|
7
|
+
# B. `_onRequestWillBeSent`, `_onRequestPaused`
|
8
|
+
# C. `_onRequestPaused`, `_onRequestWillBeSent`
|
9
|
+
# D. `_onRequestPaused`, `_onRequestWillBeSent`, `_onRequestPaused`,
|
10
|
+
# `_onRequestWillBeSent`, `_onRequestPaused`, `_onRequestPaused`
|
11
|
+
# (see crbug.com/1196004)
|
12
|
+
#
|
13
|
+
# For `_onRequest` we need the event from `_onRequestWillBeSent` and
|
14
|
+
# optionally the `interceptionId` from `_onRequestPaused`.
|
15
|
+
#
|
16
|
+
# If request interception is disabled, call `_onRequest` once per call to
|
17
|
+
# `_onRequestWillBeSent`.
|
18
|
+
# If request interception is enabled, call `_onRequest` once per call to
|
19
|
+
# `_onRequestPaused` (once per `interceptionId`).
|
20
|
+
#
|
21
|
+
# Events are stored to allow for subsequent events to call `_onRequest`.
|
22
|
+
#
|
23
|
+
# Note that (chains of) redirect requests have the same `requestId` (!) as
|
24
|
+
# the original request. We have to anticipate series of events like these:
|
25
|
+
# A. `_onRequestWillBeSent`,
|
26
|
+
# `_onRequestWillBeSent`, ...
|
27
|
+
# B. `_onRequestWillBeSent`, `_onRequestPaused`,
|
28
|
+
# `_onRequestWillBeSent`, `_onRequestPaused`, ...
|
29
|
+
# C. `_onRequestWillBeSent`, `_onRequestPaused`,
|
30
|
+
# `_onRequestPaused`, `_onRequestWillBeSent`, ...
|
31
|
+
# D. `_onRequestPaused`, `_onRequestWillBeSent`,
|
32
|
+
# `_onRequestPaused`, `_onRequestWillBeSent`, `_onRequestPaused`,
|
33
|
+
# `_onRequestWillBeSent`, `_onRequestPaused`, `_onRequestPaused`, ...
|
34
|
+
# (see crbug.com/1196004)
|
35
|
+
@request_will_be_sent_map = {}
|
36
|
+
@request_paused_map = {}
|
37
|
+
@http_requests_map = {}
|
38
|
+
#
|
39
|
+
# The below maps are used to reconcile Network.responseReceivedExtraInfo
|
40
|
+
# events with their corresponding request. Each response and redirect
|
41
|
+
# response gets an ExtraInfo event, and we don't know which will come first.
|
42
|
+
# This means that we have to store a Response or an ExtraInfo for each
|
43
|
+
# response, and emit the event when we get both of them. In addition, to
|
44
|
+
# handle redirects, we have to make them Arrays to represent the chain of
|
45
|
+
# events.
|
46
|
+
@response_received_extra_info_map = {}
|
47
|
+
@queued_redirect_info_map = {}
|
48
|
+
@queued_event_group_map = {}
|
49
|
+
end
|
50
|
+
|
51
|
+
def forget(network_request_id)
|
52
|
+
@request_will_be_sent_map.delete(network_request_id)
|
53
|
+
@request_paused_map.delete(network_request_id)
|
54
|
+
@queued_event_group_map.delete(network_request_id)
|
55
|
+
@queued_redirect_info_map.delete(network_request_id)
|
56
|
+
@response_received_extra_info_map.delete(network_request_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def response_extra_info(network_request_id)
|
60
|
+
@response_received_extra_info_map[network_request_id] ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
private def queued_redirect_info(fetch_request_id)
|
64
|
+
@queued_redirect_info_map[fetch_request_id] ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
def enqueue_redirect_info(fetch_request_id, redirect_info)
|
68
|
+
queued_redirect_info(fetch_request_id) << redirect_info
|
69
|
+
end
|
70
|
+
|
71
|
+
def take_queued_redirect_info(fetch_request_id)
|
72
|
+
queued_redirect_info(fetch_request_id).shift
|
73
|
+
end
|
74
|
+
|
75
|
+
def num_requests_in_progress
|
76
|
+
@http_requests_map.count { |_, request| !request.response }
|
77
|
+
end
|
78
|
+
|
79
|
+
def store_request_will_be_sent(network_request_id, event)
|
80
|
+
@request_will_be_sent_map[network_request_id] = event
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_request_will_be_sent(network_request_id)
|
84
|
+
@request_will_be_sent_map[network_request_id]
|
85
|
+
end
|
86
|
+
|
87
|
+
def forget_request_will_be_sent(network_request_id)
|
88
|
+
@request_will_be_sent_map.delete(network_request_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
def store_request_paused(network_request_id, event)
|
92
|
+
@request_paused_map[network_request_id] = event
|
93
|
+
end
|
94
|
+
|
95
|
+
def get_request_paused(network_request_id)
|
96
|
+
@request_paused_map[network_request_id]
|
97
|
+
end
|
98
|
+
|
99
|
+
def fotget_request_paused(network_request_id)
|
100
|
+
@request_paused_map.delete(network_request_id)
|
101
|
+
end
|
102
|
+
|
103
|
+
def store_request(network_request_id, request)
|
104
|
+
@http_requests_map[network_request_id] = request
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_request(network_request_id)
|
108
|
+
@http_requests_map[network_request_id]
|
109
|
+
end
|
110
|
+
|
111
|
+
def forget_request(network_request_id)
|
112
|
+
@http_requests_map.delete(network_request_id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def enqueue_event_group(network_request_id, queued_event_group)
|
116
|
+
@queued_event_group_map[network_request_id] = queued_event_group
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_queued_event_group(network_request_id)
|
120
|
+
@queued_event_group_map[network_request_id]
|
121
|
+
end
|
122
|
+
end
|
@@ -53,6 +53,22 @@ class Puppeteer::NetworkManager
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
+
class RedirectInfo
|
57
|
+
def initialize(event:, fetch_request_id:)
|
58
|
+
@event = event
|
59
|
+
@fetch_request_id = fetch_request_id
|
60
|
+
end
|
61
|
+
attr_reader :event, :fetch_request_id
|
62
|
+
end
|
63
|
+
|
64
|
+
class QueuedEventGroup
|
65
|
+
def initialize(response_received_event:)
|
66
|
+
@response_received_event = response_received_event
|
67
|
+
end
|
68
|
+
attr_reader :response_received_event
|
69
|
+
attr_accessor :loading_finished_event, :loading_failed_event
|
70
|
+
end
|
71
|
+
|
56
72
|
# @param {!Puppeteer.CDPSession} client
|
57
73
|
# @param {boolean} ignoreHTTPSErrors
|
58
74
|
# @param {!Puppeteer.FrameManager} frameManager
|
@@ -60,12 +76,7 @@ class Puppeteer::NetworkManager
|
|
60
76
|
@client = client
|
61
77
|
@ignore_https_errors = ignore_https_errors
|
62
78
|
@frame_manager = frame_manager
|
63
|
-
|
64
|
-
# @type {!Map<string, !Request>}
|
65
|
-
@request_id_to_request = {}
|
66
|
-
|
67
|
-
# @type {!Map<string, !Protocol.Network.requestWillBeSentPayload>}
|
68
|
-
@request_id_to_request_with_be_sent_event = {}
|
79
|
+
@network_event_manager = Puppeteer::NetworkEventManager.new
|
69
80
|
|
70
81
|
@extra_http_headers = {}
|
71
82
|
|
@@ -73,7 +84,6 @@ class Puppeteer::NetworkManager
|
|
73
84
|
@user_request_interception_enabled = false
|
74
85
|
@protocol_request_interception_enabled = false
|
75
86
|
@user_cache_disabled = false
|
76
|
-
@request_id_to_interception_id = {}
|
77
87
|
@internal_network_condition = InternalNetworkCondition.new(@client)
|
78
88
|
|
79
89
|
@client.on_event('Fetch.requestPaused') do |event|
|
@@ -97,6 +107,9 @@ class Puppeteer::NetworkManager
|
|
97
107
|
@client.on_event('Network.loadingFailed') do |event|
|
98
108
|
handle_loading_failed(event)
|
99
109
|
end
|
110
|
+
@client.on_event('Network.responseReceivedExtraInfo') do |event|
|
111
|
+
handle_response_received_extra_info(event)
|
112
|
+
end
|
100
113
|
end
|
101
114
|
|
102
115
|
def init
|
@@ -106,6 +119,14 @@ class Puppeteer::NetworkManager
|
|
106
119
|
end
|
107
120
|
end
|
108
121
|
|
122
|
+
def inspect
|
123
|
+
values = %i[network_event_manager].map do |sym|
|
124
|
+
value = instance_variable_get(:"@#{sym}")
|
125
|
+
"@#{sym}=#{value}"
|
126
|
+
end
|
127
|
+
"#<Puppeteer::HTTPRequest #{values.join(' ')}>"
|
128
|
+
end
|
129
|
+
|
109
130
|
# @param username [String|NilClass]
|
110
131
|
# @param password [String|NilClass]
|
111
132
|
def authenticate(username:, password:)
|
@@ -131,6 +152,10 @@ class Puppeteer::NetworkManager
|
|
131
152
|
@extra_http_headers.dup
|
132
153
|
end
|
133
154
|
|
155
|
+
def num_requests_in_progress
|
156
|
+
@network_event_manager.num_requests_in_progress
|
157
|
+
end
|
158
|
+
|
134
159
|
# @param value [TrueClass|FalseClass]
|
135
160
|
def offline_mode=(value)
|
136
161
|
@internal_network_condition.offline_mode=(value)
|
@@ -185,14 +210,17 @@ class Puppeteer::NetworkManager
|
|
185
210
|
|
186
211
|
private def handle_request_will_be_sent(event)
|
187
212
|
# Request interception doesn't happen for data URLs with Network Service.
|
188
|
-
if @
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
213
|
+
if @user_request_interception_enabled && !event['request']['url'].start_with?('data:')
|
214
|
+
network_request_id = event['requestId']
|
215
|
+
@network_event_manager.store_request_will_be_sent(network_request_id, event)
|
216
|
+
|
217
|
+
# CDP may have sent a Fetch.requestPaused event already. Check for it.
|
218
|
+
if_present(@network_event_manager.get_request_paused(network_request_id)) do |request_paused_event|
|
219
|
+
fetch_request_id = request_paused_event['requestId']
|
220
|
+
handle_request(event, fetch_request_id)
|
221
|
+
@network_event_manager.forget_request_paused(network_request_id)
|
195
222
|
end
|
223
|
+
|
196
224
|
return
|
197
225
|
end
|
198
226
|
handle_request(event, nil)
|
@@ -233,26 +261,61 @@ class Puppeteer::NetworkManager
|
|
233
261
|
end
|
234
262
|
end
|
235
263
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
264
|
+
network_request_id = event['networkId']
|
265
|
+
fetch_request_id = event['requestId']
|
266
|
+
return unless network_request_id
|
267
|
+
|
268
|
+
request_will_be_sent_event = @network_event_manager.get_request_will_be_sent(network_request_id)
|
269
|
+
|
270
|
+
# redirect requests have the same `requestId`
|
271
|
+
if request_will_be_sent_event &&
|
272
|
+
(request_will_be_sent_event['request']['url'] != event['request']['url'] ||
|
273
|
+
request_will_be_sent_event['request']['method'] != event['request']['method'])
|
274
|
+
|
275
|
+
@network_event_manager.forget_request_will_be_sent(network_request_id)
|
276
|
+
request_will_be_sent_event = nil
|
277
|
+
end
|
278
|
+
|
279
|
+
if request_will_be_sent_event
|
280
|
+
handle_request(request_will_be_sent_event, fetch_request_id)
|
240
281
|
else
|
241
|
-
@
|
282
|
+
@network_event_manager.store_request_paused(network_request_id, event)
|
242
283
|
end
|
243
284
|
end
|
244
285
|
|
245
|
-
private def handle_request(event,
|
286
|
+
private def handle_request(event, fetch_request_id)
|
246
287
|
redirect_chain = []
|
247
288
|
if event['redirectResponse']
|
248
|
-
|
249
|
-
|
289
|
+
# We want to emit a response and requestfinished for the
|
290
|
+
# redirectResponse, but we can't do so unless we have a
|
291
|
+
# responseExtraInfo ready to pair it up with. If we don't have any
|
292
|
+
# responseExtraInfos saved in our queue, they we have to wait until
|
293
|
+
# the next one to emit response and requestfinished, *and* we should
|
294
|
+
# also wait to emit this Request too because it should come after the
|
295
|
+
# response/requestfinished.
|
296
|
+
redirect_response_extra_info = nil
|
297
|
+
if event['redirectHasExtraInfo']
|
298
|
+
redirect_response_extra_info = @network_event_manager.response_extra_info(event['requestId']).shift
|
299
|
+
unless redirect_response_extra_info
|
300
|
+
redirect_info = RedirectInfo.new(
|
301
|
+
event: event,
|
302
|
+
fetch_request_id: fetch_request_id,
|
303
|
+
)
|
304
|
+
@network_event_manager.enqueue_redirect_info(event['requestId'], redirect_info)
|
305
|
+
return
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# If we connect late to the target, we could have missed the
|
310
|
+
# requestWillBeSent event.
|
311
|
+
if_present(@network_event_manager.get_request(event['requestId'])) do |request|
|
312
|
+
handle_request_redirect(request, event['redirectResponse'], redirect_response_extra_info)
|
250
313
|
redirect_chain = request.internal.redirect_chain
|
251
314
|
end
|
252
315
|
end
|
253
316
|
frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
|
254
|
-
request = Puppeteer::HTTPRequest.new(@client, frame,
|
255
|
-
@
|
317
|
+
request = Puppeteer::HTTPRequest.new(@client, frame, fetch_request_id, @user_request_interception_enabled, event, redirect_chain)
|
318
|
+
@network_event_manager.store_request(event['requestId'], request)
|
256
319
|
emit_event(NetworkManagerEmittedEvents::Request, request)
|
257
320
|
begin
|
258
321
|
request.finalize_interceptions
|
@@ -262,55 +325,133 @@ class Puppeteer::NetworkManager
|
|
262
325
|
end
|
263
326
|
|
264
327
|
private def handle_request_served_from_cache(event)
|
265
|
-
|
328
|
+
request = @network_event_manager.get_request(event['requestId'])
|
329
|
+
if request
|
266
330
|
request.internal.from_memory_cache = true
|
267
331
|
end
|
332
|
+
emit_event(NetworkManagerEmittedEvents::RequestServedFromCache, request)
|
268
333
|
end
|
269
334
|
|
270
335
|
# @param request [Puppeteer::HTTPRequest]
|
271
336
|
# @param response_payload [Hash]
|
272
|
-
private def handle_request_redirect(request, response_payload)
|
273
|
-
response = Puppeteer::HTTPResponse.new(@client, request, response_payload)
|
337
|
+
private def handle_request_redirect(request, response_payload, extra_info)
|
338
|
+
response = Puppeteer::HTTPResponse.new(@client, request, response_payload, extra_info)
|
274
339
|
request.internal.response = response
|
275
340
|
request.internal.redirect_chain << request
|
276
341
|
response.internal.body_loaded_promise.reject(Puppeteer::HTTPResponse::Redirected.new)
|
277
|
-
|
278
|
-
@attempted_authentications.delete(request.internal.interception_id)
|
342
|
+
forget_request(request, false)
|
279
343
|
emit_event(NetworkManagerEmittedEvents::Response, response)
|
280
344
|
emit_event(NetworkManagerEmittedEvents::RequestFinished, request)
|
281
345
|
end
|
282
346
|
|
283
|
-
|
284
|
-
|
285
|
-
request = @request_id_to_request[event['requestId']]
|
347
|
+
private def emit_response_event(response_received_event, extra_info)
|
348
|
+
request = @network_event_manager.get_request(response_received_event['requestId'])
|
286
349
|
# FileUpload sends a response without a matching request.
|
287
350
|
return unless request
|
288
351
|
|
289
|
-
|
352
|
+
unless @network_event_manager.response_extra_info(response_received_event['requestId']).empty?
|
353
|
+
debug_puts("Unexpected extraInfo events for request #{response_received_event['requestId']}")
|
354
|
+
end
|
355
|
+
|
356
|
+
response = Puppeteer::HTTPResponse.new(@client, request, response_received_event['response'], extra_info)
|
290
357
|
request.internal.response = response
|
291
358
|
emit_event(NetworkManagerEmittedEvents::Response, response)
|
292
359
|
end
|
293
360
|
|
361
|
+
# @param event [Hash]
|
362
|
+
private def handle_response_received(event)
|
363
|
+
request = @network_event_manager.get_request(event['requestId'])
|
364
|
+
extra_info = nil
|
365
|
+
if request && !request.internal.from_memory_cache? && event['hasExtraInfo']
|
366
|
+
extra_info = @network_event_manager.response_extra_info(event['requestId']).shift
|
367
|
+
|
368
|
+
unless extra_info
|
369
|
+
# Wait until we get the corresponding ExtraInfo event.
|
370
|
+
@network_event_manager.enqueue_event_group(event['requestId'], QueuedEventGroup.new(response_received_event: event))
|
371
|
+
return
|
372
|
+
end
|
373
|
+
end
|
374
|
+
emit_response_event(event, extra_info)
|
375
|
+
end
|
376
|
+
|
377
|
+
private def handle_response_received_extra_info(event)
|
378
|
+
# We may have skipped a redirect response/request pair due to waiting for
|
379
|
+
# this ExtraInfo event. If so, continue that work now that we have the
|
380
|
+
# request.
|
381
|
+
if_present(@network_event_manager.take_queued_redirect_info(event['requestId'])) do |redirect_info|
|
382
|
+
@network_event_manager.response_extra_info(event['requestId']) << event
|
383
|
+
handle_request(redirect_info.event, redirect_info)
|
384
|
+
return
|
385
|
+
end
|
386
|
+
|
387
|
+
# We may have skipped response and loading events because we didn't have
|
388
|
+
# this ExtraInfo event yet. If so, emit those events now.
|
389
|
+
if_present(@network_event_manager.get_queued_event_group(event['requestId'])) do |queued_events|
|
390
|
+
emit_response_event(queued_events.response_received_event, event)
|
391
|
+
if_present(queued_events.loading_finished_event) do |loading_finished_event|
|
392
|
+
emit_loading_finished(loading_finished_event)
|
393
|
+
end
|
394
|
+
if_present(queued_events.loading_failed_event) do |loading_failed_event|
|
395
|
+
emit_loading_failed(loading_failed_event)
|
396
|
+
end
|
397
|
+
return
|
398
|
+
end
|
399
|
+
|
400
|
+
# Wait until we get another event that can use this ExtraInfo event.
|
401
|
+
@network_event_manager.response_extra_info(event['requestId']) << event
|
402
|
+
end
|
403
|
+
|
404
|
+
private def forget_request(request, forget_events)
|
405
|
+
request_id = request.internal.request_id
|
406
|
+
interception_id = request.internal.interception_id
|
407
|
+
|
408
|
+
@network_event_manager.forget_request(request_id)
|
409
|
+
@attempted_authentications.delete(interception_id)
|
410
|
+
if forget_events
|
411
|
+
@network_event_manager.forget(request_id)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
294
415
|
private def handle_loading_finished(event)
|
295
|
-
request
|
416
|
+
# If the response event for this request is still waiting on a
|
417
|
+
# corresponding ExtraInfo event, then wait to emit this event too.
|
418
|
+
queued_events = @network_event_manager.get_queued_event_group(event['requestId'])
|
419
|
+
if queued_events
|
420
|
+
queued_events.loading_finished_event = event
|
421
|
+
else
|
422
|
+
emit_loading_finished(event)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
private def emit_loading_finished(event)
|
427
|
+
request = @network_event_manager.get_request(event['requestId'])
|
296
428
|
# For certain requestIds we never receive requestWillBeSent event.
|
297
429
|
# @see https://crbug.com/750469
|
298
430
|
return unless request
|
299
431
|
|
300
|
-
|
301
432
|
# Under certain conditions we never get the Network.responseReceived
|
302
433
|
# event from protocol. @see https://crbug.com/883475
|
303
434
|
if_present(request.response) do |response|
|
304
435
|
response.internal.body_loaded_promise.fulfill(nil)
|
305
436
|
end
|
306
437
|
|
307
|
-
|
308
|
-
@attempted_authentications.delete(request.internal.interception_id)
|
438
|
+
forget_request(request, true)
|
309
439
|
emit_event(NetworkManagerEmittedEvents::RequestFinished, request)
|
310
440
|
end
|
311
441
|
|
312
442
|
private def handle_loading_failed(event)
|
313
|
-
request
|
443
|
+
# If the response event for this request is still waiting on a
|
444
|
+
# corresponding ExtraInfo event, then wait to emit this event too.
|
445
|
+
queued_events = @network_event_manager.get_queued_event_group(event['requestId'])
|
446
|
+
if queued_events
|
447
|
+
queued_events.loading_failed_event = event
|
448
|
+
else
|
449
|
+
emit_loading_failed(event)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
private def emit_loading_failed(event)
|
454
|
+
request = @network_event_manager.get_request(event['requestId'])
|
314
455
|
# For certain requestIds we never receive requestWillBeSent event.
|
315
456
|
# @see https://crbug.com/750469
|
316
457
|
return unless request
|
@@ -319,8 +460,7 @@ class Puppeteer::NetworkManager
|
|
319
460
|
if_present(request.response) do |response|
|
320
461
|
response.internal.body_loaded_promise.fulfill(nil)
|
321
462
|
end
|
322
|
-
|
323
|
-
@attempted_authentications.delete(request.internal.interception_id)
|
463
|
+
forget_request(request, true)
|
324
464
|
emit_event(NetworkManagerEmittedEvents::RequestFailed, request)
|
325
465
|
end
|
326
466
|
end
|
@@ -34,7 +34,7 @@ class Puppeteer::Page
|
|
34
34
|
@type ||= 'png'
|
35
35
|
|
36
36
|
if options[:quality]
|
37
|
-
|
37
|
+
if @type != 'jpeg' && @type != 'webp'
|
38
38
|
raise ArgumentError.new("options.quality is unsupported for the #{@type} screenshots")
|
39
39
|
end
|
40
40
|
unless options[:quality].is_a?(Numeric)
|
data/lib/puppeteer/page.rb
CHANGED
@@ -50,19 +50,26 @@ class Puppeteer::Page
|
|
50
50
|
@workers = {}
|
51
51
|
@user_drag_interception_enabled = false
|
52
52
|
|
53
|
-
@client.
|
54
|
-
if event['targetInfo']['type'] != 'worker'
|
53
|
+
@client.add_event_listener('Target.attachedToTarget') do |event|
|
54
|
+
if event['targetInfo']['type'] != 'worker' && event['targetInfo']['type'] != 'iframe'
|
55
55
|
# If we don't detach from service workers, they will never die.
|
56
|
+
# We still want to attach to workers for emitting events.
|
57
|
+
# We still want to attach to iframes so sessions may interact with them.
|
58
|
+
# We detach from all other types out of an abundance of caution.
|
59
|
+
# See https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypePage%5B%5D%22
|
60
|
+
# for the complete list of available types.
|
56
61
|
@client.async_send_message('Target.detachFromTarget', sessionId: event['sessionId'])
|
57
62
|
next
|
58
63
|
end
|
59
64
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
if event['targetInfo']['type'] == 'worker'
|
66
|
+
session = Puppeteer::Connection.from_session(@client).session(event['sessionId']) # rubocop:disable Lint/UselessAssignment
|
67
|
+
# const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
|
68
|
+
# this._workers.set(event.sessionId, worker);
|
69
|
+
# this.emit(PageEmittedEvents::WorkerCreated, worker);
|
70
|
+
end
|
64
71
|
end
|
65
|
-
@client.
|
72
|
+
@client.add_event_listener('Target.detachedFromTarget') do |event|
|
66
73
|
session_id = event['sessionId']
|
67
74
|
worker = @workers[session_id]
|
68
75
|
next unless worker
|
@@ -103,10 +110,10 @@ class Puppeteer::Page
|
|
103
110
|
@client.on_event('Page.loadEventFired') do |event|
|
104
111
|
emit_event(PageEmittedEvents::Load)
|
105
112
|
end
|
106
|
-
@client.
|
113
|
+
@client.add_event_listener('Runtime.consoleAPICalled') do |event|
|
107
114
|
handle_console_api(event)
|
108
115
|
end
|
109
|
-
@client.
|
116
|
+
@client.add_event_listener('Runtime.bindingCalled') do |event|
|
110
117
|
handle_binding_called(event)
|
111
118
|
end
|
112
119
|
@client.on_event('Page.javascriptDialogOpening') do |event|
|
@@ -518,7 +525,7 @@ class Puppeteer::Page
|
|
518
525
|
return
|
519
526
|
end
|
520
527
|
|
521
|
-
context = @frame_manager.execution_context_by_id(event['executionContextId'])
|
528
|
+
context = @frame_manager.execution_context_by_id(event['executionContextId'], @client)
|
522
529
|
values = event['args'].map do |arg|
|
523
530
|
remote_object = Puppeteer::RemoteObject.new(arg)
|
524
531
|
Puppeteer::JSHandle.create(context: context, remote_object: remote_object)
|
@@ -664,19 +671,38 @@ class Puppeteer::Page
|
|
664
671
|
private def wait_for_network_manager_event(event_name, predicate:, timeout:)
|
665
672
|
option_timeout = timeout || @timeout_settings.timeout
|
666
673
|
|
667
|
-
|
668
|
-
|
674
|
+
promise = resolvable_future
|
675
|
+
|
676
|
+
listener_id = @frame_manager.network_manager.add_event_listener(event_name) do |event_target|
|
677
|
+
if predicate.call(event_target)
|
678
|
+
promise.fulfill(event_target)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
|
682
|
+
begin
|
683
|
+
# Timeout.timeout(0) means "no limit" for timeout.
|
684
|
+
Timeout.timeout(option_timeout / 1000.0) do
|
685
|
+
await_any(promise, session_close_promise)
|
686
|
+
end
|
687
|
+
rescue Timeout::Error
|
688
|
+
raise Puppeteer::TimeoutError.new("waiting for #{event_name} failed: timeout #{option_timeout}ms exceeded")
|
689
|
+
ensure
|
669
690
|
@frame_manager.network_manager.remove_event_listener(listener_id)
|
670
691
|
end
|
692
|
+
end
|
693
|
+
|
694
|
+
private def wait_for_frame_manager_event(*event_names, predicate:, timeout:)
|
695
|
+
option_timeout = timeout || @timeout_settings.timeout
|
671
696
|
|
672
697
|
promise = resolvable_future
|
673
698
|
|
674
|
-
|
675
|
-
@frame_manager.
|
699
|
+
listener_ids = event_names.map do |event_name|
|
700
|
+
@frame_manager.add_event_listener(event_name) do |event_target|
|
676
701
|
if predicate.call(event_target)
|
677
|
-
promise.fulfill(event_target)
|
702
|
+
promise.fulfill(event_target) unless promise.resolved?
|
678
703
|
end
|
679
704
|
end
|
705
|
+
end
|
680
706
|
|
681
707
|
begin
|
682
708
|
# Timeout.timeout(0) means "no limit" for timeout.
|
@@ -684,9 +710,11 @@ class Puppeteer::Page
|
|
684
710
|
await_any(promise, session_close_promise)
|
685
711
|
end
|
686
712
|
rescue Timeout::Error
|
687
|
-
raise Puppeteer::TimeoutError.new("waiting for #{
|
713
|
+
raise Puppeteer::TimeoutError.new("waiting for #{event_names.join(" or ")} failed: timeout #{option_timeout}ms exceeded")
|
688
714
|
ensure
|
689
|
-
|
715
|
+
listener_ids.each do |listener_id|
|
716
|
+
@frame_manager.remove_event_listener(listener_id)
|
717
|
+
end
|
690
718
|
end
|
691
719
|
end
|
692
720
|
|
@@ -709,7 +737,7 @@ class Puppeteer::Page
|
|
709
737
|
if url
|
710
738
|
-> (request) { request.url == url }
|
711
739
|
else
|
712
|
-
|
740
|
+
predicate
|
713
741
|
end
|
714
742
|
|
715
743
|
wait_for_network_manager_event(NetworkManagerEmittedEvents::Request,
|
@@ -743,7 +771,7 @@ class Puppeteer::Page
|
|
743
771
|
if url
|
744
772
|
-> (response) { response.url == url }
|
745
773
|
else
|
746
|
-
|
774
|
+
predicate
|
747
775
|
end
|
748
776
|
|
749
777
|
wait_for_network_manager_event(NetworkManagerEmittedEvents::Response,
|
@@ -758,6 +786,34 @@ class Puppeteer::Page
|
|
758
786
|
# @param predicate [Proc(Puppeteer::HTTPRequest -> Boolean)]
|
759
787
|
define_async_method :async_wait_for_response
|
760
788
|
|
789
|
+
def wait_for_frame(url: nil, predicate: nil, timeout: nil)
|
790
|
+
if !url && !predicate
|
791
|
+
raise ArgumentError.new('url or predicate must be specified')
|
792
|
+
end
|
793
|
+
if predicate && !predicate.is_a?(Proc)
|
794
|
+
raise ArgumentError.new('predicate must be a proc.')
|
795
|
+
end
|
796
|
+
frame_predicate =
|
797
|
+
if url
|
798
|
+
-> (frame) { frame.url == url }
|
799
|
+
else
|
800
|
+
predicate
|
801
|
+
end
|
802
|
+
|
803
|
+
wait_for_frame_manager_event(
|
804
|
+
FrameManagerEmittedEvents::FrameAttached,
|
805
|
+
FrameManagerEmittedEvents::FrameNavigated,
|
806
|
+
predicate: frame_predicate,
|
807
|
+
timeout: timeout,
|
808
|
+
)
|
809
|
+
end
|
810
|
+
|
811
|
+
# @!method async_wait_for_frame(url: nil, predicate: nil, timeout: nil)
|
812
|
+
#
|
813
|
+
# @param url [String]
|
814
|
+
# @param predicate [Proc(Puppeteer::Frame -> Boolean)]
|
815
|
+
define_async_method :async_wait_for_frame
|
816
|
+
|
761
817
|
# @param timeout [number|nil]
|
762
818
|
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
763
819
|
def go_back(timeout: nil, wait_until: nil)
|
data/lib/puppeteer/puppeteer.rb
CHANGED
@@ -44,6 +44,7 @@ class Puppeteer::Puppeteer
|
|
44
44
|
args: nil,
|
45
45
|
user_data_dir: nil,
|
46
46
|
devtools: nil,
|
47
|
+
debugging_port: nil,
|
47
48
|
headless: nil,
|
48
49
|
ignore_https_errors: nil,
|
49
50
|
default_viewport: NoViewport.new,
|
@@ -63,6 +64,7 @@ class Puppeteer::Puppeteer
|
|
63
64
|
args: args,
|
64
65
|
user_data_dir: user_data_dir,
|
65
66
|
devtools: devtools,
|
67
|
+
debugging_port: debugging_port,
|
66
68
|
headless: headless,
|
67
69
|
ignore_https_errors: ignore_https_errors,
|
68
70
|
default_viewport: default_viewport,
|
@@ -26,8 +26,8 @@ class Puppeteer::QueryHandlerManager
|
|
26
26
|
@query_handler.query_one(element_handle, @selector)
|
27
27
|
end
|
28
28
|
|
29
|
-
def wait_for(dom_world, visible:, hidden:, timeout:)
|
30
|
-
@query_handler.wait_for(dom_world, @selector, visible: visible, hidden: hidden, timeout: timeout)
|
29
|
+
def wait_for(dom_world, visible:, hidden:, timeout:, root:)
|
30
|
+
@query_handler.wait_for(dom_world, @selector, visible: visible, hidden: hidden, timeout: timeout, root: root)
|
31
31
|
end
|
32
32
|
|
33
33
|
def query_all(element_handle)
|
data/lib/puppeteer/target.rb
CHANGED
data/lib/puppeteer/version.rb
CHANGED