puppeteer-ruby 0.35.1 → 0.37.2
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/CHANGELOG.md +71 -45
- data/README.md +69 -0
- data/docs/api_coverage.md +55 -45
- data/lib/puppeteer/browser.rb +2 -8
- data/lib/puppeteer/browser_context.rb +1 -0
- data/lib/puppeteer/browser_runner.rb +1 -1
- data/lib/puppeteer/concurrent_ruby_utils.rb +2 -2
- data/lib/puppeteer/coverage.rb +11 -2
- data/lib/puppeteer/define_async_method.rb +1 -1
- data/lib/puppeteer/devices.rb +132 -0
- data/lib/puppeteer/dom_world.rb +10 -9
- data/lib/puppeteer/element_handle/offset.rb +28 -0
- data/lib/puppeteer/element_handle/point.rb +11 -0
- data/lib/puppeteer/element_handle.rb +68 -7
- data/lib/puppeteer/frame.rb +4 -3
- data/lib/puppeteer/frame_manager.rb +2 -2
- data/lib/puppeteer/{request.rb → http_request.rb} +150 -21
- data/lib/puppeteer/{response.rb → http_response.rb} +2 -2
- data/lib/puppeteer/js_coverage.rb +28 -7
- data/lib/puppeteer/launcher/launch_options.rb +3 -3
- data/lib/puppeteer/lifecycle_watcher.rb +2 -2
- data/lib/puppeteer/mouse.rb +54 -1
- data/lib/puppeteer/network_condition.rb +12 -0
- data/lib/puppeteer/network_conditions.rb +24 -0
- data/lib/puppeteer/network_manager.rb +64 -18
- data/lib/puppeteer/page/metrics.rb +49 -0
- data/lib/puppeteer/page/screenshot_options.rb +3 -1
- data/lib/puppeteer/page.rb +166 -134
- data/lib/puppeteer/puppeteer.rb +5 -0
- data/lib/puppeteer/timeout_helper.rb +22 -0
- data/lib/puppeteer/tracing.rb +6 -1
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +1 -1
- data/lib/puppeteer/web_socket.rb +1 -0
- data/lib/puppeteer.rb +17 -14
- data/puppeteer-ruby.gemspec +1 -1
- metadata +11 -6
@@ -12,6 +12,14 @@ class Puppeteer::JSCoverage
|
|
12
12
|
attr_reader :url, :ranges, :text
|
13
13
|
end
|
14
14
|
|
15
|
+
class ItemWithRawScriptCoverage < Item
|
16
|
+
def initialize(url:, ranges:, text:, raw_script_coverage:)
|
17
|
+
super(url: url, ranges: ranges, text: text)
|
18
|
+
@raw_script_coverage = raw_script_coverage
|
19
|
+
end
|
20
|
+
attr_reader :raw_script_coverage
|
21
|
+
end
|
22
|
+
|
15
23
|
# @param client [Puppeteer::CDPSession]
|
16
24
|
def initialize(client)
|
17
25
|
@client = client
|
@@ -20,7 +28,10 @@ class Puppeteer::JSCoverage
|
|
20
28
|
@script_sources = {}
|
21
29
|
end
|
22
30
|
|
23
|
-
def start(
|
31
|
+
def start(
|
32
|
+
reset_on_navigation: nil,
|
33
|
+
report_anonymous_scripts: nil,
|
34
|
+
include_raw_script_coverage: nil)
|
24
35
|
raise 'JSCoverage is already enabled' if @enabled
|
25
36
|
|
26
37
|
@reset_on_navigation =
|
@@ -30,6 +41,7 @@ class Puppeteer::JSCoverage
|
|
30
41
|
true
|
31
42
|
end
|
32
43
|
@report_anonymous_scripts = report_anonymous_scripts || false
|
44
|
+
@include_raw_script_coverage = include_raw_script_coverage || false
|
33
45
|
@enabled = true
|
34
46
|
@script_urls.clear
|
35
47
|
@script_sources.clear
|
@@ -43,7 +55,7 @@ class Puppeteer::JSCoverage
|
|
43
55
|
await_all(
|
44
56
|
@client.async_send_message('Profiler.enable'),
|
45
57
|
@client.async_send_message('Profiler.startPreciseCoverage',
|
46
|
-
callCount:
|
58
|
+
callCount: @include_raw_script_coverage,
|
47
59
|
detailed: true,
|
48
60
|
),
|
49
61
|
@client.async_send_message('Debugger.enable'),
|
@@ -107,11 +119,20 @@ class Puppeteer::JSCoverage
|
|
107
119
|
end
|
108
120
|
end
|
109
121
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
122
|
+
if @include_raw_script_coverage
|
123
|
+
coverage << ItemWithRawScriptCoverage.new(
|
124
|
+
url: url,
|
125
|
+
ranges: convert_to_disjoint_ranges(flatten_ranges),
|
126
|
+
text: text,
|
127
|
+
raw_script_coverage: entry,
|
128
|
+
)
|
129
|
+
else
|
130
|
+
coverage << Item.new(
|
131
|
+
url: url,
|
132
|
+
ranges: convert_to_disjoint_ranges(flatten_ranges),
|
133
|
+
text: text,
|
134
|
+
)
|
135
|
+
end
|
115
136
|
end
|
116
137
|
|
117
138
|
coverage
|
@@ -35,9 +35,9 @@ module Puppeteer::Launcher
|
|
35
35
|
@channel = options[:channel]
|
36
36
|
@executable_path = options[:executable_path]
|
37
37
|
@ignore_default_args = options[:ignore_default_args] || false
|
38
|
-
@handle_SIGINT = options
|
39
|
-
@handle_SIGTERM = options
|
40
|
-
@handle_SIGHUP = options
|
38
|
+
@handle_SIGINT = options.fetch(:handle_SIGINT, true)
|
39
|
+
@handle_SIGTERM = options.fetch(:handle_SIGTERM, true)
|
40
|
+
@handle_SIGHUP = options.fetch(:handle_SIGHUP, true)
|
41
41
|
@timeout = options[:timeout] || 30000
|
42
42
|
@dumpio = options[:dumpio] || false
|
43
43
|
@env = options[:env] || ENV
|
@@ -88,7 +88,7 @@ class Puppeteer::LifecycleWatcher
|
|
88
88
|
check_lifecycle_complete
|
89
89
|
end
|
90
90
|
|
91
|
-
# @param [Puppeteer::
|
91
|
+
# @param [Puppeteer::HTTPRequest] request
|
92
92
|
def handle_request(request)
|
93
93
|
return if request.frame != @frame || !request.navigation_request?
|
94
94
|
@navigation_request = request
|
@@ -103,7 +103,7 @@ class Puppeteer::LifecycleWatcher
|
|
103
103
|
check_lifecycle_complete
|
104
104
|
end
|
105
105
|
|
106
|
-
# @return [Puppeteer::
|
106
|
+
# @return [Puppeteer::HTTPResponse]
|
107
107
|
def navigation_response
|
108
108
|
if_present(@navigation_request) do |request|
|
109
109
|
request.response
|
data/lib/puppeteer/mouse.rb
CHANGED
@@ -94,6 +94,8 @@ class Puppeteer::Mouse
|
|
94
94
|
)
|
95
95
|
end
|
96
96
|
|
97
|
+
define_async_method :async_up
|
98
|
+
|
97
99
|
# Dispatches a `mousewheel` event.
|
98
100
|
#
|
99
101
|
# @param delta_x [Integer]
|
@@ -110,5 +112,56 @@ class Puppeteer::Mouse
|
|
110
112
|
)
|
111
113
|
end
|
112
114
|
|
113
|
-
|
115
|
+
def drag(start, target)
|
116
|
+
promise = resolvable_future do |f|
|
117
|
+
@client.once('Input.dragIntercepted') do |event|
|
118
|
+
f.fulfill(event['data'])
|
119
|
+
end
|
120
|
+
end
|
121
|
+
move(start.x, start.y)
|
122
|
+
down
|
123
|
+
move(target.x, target.y)
|
124
|
+
promise.value!
|
125
|
+
end
|
126
|
+
|
127
|
+
def drag_enter(target, data)
|
128
|
+
@client.send_message('Input.dispatchDragEvent',
|
129
|
+
type: 'dragEnter',
|
130
|
+
x: target.x,
|
131
|
+
y: target.y,
|
132
|
+
modifiers: @keyboard.modifiers,
|
133
|
+
data: data,
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
def drag_over(target, data)
|
138
|
+
@client.send_message('Input.dispatchDragEvent',
|
139
|
+
type: 'dragOver',
|
140
|
+
x: target.x,
|
141
|
+
y: target.y,
|
142
|
+
modifiers: @keyboard.modifiers,
|
143
|
+
data: data,
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def drop(target, data)
|
148
|
+
@client.send_message('Input.dispatchDragEvent',
|
149
|
+
type: 'drop',
|
150
|
+
x: target.x,
|
151
|
+
y: target.y,
|
152
|
+
modifiers: @keyboard.modifiers,
|
153
|
+
data: data,
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
def drag_and_drop(start, target, delay: nil)
|
158
|
+
data = drag(start, target)
|
159
|
+
drag_enter(target, data)
|
160
|
+
drag_over(target, data)
|
161
|
+
if delay
|
162
|
+
sleep(delay / 1000.0)
|
163
|
+
end
|
164
|
+
drop(target, data)
|
165
|
+
up
|
166
|
+
end
|
114
167
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Puppeteer::NetworkCondition
|
2
|
+
# @param download [Number] Download speed (bytes/s)
|
3
|
+
# @param upload [Number] Upload speed (bytes/s)
|
4
|
+
# @param latency [Number] Latency (ms)
|
5
|
+
def initialize(download:, upload:, latency:)
|
6
|
+
@download = download
|
7
|
+
@upload = upload
|
8
|
+
@latency = latency
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :download, :upload, :latency
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative './network_condition'
|
2
|
+
|
3
|
+
Puppeteer::NETWORK_CONDITIONS = {
|
4
|
+
'Slow 3G' => Puppeteer::NetworkCondition.new(
|
5
|
+
download: ((500 * 1000) / 8) * 0.8,
|
6
|
+
upload: ((500 * 1000) / 8) * 0.8,
|
7
|
+
latency: 400 * 5,
|
8
|
+
),
|
9
|
+
'Fast 3G' => Puppeteer::NetworkCondition.new(
|
10
|
+
download: ((1.6 * 1000 * 1000) / 8) * 0.9,
|
11
|
+
upload: ((750 * 1000) / 8) * 0.9,
|
12
|
+
latency: 150 * 3.75,
|
13
|
+
),
|
14
|
+
}
|
15
|
+
|
16
|
+
module Puppeteer::NetworkConditions
|
17
|
+
module_function def slow_3g
|
18
|
+
Puppeteer::NETWORK_CONDITIONS['Slow 3G']
|
19
|
+
end
|
20
|
+
|
21
|
+
module_function def fast_3g
|
22
|
+
Puppeteer::NETWORK_CONDITIONS['Fast 3G']
|
23
|
+
end
|
24
|
+
end
|
@@ -13,6 +13,46 @@ class Puppeteer::NetworkManager
|
|
13
13
|
attr_reader :username, :password
|
14
14
|
end
|
15
15
|
|
16
|
+
class InternalNetworkCondition
|
17
|
+
attr_writer :offline, :upload, :download, :latency
|
18
|
+
|
19
|
+
def initialize(client)
|
20
|
+
@client = client
|
21
|
+
@offline = false
|
22
|
+
@upload = -1
|
23
|
+
@download = -1
|
24
|
+
@latency = 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def offline_mode=(value)
|
28
|
+
return if @offline == value
|
29
|
+
@offline = value
|
30
|
+
update_network_conditions
|
31
|
+
end
|
32
|
+
|
33
|
+
def network_condition=(network_condition)
|
34
|
+
if network_condition
|
35
|
+
@upload = network_condition.upload
|
36
|
+
@download = network_condition.download
|
37
|
+
@latency = network_condition.latency
|
38
|
+
else
|
39
|
+
@upload = -1
|
40
|
+
@download = -1
|
41
|
+
@latency = 0
|
42
|
+
end
|
43
|
+
update_network_conditions
|
44
|
+
end
|
45
|
+
|
46
|
+
private def update_network_conditions
|
47
|
+
@client.send_message('Network.emulateNetworkConditions',
|
48
|
+
offline: @offline,
|
49
|
+
latency: @latency,
|
50
|
+
downloadThroughput: @download,
|
51
|
+
uploadThroughput: @upload,
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
16
56
|
# @param {!Puppeteer.CDPSession} client
|
17
57
|
# @param {boolean} ignoreHTTPSErrors
|
18
58
|
# @param {!Puppeteer.FrameManager} frameManager
|
@@ -29,13 +69,12 @@ class Puppeteer::NetworkManager
|
|
29
69
|
|
30
70
|
@extra_http_headers = {}
|
31
71
|
|
32
|
-
@offline = false
|
33
|
-
|
34
72
|
@attempted_authentications = Set.new
|
35
73
|
@user_request_interception_enabled = false
|
36
74
|
@protocol_request_interception_enabled = false
|
37
75
|
@user_cache_disabled = false
|
38
76
|
@request_id_to_interception_id = {}
|
77
|
+
@internal_network_condition = InternalNetworkCondition.new(@client)
|
39
78
|
|
40
79
|
@client.on_event('Fetch.requestPaused') do |event|
|
41
80
|
handle_request_paused(event)
|
@@ -94,21 +133,23 @@ class Puppeteer::NetworkManager
|
|
94
133
|
|
95
134
|
# @param value [TrueClass|FalseClass]
|
96
135
|
def offline_mode=(value)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
downloadThroughput: -1,
|
104
|
-
uploadThroughput: -1,
|
105
|
-
)
|
136
|
+
@internal_network_condition.offline_mode=(value)
|
137
|
+
end
|
138
|
+
|
139
|
+
# @param network_condition [Puppeteer::NetworkCondition|nil]
|
140
|
+
def emulate_network_conditions(network_condition)
|
141
|
+
@internal_network_condition.network_condition = network_condition
|
106
142
|
end
|
107
143
|
|
108
144
|
# @param user_agent [String]
|
109
|
-
|
110
|
-
|
145
|
+
# @param user_agent_metadata [Hash]
|
146
|
+
def set_user_agent(user_agent, user_agent_metadata = nil)
|
147
|
+
@client.send_message('Network.setUserAgentOverride', {
|
148
|
+
userAgent: user_agent,
|
149
|
+
userAgentMetadata: user_agent_metadata,
|
150
|
+
}.compact)
|
111
151
|
end
|
152
|
+
alias_method :user_agent=, :set_user_agent
|
112
153
|
|
113
154
|
def cache_enabled=(enabled)
|
114
155
|
@user_cache_disabled = !enabled
|
@@ -210,9 +251,14 @@ class Puppeteer::NetworkManager
|
|
210
251
|
end
|
211
252
|
end
|
212
253
|
frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
|
213
|
-
request = Puppeteer::
|
254
|
+
request = Puppeteer::HTTPRequest.new(@client, frame, interception_id, @user_request_interception_enabled, event, redirect_chain)
|
214
255
|
@request_id_to_request[event['requestId']] = request
|
215
256
|
emit_event(NetworkManagerEmittedEvents::Request, request)
|
257
|
+
begin
|
258
|
+
request.finalize_interceptions
|
259
|
+
rescue => err
|
260
|
+
debug_puts(err)
|
261
|
+
end
|
216
262
|
end
|
217
263
|
|
218
264
|
private def handle_request_served_from_cache(event)
|
@@ -221,13 +267,13 @@ class Puppeteer::NetworkManager
|
|
221
267
|
end
|
222
268
|
end
|
223
269
|
|
224
|
-
# @param request [Puppeteer::
|
270
|
+
# @param request [Puppeteer::HTTPRequest]
|
225
271
|
# @param response_payload [Hash]
|
226
272
|
private def handle_request_redirect(request, response_payload)
|
227
|
-
response = Puppeteer::
|
273
|
+
response = Puppeteer::HTTPResponse.new(@client, request, response_payload)
|
228
274
|
request.internal.response = response
|
229
275
|
request.internal.redirect_chain << request
|
230
|
-
response.internal.body_loaded_promise.reject(Puppeteer::
|
276
|
+
response.internal.body_loaded_promise.reject(Puppeteer::HTTPResponse::Redirected.new)
|
231
277
|
@request_id_to_request.delete(request.internal.request_id)
|
232
278
|
@attempted_authentications.delete(request.internal.interception_id)
|
233
279
|
emit_event(NetworkManagerEmittedEvents::Response, response)
|
@@ -240,7 +286,7 @@ class Puppeteer::NetworkManager
|
|
240
286
|
# FileUpload sends a response without a matching request.
|
241
287
|
return unless request
|
242
288
|
|
243
|
-
response = Puppeteer::
|
289
|
+
response = Puppeteer::HTTPResponse.new(@client, request, event['response'])
|
244
290
|
request.internal.response = response
|
245
291
|
emit_event(NetworkManagerEmittedEvents::Response, response)
|
246
292
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Puppeteer::Page
|
2
|
+
class Metrics
|
3
|
+
SUPPORTED_KEYS = Set.new([
|
4
|
+
'Timestamp',
|
5
|
+
'Documents',
|
6
|
+
'Frames',
|
7
|
+
'JSEventListeners',
|
8
|
+
'Nodes',
|
9
|
+
'LayoutCount',
|
10
|
+
'RecalcStyleCount',
|
11
|
+
'LayoutDuration',
|
12
|
+
'RecalcStyleDuration',
|
13
|
+
'ScriptDuration',
|
14
|
+
'TaskDuration',
|
15
|
+
'JSHeapUsedSize',
|
16
|
+
'JSHeapTotalSize',
|
17
|
+
]).freeze
|
18
|
+
|
19
|
+
SUPPORTED_KEYS.each do |key|
|
20
|
+
attr_reader key
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param metrics_result [Hash] response for Performance.getMetrics
|
24
|
+
def initialize(metrics_response)
|
25
|
+
metrics_response.each do |metric|
|
26
|
+
if SUPPORTED_KEYS.include?(metric['name'])
|
27
|
+
instance_variable_set(:"@#{metric['name']}", metric['value'])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](key)
|
33
|
+
if SUPPORTED_KEYS.include?(key.to_s)
|
34
|
+
instance_variable_get(:"@#{key}")
|
35
|
+
else
|
36
|
+
raise ArgumentError.new("invalid metric key specified: #{key}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class MetricsEvent
|
42
|
+
def initialize(metrics_event)
|
43
|
+
@title = metrics_event['title']
|
44
|
+
@metrics = Metrics.new(metrics_event['metrics'])
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :title, :metrics
|
48
|
+
end
|
49
|
+
end
|
@@ -15,7 +15,7 @@ class Puppeteer::Page
|
|
15
15
|
# @params options [Hash]
|
16
16
|
def initialize(options)
|
17
17
|
if options[:type]
|
18
|
-
unless [:png, :jpeg].include?(options[:type].to_sym)
|
18
|
+
unless [:png, :jpeg, :webp].include?(options[:type].to_sym)
|
19
19
|
raise ArgumentError.new("Unknown options.type value: #{options[:type]}")
|
20
20
|
end
|
21
21
|
@type = options[:type]
|
@@ -25,6 +25,8 @@ class Puppeteer::Page
|
|
25
25
|
@type = 'png'
|
26
26
|
elsif mime_types.include?('image/jpeg')
|
27
27
|
@type = 'jpeg'
|
28
|
+
elsif mime_types.include?('image/webp')
|
29
|
+
@type = 'webp'
|
28
30
|
else
|
29
31
|
raise ArgumentError.new("Unsupported screenshot mime type resolved: #{mime_types}, path: #{options[:path]}")
|
30
32
|
end
|