puppeteer-ruby 0.39.0 → 0.40.0
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/docs/api_coverage.md +2 -2
- data/lib/puppeteer/aria_query_handler.rb +6 -3
- data/lib/puppeteer/browser_runner.rb +1 -1
- data/lib/puppeteer/custom_query_handler.rb +2 -2
- data/lib/puppeteer/dom_world.rb +12 -8
- data/lib/puppeteer/element_handle.rb +54 -0
- data/lib/puppeteer/events.rb +1 -0
- data/lib/puppeteer/http_response.rb +25 -4
- data/lib/puppeteer/js_handle.rb +8 -0
- data/lib/puppeteer/network_event_manager.rb +122 -0
- data/lib/puppeteer/network_manager.rb +180 -40
- data/lib/puppeteer/query_handler_manager.rb +2 -2
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af966d033f8cff7c99c121805ffe7dde6f4d73cfe0fa9f34937b9eedbeafd9b8
|
4
|
+
data.tar.gz: 3578653749391fd953144e21160beb6baee5eb6463b43b8cecafcf3f3433bda7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ae375c2763cd2fd597f72b0e82ccae84eba414cb4e1b7b3e0067c250c869020118f58fc1d74e0bfe0bb512318501fcedd5c1a25f3725de44a29e01393227765
|
7
|
+
data.tar.gz: 4dec50af05864ec0410e4d99bbec2cdccfbf06412eb21b3f923be4de892244510ba4a5d553dafc04b62d6de6381caf852238a9e971d2ba2d30c3009e78c9e048
|
data/docs/api_coverage.md
CHANGED
@@ -36,10 +36,11 @@ class Puppeteer::AriaQueryHandler
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil)
|
39
|
+
def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil, root: nil)
|
40
|
+
# addHandlerToWorld
|
40
41
|
binding_function = Puppeteer::DOMWorld::BindingFunction.new(
|
41
42
|
name: 'ariaQuerySelector',
|
42
|
-
proc: -> (sel) { query_one(dom_world.send(:document), sel) },
|
43
|
+
proc: -> (sel) { query_one(root || dom_world.send(:document), sel) },
|
43
44
|
)
|
44
45
|
dom_world.send(:wait_for_selector_in_page,
|
45
46
|
'(_, selector) => globalThis.ariaQuerySelector(selector)',
|
@@ -47,7 +48,9 @@ class Puppeteer::AriaQueryHandler
|
|
47
48
|
visible: visible,
|
48
49
|
hidden: hidden,
|
49
50
|
timeout: timeout,
|
50
|
-
binding_function: binding_function
|
51
|
+
binding_function: binding_function,
|
52
|
+
root: root,
|
53
|
+
)
|
51
54
|
end
|
52
55
|
|
53
56
|
def query_all(element, selector)
|
@@ -149,7 +149,7 @@ class Puppeteer::BrowserRunner
|
|
149
149
|
# Attempt to remove temporary profile directory to avoid littering.
|
150
150
|
begin
|
151
151
|
if @using_temp_user_data_dir
|
152
|
-
FileUtils.rm_rf(@
|
152
|
+
FileUtils.rm_rf(@user_data_dir)
|
153
153
|
end
|
154
154
|
rescue => err
|
155
155
|
debug_puts(err)
|
@@ -21,12 +21,12 @@ class Puppeteer::CustomQueryHandler
|
|
21
21
|
nil
|
22
22
|
end
|
23
23
|
|
24
|
-
def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil)
|
24
|
+
def wait_for(dom_world, selector, visible: nil, hidden: nil, timeout: nil, root: nil)
|
25
25
|
unless @query_one
|
26
26
|
raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
|
27
27
|
end
|
28
28
|
|
29
|
-
dom_world.send(:wait_for_selector_in_page, @query_one, selector, visible: visible, hidden: hidden, timeout: timeout)
|
29
|
+
dom_world.send(:wait_for_selector_in_page, @query_one, selector, visible: visible, hidden: hidden, timeout: timeout, root: root)
|
30
30
|
end
|
31
31
|
|
32
32
|
def query_all(element, selector)
|
data/lib/puppeteer/dom_world.rb
CHANGED
@@ -420,10 +420,10 @@ class Puppeteer::DOMWorld
|
|
420
420
|
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
421
421
|
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
422
422
|
# @param timeout [Integer]
|
423
|
-
def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
|
423
|
+
def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil, root: nil)
|
424
424
|
# call wait_for_selector_in_page with custom query selector.
|
425
425
|
query_selector_manager = Puppeteer::QueryHandlerManager.instance
|
426
|
-
query_selector_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout)
|
426
|
+
query_selector_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout, root: root)
|
427
427
|
end
|
428
428
|
|
429
429
|
private def binding_identifier(name, context)
|
@@ -497,10 +497,11 @@ class Puppeteer::DOMWorld
|
|
497
497
|
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
498
498
|
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
499
499
|
# @param timeout [Integer]
|
500
|
-
private def wait_for_selector_in_page(query_one, selector, visible: nil, hidden: nil, timeout: nil, binding_function: nil)
|
500
|
+
private def wait_for_selector_in_page(query_one, selector, visible: nil, hidden: nil, timeout: nil, root: nil, binding_function: nil)
|
501
501
|
option_wait_for_visible = visible || false
|
502
502
|
option_wait_for_hidden = hidden || false
|
503
503
|
option_timeout = timeout || @timeout_settings.timeout
|
504
|
+
option_root = root
|
504
505
|
|
505
506
|
polling =
|
506
507
|
if option_wait_for_visible || option_wait_for_hidden
|
@@ -511,11 +512,11 @@ class Puppeteer::DOMWorld
|
|
511
512
|
title = "selector #{selector}#{option_wait_for_hidden ? 'to be hidden' : ''}"
|
512
513
|
|
513
514
|
selector_predicate = make_predicate_string(
|
514
|
-
predicate_arg_def: '(selector, waitForVisible, waitForHidden)',
|
515
|
+
predicate_arg_def: '(root, selector, waitForVisible, waitForHidden)',
|
515
516
|
predicate_query_handler: query_one,
|
516
517
|
async: true,
|
517
518
|
predicate_body: <<~JAVASCRIPT
|
518
|
-
const node = await predicateQueryHandler(
|
519
|
+
const node = await predicateQueryHandler(root, selector)
|
519
520
|
return checkWaitForOptions(node, waitForVisible, waitForHidden);
|
520
521
|
JAVASCRIPT
|
521
522
|
)
|
@@ -527,6 +528,7 @@ class Puppeteer::DOMWorld
|
|
527
528
|
polling: polling,
|
528
529
|
timeout: option_timeout,
|
529
530
|
args: [selector, option_wait_for_visible, option_wait_for_hidden],
|
531
|
+
root: option_root,
|
530
532
|
binding_function: binding_function,
|
531
533
|
)
|
532
534
|
handle = wait_task.await_promise
|
@@ -541,10 +543,11 @@ class Puppeteer::DOMWorld
|
|
541
543
|
# @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
|
542
544
|
# @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
|
543
545
|
# @param timeout [Integer]
|
544
|
-
def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
|
546
|
+
def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil, root: nil)
|
545
547
|
option_wait_for_visible = visible || false
|
546
548
|
option_wait_for_hidden = hidden || false
|
547
549
|
option_timeout = timeout || @timeout_settings.timeout
|
550
|
+
option_root = root
|
548
551
|
|
549
552
|
polling =
|
550
553
|
if option_wait_for_visible || option_wait_for_hidden
|
@@ -555,9 +558,9 @@ class Puppeteer::DOMWorld
|
|
555
558
|
title = "XPath #{xpath}#{option_wait_for_hidden ? 'to be hidden' : ''}"
|
556
559
|
|
557
560
|
xpath_predicate = make_predicate_string(
|
558
|
-
predicate_arg_def: '(selector, waitForVisible, waitForHidden)',
|
561
|
+
predicate_arg_def: '(root, selector, waitForVisible, waitForHidden)',
|
559
562
|
predicate_body: <<~JAVASCRIPT
|
560
|
-
const node = document.evaluate(selector,
|
563
|
+
const node = document.evaluate(selector, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
561
564
|
return checkWaitForOptions(node, waitForVisible, waitForHidden);
|
562
565
|
JAVASCRIPT
|
563
566
|
)
|
@@ -569,6 +572,7 @@ class Puppeteer::DOMWorld
|
|
569
572
|
polling: polling,
|
570
573
|
timeout: option_timeout,
|
571
574
|
args: [xpath, option_wait_for_visible, option_wait_for_hidden],
|
575
|
+
root: option_root,
|
572
576
|
)
|
573
577
|
handle = wait_task.await_promise
|
574
578
|
unless handle.as_element
|
@@ -20,6 +20,60 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
20
20
|
@disposed = false
|
21
21
|
end
|
22
22
|
|
23
|
+
def inspect
|
24
|
+
values = %i[context remote_object page disposed].map do |sym|
|
25
|
+
value = instance_variable_get(:"@#{sym}")
|
26
|
+
"@#{sym}=#{value}"
|
27
|
+
end
|
28
|
+
"#<Puppeteer::ElementHandle #{values.join(' ')}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Wait for the `selector` to appear within the element. If at the moment of calling the
|
33
|
+
# method the `selector` already exists, the method will return immediately. If
|
34
|
+
# the `selector` doesn't appear after the `timeout` milliseconds of waiting, the
|
35
|
+
# function will throw.
|
36
|
+
#
|
37
|
+
# This method does not work across navigations or if the element is detached from DOM.
|
38
|
+
#
|
39
|
+
# @param selector - A
|
40
|
+
# {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
|
41
|
+
# of an element to wait for
|
42
|
+
# @param options - Optional waiting parameters
|
43
|
+
# @returns Promise which resolves when element specified by selector string
|
44
|
+
# is added to DOM. Resolves to `null` if waiting for hidden: `true` and
|
45
|
+
# selector is not found in DOM.
|
46
|
+
# @remarks
|
47
|
+
# The optional parameters in `options` are:
|
48
|
+
#
|
49
|
+
# - `visible`: wait for the selected element to be present in DOM and to be
|
50
|
+
# visible, i.e. to not have `display: none` or `visibility: hidden` CSS
|
51
|
+
# properties. Defaults to `false`.
|
52
|
+
#
|
53
|
+
# - `hidden`: wait for the selected element to not be found in the DOM or to be hidden,
|
54
|
+
# i.e. have `display: none` or `visibility: hidden` CSS properties. Defaults to
|
55
|
+
# `false`.
|
56
|
+
#
|
57
|
+
# - `timeout`: maximum time to wait in milliseconds. Defaults to `30000`
|
58
|
+
# (30 seconds). Pass `0` to disable timeout. The default value can be changed
|
59
|
+
# by using the {@link Page.setDefaultTimeout} method.
|
60
|
+
def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
|
61
|
+
frame = @context.frame
|
62
|
+
|
63
|
+
secondary_world = frame.secondary_world
|
64
|
+
adopted_root = secondary_world.execution_context.adopt_element_handle(self)
|
65
|
+
handle = secondary_world.wait_for_selector(selector, visible: visible, hidden: hidden, timeout: timeout, root: adopted_root)
|
66
|
+
adopted_root.dispose
|
67
|
+
return nil unless handle
|
68
|
+
|
69
|
+
main_world = frame.main_world
|
70
|
+
result = main_world.execution_context.adopt_element_handle(handle)
|
71
|
+
handle.dispose
|
72
|
+
result
|
73
|
+
end
|
74
|
+
|
75
|
+
define_async_method :async_wait_for_selector
|
76
|
+
|
23
77
|
def as_element
|
24
78
|
self
|
25
79
|
end
|
data/lib/puppeteer/events.rb
CHANGED
@@ -81,6 +81,7 @@ module NetworkManagerEmittedEvents ; end
|
|
81
81
|
|
82
82
|
{
|
83
83
|
Request: EventsDefinitionUtils.symbol('NetworkManager.Request'),
|
84
|
+
RequestServedFromCache: EventsDefinitionUtils.symbol('NetworkManager.RequestServedFromCache'),
|
84
85
|
Response: EventsDefinitionUtils.symbol('NetworkManager.Response'),
|
85
86
|
RequestFailed: EventsDefinitionUtils.symbol('NetworkManager.RequestFailed'),
|
86
87
|
RequestFinished: EventsDefinitionUtils.symbol('NetworkManager.RequestFinished'),
|
@@ -31,7 +31,8 @@ class Puppeteer::HTTPResponse
|
|
31
31
|
# @param client [Puppeteer::CDPSession]
|
32
32
|
# @param request [Puppeteer::HTTPRequest]
|
33
33
|
# @param response_payload [Hash]
|
34
|
-
|
34
|
+
# @param extra_info [Hash|nil]
|
35
|
+
def initialize(client, request, response_payload, extra_info)
|
35
36
|
@client = client
|
36
37
|
@request = request
|
37
38
|
|
@@ -41,14 +42,15 @@ class Puppeteer::HTTPResponse
|
|
41
42
|
port: response_payload['remotePort'],
|
42
43
|
)
|
43
44
|
|
44
|
-
@
|
45
|
-
@status_text = response_payload['statusText']
|
45
|
+
@status_text = parse_štatus_text_from_extra_info(extra_info) || response_payload['statusText']
|
46
46
|
@url = request.url
|
47
47
|
@from_disk_cache = !!response_payload['fromDiskCache']
|
48
48
|
@from_service_worker = !!response_payload['fromServiceWorker']
|
49
49
|
|
50
|
+
@status = extra_info ? extra_info['statusCode'] : response_payload['status']
|
50
51
|
@headers = {}
|
51
|
-
|
52
|
+
headers = extra_info ? extra_info['headers'] : response_payload['headers']
|
53
|
+
headers.each do |key, value|
|
52
54
|
@headers[key.downcase] = value
|
53
55
|
end
|
54
56
|
@security_details = if_present(response_payload['securityDetails']) do |security_payload|
|
@@ -62,6 +64,25 @@ class Puppeteer::HTTPResponse
|
|
62
64
|
|
63
65
|
attr_reader :remote_address, :url, :status, :status_text, :headers, :security_details, :request
|
64
66
|
|
67
|
+
def inspect
|
68
|
+
values = %i[remote_address url status status_text headers security_details request].map do |sym|
|
69
|
+
value = instance_variable_get(:"@#{sym}")
|
70
|
+
"@#{sym}=#{value}"
|
71
|
+
end
|
72
|
+
"#<Puppeteer::HTTPRequest #{values.join(' ')}>"
|
73
|
+
end
|
74
|
+
|
75
|
+
private def parse_štatus_text_from_extra_info(extra_info)
|
76
|
+
return nil if !extra_info || !extra_info['headersText']
|
77
|
+
first_line = extra_info['headersText'].split("\r").first
|
78
|
+
return nil unless first_line
|
79
|
+
/[^ ]* [^ ]* (.*)/.match(first_line) do |m|
|
80
|
+
return m[1]
|
81
|
+
end
|
82
|
+
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
65
86
|
# @return [Boolean]
|
66
87
|
def ok?
|
67
88
|
@status == 0 || (@status >= 200 && @status <= 299)
|
data/lib/puppeteer/js_handle.rb
CHANGED
@@ -36,6 +36,14 @@ class Puppeteer::JSHandle
|
|
36
36
|
|
37
37
|
attr_reader :context, :remote_object
|
38
38
|
|
39
|
+
def inspect
|
40
|
+
values = %i[context remote_object disposed].map do |sym|
|
41
|
+
value = instance_variable_get(:"@#{sym}")
|
42
|
+
"@#{sym}=#{value}"
|
43
|
+
end
|
44
|
+
"#<Puppeteer::JSHandle #{values.join(' ')}>"
|
45
|
+
end
|
46
|
+
|
39
47
|
# @return [Puppeteer::ExecutionContext]
|
40
48
|
def execution_context
|
41
49
|
@context
|
@@ -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
|
@@ -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/version.rb
CHANGED
data/lib/puppeteer/wait_task.rb
CHANGED
@@ -9,7 +9,7 @@ class Puppeteer::WaitTask
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
def initialize(dom_world:, predicate_body:, title:, polling:, timeout:, args: [], binding_function: nil)
|
12
|
+
def initialize(dom_world:, predicate_body:, title:, polling:, timeout:, args: [], binding_function: nil, root: nil)
|
13
13
|
if polling.is_a?(String)
|
14
14
|
if polling != 'raf' && polling != 'mutation'
|
15
15
|
raise ArgumentError.new("Unknown polling option: #{polling}")
|
@@ -25,6 +25,7 @@ class Puppeteer::WaitTask
|
|
25
25
|
@dom_world = dom_world
|
26
26
|
@polling = polling
|
27
27
|
@timeout = timeout
|
28
|
+
@root = root
|
28
29
|
@predicate_body = "return (#{predicate_body})(...args);"
|
29
30
|
@args = args
|
30
31
|
@binding_function = binding_function
|
@@ -68,6 +69,7 @@ class Puppeteer::WaitTask
|
|
68
69
|
begin
|
69
70
|
success = context.evaluate_handle(
|
70
71
|
WAIT_FOR_PREDICATE_PAGE_FUNCTION,
|
72
|
+
@root,
|
71
73
|
@predicate_body,
|
72
74
|
@polling,
|
73
75
|
@timeout,
|
@@ -121,8 +123,9 @@ class Puppeteer::WaitTask
|
|
121
123
|
private define_async_method :async_rerun
|
122
124
|
|
123
125
|
WAIT_FOR_PREDICATE_PAGE_FUNCTION = <<~JAVASCRIPT
|
124
|
-
async function _(predicateBody, polling, timeout, ...args) {
|
126
|
+
async function _(root, predicateBody, polling, timeout, ...args) {
|
125
127
|
const predicate = new Function('...args', predicateBody);
|
128
|
+
root = root || document
|
126
129
|
let timedOut = false;
|
127
130
|
if (timeout)
|
128
131
|
setTimeout(() => (timedOut = true), timeout);
|
@@ -136,7 +139,7 @@ class Puppeteer::WaitTask
|
|
136
139
|
* @return {!Promise<*>}
|
137
140
|
*/
|
138
141
|
async function pollMutation() {
|
139
|
-
const success = await predicate(...args);
|
142
|
+
const success = await predicate(root, ...args);
|
140
143
|
if (success) return Promise.resolve(success);
|
141
144
|
let fulfill;
|
142
145
|
const result = new Promise((x) => (fulfill = x));
|
@@ -145,13 +148,13 @@ class Puppeteer::WaitTask
|
|
145
148
|
observer.disconnect();
|
146
149
|
fulfill();
|
147
150
|
}
|
148
|
-
const success = await predicate(...args);
|
151
|
+
const success = await predicate(root, ...args);
|
149
152
|
if (success) {
|
150
153
|
observer.disconnect();
|
151
154
|
fulfill(success);
|
152
155
|
}
|
153
156
|
});
|
154
|
-
observer.observe(
|
157
|
+
observer.observe(root, {
|
155
158
|
childList: true,
|
156
159
|
subtree: true,
|
157
160
|
attributes: true,
|
@@ -168,7 +171,7 @@ class Puppeteer::WaitTask
|
|
168
171
|
fulfill();
|
169
172
|
return;
|
170
173
|
}
|
171
|
-
const success = await predicate(...args);
|
174
|
+
const success = await predicate(root, ...args);
|
172
175
|
if (success) fulfill(success);
|
173
176
|
else requestAnimationFrame(onRaf);
|
174
177
|
}
|
@@ -183,7 +186,7 @@ class Puppeteer::WaitTask
|
|
183
186
|
fulfill();
|
184
187
|
return;
|
185
188
|
}
|
186
|
-
const success = await predicate(...args);
|
189
|
+
const success = await predicate(root, ...args);
|
187
190
|
if (success) fulfill(success);
|
188
191
|
else setTimeout(onTimeout, pollInterval);
|
189
192
|
}
|
data/lib/puppeteer.rb
CHANGED
@@ -47,6 +47,7 @@ require 'puppeteer/launcher'
|
|
47
47
|
require 'puppeteer/lifecycle_watcher'
|
48
48
|
require 'puppeteer/mouse'
|
49
49
|
require 'puppeteer/network_conditions'
|
50
|
+
require 'puppeteer/network_event_manager'
|
50
51
|
require 'puppeteer/network_manager'
|
51
52
|
require 'puppeteer/page'
|
52
53
|
require 'puppeteer/protocol_stream_reader'
|
data/puppeteer-ruby.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency 'concurrent-ruby', '~> 1.1.0'
|
25
25
|
spec.add_dependency 'websocket-driver', '>= 0.6.0'
|
26
26
|
spec.add_dependency 'mime-types', '>= 3.0'
|
27
|
-
spec.add_development_dependency 'bundler', '~> 2.
|
27
|
+
spec.add_development_dependency 'bundler', '~> 2.3.4'
|
28
28
|
spec.add_development_dependency 'chunky_png'
|
29
29
|
spec.add_development_dependency 'dry-inflector'
|
30
30
|
spec.add_development_dependency 'pry-byebug'
|
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency 'rollbar'
|
33
33
|
spec.add_development_dependency 'rspec', '~> 3.10.0 '
|
34
34
|
spec.add_development_dependency 'rspec_junit_formatter' # for CircleCI.
|
35
|
-
spec.add_development_dependency 'rubocop', '~> 1.
|
35
|
+
spec.add_development_dependency 'rubocop', '~> 1.24.0'
|
36
36
|
spec.add_development_dependency 'rubocop-rspec'
|
37
37
|
spec.add_development_dependency 'sinatra'
|
38
38
|
spec.add_development_dependency 'webrick'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puppeteer-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.40.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- YusukeIwaki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.
|
61
|
+
version: 2.3.4
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 2.
|
68
|
+
version: 2.3.4
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: chunky_png
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,14 +170,14 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 1.
|
173
|
+
version: 1.24.0
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: 1.
|
180
|
+
version: 1.24.0
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: rubocop-rspec
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -306,6 +306,7 @@ files:
|
|
306
306
|
- lib/puppeteer/mouse.rb
|
307
307
|
- lib/puppeteer/network_condition.rb
|
308
308
|
- lib/puppeteer/network_conditions.rb
|
309
|
+
- lib/puppeteer/network_event_manager.rb
|
309
310
|
- lib/puppeteer/network_manager.rb
|
310
311
|
- lib/puppeteer/page.rb
|
311
312
|
- lib/puppeteer/page/metrics.rb
|
@@ -346,7 +347,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
346
347
|
- !ruby/object:Gem::Version
|
347
348
|
version: '0'
|
348
349
|
requirements: []
|
349
|
-
rubygems_version: 3.
|
350
|
+
rubygems_version: 3.3.3
|
350
351
|
signing_key:
|
351
352
|
specification_version: 4
|
352
353
|
summary: A ruby port of puppeteer
|