puppeteer-ruby 0.37.3 → 0.40.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +25 -1
- data/docs/api_coverage.md +6 -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 +64 -8
- 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 +46 -27
- data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
- data/lib/puppeteer/launcher/firefox.rb +258 -225
- 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(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