puppeteer-ruby 0.34.3 → 0.37.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/CHANGELOG.md +69 -43
- data/README.md +78 -11
- data/docs/api_coverage.md +27 -6
- data/lib/puppeteer/browser.rb +2 -8
- data/lib/puppeteer/browser_context.rb +1 -0
- 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 +3 -2
- data/lib/puppeteer/js_coverage.rb +28 -7
- data/lib/puppeteer/launcher/chrome.rb +64 -4
- data/lib/puppeteer/launcher/firefox.rb +48 -4
- data/lib/puppeteer/launcher/launch_options.rb +2 -1
- data/lib/puppeteer/launcher.rb +0 -1
- 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 +47 -11
- data/lib/puppeteer/page/metrics.rb +49 -0
- data/lib/puppeteer/page/screenshot_options.rb +3 -1
- data/lib/puppeteer/page.rb +147 -93
- data/lib/puppeteer/puppeteer.rb +10 -2
- 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/web_socket.rb +1 -0
- data/lib/puppeteer-ruby.rb +2 -0
- data/lib/puppeteer.rb +15 -12
- data/puppeteer-ruby.gemspec +1 -1
- metadata +10 -7
- data/Dockerfile +0 -9
- data/docker-compose.yml +0 -34
- data/lib/puppeteer/launcher/base.rb +0 -66
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,15 +133,12 @@ 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]
|
@@ -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
|
data/lib/puppeteer/page.rb
CHANGED
@@ -2,11 +2,13 @@ require 'base64'
|
|
2
2
|
require 'json'
|
3
3
|
require "stringio"
|
4
4
|
|
5
|
+
require_relative './page/metrics'
|
5
6
|
require_relative './page/pdf_options'
|
6
7
|
require_relative './page/screenshot_options'
|
7
8
|
require_relative './page/screenshot_task_queue'
|
8
9
|
|
9
10
|
class Puppeteer::Page
|
11
|
+
include Puppeteer::DebugPrint
|
10
12
|
include Puppeteer::EventCallbackable
|
11
13
|
include Puppeteer::IfPresent
|
12
14
|
using Puppeteer::DefineAsyncMethod
|
@@ -46,6 +48,8 @@ class Puppeteer::Page
|
|
46
48
|
@screenshot_task_queue = ScreenshotTaskQueue.new
|
47
49
|
|
48
50
|
@workers = {}
|
51
|
+
@user_drag_interception_enabled = false
|
52
|
+
|
49
53
|
@client.on_event('Target.attachedToTarget') do |event|
|
50
54
|
if event['targetInfo']['type'] != 'worker'
|
51
55
|
# If we don't detach from service workers, they will never die.
|
@@ -102,7 +106,9 @@ class Puppeteer::Page
|
|
102
106
|
@client.on('Runtime.consoleAPICalled') do |event|
|
103
107
|
handle_console_api(event)
|
104
108
|
end
|
105
|
-
|
109
|
+
@client.on('Runtime.bindingCalled') do |event|
|
110
|
+
handle_binding_called(event)
|
111
|
+
end
|
106
112
|
@client.on_event('Page.javascriptDialogOpening') do |event|
|
107
113
|
handle_dialog_opening(event)
|
108
114
|
end
|
@@ -112,7 +118,9 @@ class Puppeteer::Page
|
|
112
118
|
@client.on_event('Inspector.targetCrashed') do |event|
|
113
119
|
handle_target_crashed
|
114
120
|
end
|
115
|
-
|
121
|
+
@client.on_event('Performance.metrics') do |event|
|
122
|
+
emit_event(PageEmittedEvents::Metrics, MetricsEvent.new(event))
|
123
|
+
end
|
116
124
|
@client.on_event('Log.entryAdded') do |event|
|
117
125
|
handle_log_entry_added(event)
|
118
126
|
end
|
@@ -134,6 +142,11 @@ class Puppeteer::Page
|
|
134
142
|
)
|
135
143
|
end
|
136
144
|
|
145
|
+
def drag_interception_enabled?
|
146
|
+
@user_drag_interception_enabled
|
147
|
+
end
|
148
|
+
alias_method :drag_interception_enabled, :drag_interception_enabled?
|
149
|
+
|
137
150
|
# @param event_name [Symbol]
|
138
151
|
def on(event_name, &block)
|
139
152
|
unless PageEmittedEvents.values.include?(event_name.to_s)
|
@@ -266,10 +279,20 @@ class Puppeteer::Page
|
|
266
279
|
@frame_manager.network_manager.request_interception = value
|
267
280
|
end
|
268
281
|
|
282
|
+
def drag_interception_enabled=(enabled)
|
283
|
+
@user_drag_interception_enabled = enabled
|
284
|
+
@client.send_message('Input.setInterceptDrags', enabled: enabled)
|
285
|
+
end
|
286
|
+
|
269
287
|
def offline_mode=(enabled)
|
270
288
|
@frame_manager.network_manager.offline_mode = enabled
|
271
289
|
end
|
272
290
|
|
291
|
+
# @param network_condition [Puppeteer::NetworkCondition|nil]
|
292
|
+
def emulate_network_conditions(network_condition)
|
293
|
+
@frame_manager.network_manager.emulate_network_conditions(network_condition)
|
294
|
+
end
|
295
|
+
|
273
296
|
# @param {number} timeout
|
274
297
|
def default_navigation_timeout=(timeout)
|
275
298
|
@timeout_settings.default_navigation_timeout = timeout
|
@@ -381,8 +404,9 @@ class Puppeteer::Page
|
|
381
404
|
# @param path [String?]
|
382
405
|
# @param content [String?]
|
383
406
|
# @param type [String?]
|
384
|
-
|
385
|
-
|
407
|
+
# @param id [String?]
|
408
|
+
def add_script_tag(url: nil, path: nil, content: nil, type: nil, id: nil)
|
409
|
+
main_frame.add_script_tag(url: url, path: path, content: content, type: type, id: id)
|
386
410
|
end
|
387
411
|
|
388
412
|
# @param url [String?]
|
@@ -392,6 +416,51 @@ class Puppeteer::Page
|
|
392
416
|
main_frame.add_style_tag(url: url, path: path, content: content)
|
393
417
|
end
|
394
418
|
|
419
|
+
# @param name [String]
|
420
|
+
# @param puppeteer_function [Proc]
|
421
|
+
def expose_function(name, puppeteer_function)
|
422
|
+
if @page_bindings[name]
|
423
|
+
raise ArgumentError.new("Failed to add page binding with name `#{name}` already exists!")
|
424
|
+
end
|
425
|
+
@page_bindings[name] = puppeteer_function
|
426
|
+
|
427
|
+
add_page_binding = <<~JAVASCRIPT
|
428
|
+
function (type, bindingName) {
|
429
|
+
/* Cast window to any here as we're about to add properties to it
|
430
|
+
* via win[bindingName] which TypeScript doesn't like.
|
431
|
+
*/
|
432
|
+
const win = window;
|
433
|
+
const binding = win[bindingName];
|
434
|
+
|
435
|
+
win[bindingName] = (...args) => {
|
436
|
+
const me = window[bindingName];
|
437
|
+
let callbacks = me.callbacks;
|
438
|
+
if (!callbacks) {
|
439
|
+
callbacks = new Map();
|
440
|
+
me.callbacks = callbacks;
|
441
|
+
}
|
442
|
+
const seq = (me.lastSeq || 0) + 1;
|
443
|
+
me.lastSeq = seq;
|
444
|
+
const promise = new Promise((resolve, reject) =>
|
445
|
+
callbacks.set(seq, { resolve, reject })
|
446
|
+
);
|
447
|
+
binding(JSON.stringify({ type, name: bindingName, seq, args }));
|
448
|
+
return promise;
|
449
|
+
};
|
450
|
+
}
|
451
|
+
JAVASCRIPT
|
452
|
+
|
453
|
+
source = JavaScriptFunction.new(add_page_binding, ['exposedFun', name]).source
|
454
|
+
@client.send_message('Runtime.addBinding', name: name)
|
455
|
+
@client.send_message('Page.addScriptToEvaluateOnNewDocument', source: source)
|
456
|
+
|
457
|
+
promises = @frame_manager.frames.map do |frame|
|
458
|
+
frame.async_evaluate("() => #{source}")
|
459
|
+
end
|
460
|
+
await_all(*promises)
|
461
|
+
|
462
|
+
nil
|
463
|
+
end
|
395
464
|
# /**
|
396
465
|
# * @param {string} name
|
397
466
|
# * @param {Function} puppeteerFunction
|
@@ -440,36 +509,10 @@ class Puppeteer::Page
|
|
440
509
|
@frame_manager.network_manager.user_agent = user_agent
|
441
510
|
end
|
442
511
|
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
# const response = await this._client.send('Performance.getMetrics');
|
448
|
-
# return this._buildMetricsObject(response.metrics);
|
449
|
-
# }
|
450
|
-
|
451
|
-
# /**
|
452
|
-
# * @param {!Protocol.Performance.metricsPayload} event
|
453
|
-
# */
|
454
|
-
# _emitMetrics(event) {
|
455
|
-
# this.emit(PageEmittedEvents::Metrics, {
|
456
|
-
# title: event.title,
|
457
|
-
# metrics: this._buildMetricsObject(event.metrics)
|
458
|
-
# });
|
459
|
-
# }
|
460
|
-
|
461
|
-
# /**
|
462
|
-
# * @param {?Array<!Protocol.Performance.Metric>} metrics
|
463
|
-
# * @return {!Metrics}
|
464
|
-
# */
|
465
|
-
# _buildMetricsObject(metrics) {
|
466
|
-
# const result = {};
|
467
|
-
# for (const metric of metrics || []) {
|
468
|
-
# if (supportedMetrics.has(metric.name))
|
469
|
-
# result[metric.name] = metric.value;
|
470
|
-
# }
|
471
|
-
# return result;
|
472
|
-
# }
|
512
|
+
def metrics
|
513
|
+
response = @client.send_message('Performance.getMetrics')
|
514
|
+
Metrics.new(response['metrics'])
|
515
|
+
end
|
473
516
|
|
474
517
|
class PageError < StandardError ; end
|
475
518
|
|
@@ -506,56 +549,51 @@ class Puppeteer::Page
|
|
506
549
|
add_console_message(event['type'], values, event['stackTrace'])
|
507
550
|
end
|
508
551
|
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
# expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
523
|
-
# }
|
524
|
-
# this._client.send('Runtime.evaluate', { expression, contextId: event.executionContextId }).catch(debugError);
|
525
|
-
|
526
|
-
# /**
|
527
|
-
# * @param {string} name
|
528
|
-
# * @param {number} seq
|
529
|
-
# * @param {*} result
|
530
|
-
# */
|
531
|
-
# function deliverResult(name, seq, result) {
|
532
|
-
# window[name]['callbacks'].get(seq).resolve(result);
|
533
|
-
# window[name]['callbacks'].delete(seq);
|
534
|
-
# }
|
552
|
+
def handle_binding_called(event)
|
553
|
+
execution_context_id = event['executionContextId']
|
554
|
+
payload =
|
555
|
+
begin
|
556
|
+
JSON.parse(event['payload'])
|
557
|
+
rescue
|
558
|
+
# The binding was either called by something in the page or it was
|
559
|
+
# called before our wrapper was initialized.
|
560
|
+
return
|
561
|
+
end
|
562
|
+
name = payload['name']
|
563
|
+
seq = payload['seq']
|
564
|
+
args = payload['args']
|
535
565
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
# * @param {string} message
|
540
|
-
# * @param {string} stack
|
541
|
-
# */
|
542
|
-
# function deliverError(name, seq, message, stack) {
|
543
|
-
# const error = new Error(message);
|
544
|
-
# error.stack = stack;
|
545
|
-
# window[name]['callbacks'].get(seq).reject(error);
|
546
|
-
# window[name]['callbacks'].delete(seq);
|
547
|
-
# }
|
566
|
+
if payload['type'] != 'exposedFun' || !@page_bindings[name]
|
567
|
+
return
|
568
|
+
end
|
548
569
|
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
570
|
+
expression =
|
571
|
+
begin
|
572
|
+
result = @page_bindings[name].call(*args)
|
573
|
+
|
574
|
+
deliver_result = <<~JAVASCRIPT
|
575
|
+
function (name, seq, result) {
|
576
|
+
window[name].callbacks.get(seq).resolve(result);
|
577
|
+
window[name].callbacks.delete(seq);
|
578
|
+
}
|
579
|
+
JAVASCRIPT
|
580
|
+
|
581
|
+
JavaScriptFunction.new(deliver_result, [name, seq, result]).source
|
582
|
+
rescue => err
|
583
|
+
deliver_error = <<~JAVASCRIPT
|
584
|
+
function (name, seq, message) {
|
585
|
+
const error = new Error(message);
|
586
|
+
window[name].callbacks.get(seq).reject(error);
|
587
|
+
window[name].callbacks.delete(seq);
|
588
|
+
}
|
589
|
+
JAVASCRIPT
|
590
|
+
JavaScriptFunction.new(deliver_error, [name, seq, err.message]).source
|
591
|
+
end
|
592
|
+
|
593
|
+
@client.async_send_message('Runtime.evaluate', expression: expression, contextId: execution_context_id).rescue do |error|
|
594
|
+
debug_puts(error)
|
595
|
+
end
|
596
|
+
end
|
559
597
|
|
560
598
|
private def add_console_message(type, args, stack_trace)
|
561
599
|
text_tokens = args.map { |arg| arg.remote_object.value }
|
@@ -631,10 +669,9 @@ class Puppeteer::Page
|
|
631
669
|
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
632
670
|
# @return [Puppeteer::Response]
|
633
671
|
def reload(timeout: nil, wait_until: nil)
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
).first
|
672
|
+
wait_for_navigation(timeout: timeout, wait_until: wait_until) do
|
673
|
+
@client.send_message('Page.reload')
|
674
|
+
end
|
638
675
|
end
|
639
676
|
|
640
677
|
def wait_for_navigation(timeout: nil, wait_until: nil)
|
@@ -760,10 +797,9 @@ class Puppeteer::Page
|
|
760
797
|
entries = history['entries']
|
761
798
|
index = history['currentIndex'] + delta
|
762
799
|
if_present(entries[index]) do |entry|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
)
|
800
|
+
wait_for_navigation(timeout: timeout, wait_until: wait_until) do
|
801
|
+
@client.send_message('Page.navigateToHistoryEntry', entryId: entry['id'])
|
802
|
+
end
|
767
803
|
end
|
768
804
|
end
|
769
805
|
|
@@ -799,6 +835,15 @@ class Puppeteer::Page
|
|
799
835
|
@client.send_message('Emulation.setEmulatedMedia', media: media_type_str)
|
800
836
|
end
|
801
837
|
|
838
|
+
# @param factor [Number|nil] Factor at which the CPU will be throttled (2x, 2.5x. 3x, ...). Passing `nil` disables cpu throttling.
|
839
|
+
def emulate_cpu_throttling(factor)
|
840
|
+
if factor.nil? || factor >= 1
|
841
|
+
@client.send_message('Emulation.setCPUThrottlingRate', rate: factor || 1)
|
842
|
+
else
|
843
|
+
raise ArgumentError.new('Throttling rate should be greater or equal to 1')
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
802
847
|
# @param features [Array]
|
803
848
|
def emulate_media_features(features)
|
804
849
|
if features.nil?
|
@@ -921,7 +966,7 @@ class Puppeteer::Page
|
|
921
966
|
main_frame.title
|
922
967
|
end
|
923
968
|
|
924
|
-
# @param type [String] "png"|"jpeg"
|
969
|
+
# @param type [String] "png"|"jpeg"|"webp"
|
925
970
|
# @param path [String]
|
926
971
|
# @param full_page [Boolean]
|
927
972
|
# @param clip [Hash]
|
@@ -1015,11 +1060,20 @@ class Puppeteer::Page
|
|
1015
1060
|
|
1016
1061
|
# @return [Enumerable<String>]
|
1017
1062
|
def create_pdf_stream(options = {})
|
1063
|
+
timeout_helper = Puppeteer::TimeoutHelper.new('Page.printToPDF',
|
1064
|
+
timeout_ms: options[:timeout],
|
1065
|
+
default_timeout_ms: 30000)
|
1018
1066
|
pdf_options = PDFOptions.new(options)
|
1019
1067
|
omit_background = options[:omit_background]
|
1020
1068
|
set_transparent_background_color if omit_background
|
1021
|
-
result =
|
1022
|
-
|
1069
|
+
result =
|
1070
|
+
begin
|
1071
|
+
timeout_helper.with_timeout do
|
1072
|
+
@client.send_message('Page.printToPDF', pdf_options.page_print_args)
|
1073
|
+
end
|
1074
|
+
ensure
|
1075
|
+
reset_default_background_color if omit_background
|
1076
|
+
end
|
1023
1077
|
|
1024
1078
|
Puppeteer::ProtocolStreamReader.new(
|
1025
1079
|
client: @client,
|
data/lib/puppeteer/puppeteer.rb
CHANGED
@@ -11,6 +11,7 @@ class Puppeteer::Puppeteer
|
|
11
11
|
class NoViewport ; end
|
12
12
|
|
13
13
|
# @param product [String]
|
14
|
+
# @param channel [String|Symbol]
|
14
15
|
# @param executable_path [String]
|
15
16
|
# @param ignore_default_args [Array<String>|nil]
|
16
17
|
# @param handle_SIGINT [Boolean]
|
@@ -30,6 +31,7 @@ class Puppeteer::Puppeteer
|
|
30
31
|
# @return [Puppeteer::Browser]
|
31
32
|
def launch(
|
32
33
|
product: nil,
|
34
|
+
channel: nil,
|
33
35
|
executable_path: nil,
|
34
36
|
ignore_default_args: nil,
|
35
37
|
handle_SIGINT: nil,
|
@@ -48,6 +50,7 @@ class Puppeteer::Puppeteer
|
|
48
50
|
slow_mo: nil
|
49
51
|
)
|
50
52
|
options = {
|
53
|
+
channel: channel&.to_s,
|
51
54
|
executable_path: executable_path,
|
52
55
|
ignore_default_args: ignore_default_args,
|
53
56
|
handle_SIGINT: handle_SIGINT,
|
@@ -118,8 +121,8 @@ class Puppeteer::Puppeteer
|
|
118
121
|
end
|
119
122
|
|
120
123
|
# @return [String]
|
121
|
-
def executable_path
|
122
|
-
launcher.executable_path
|
124
|
+
def executable_path(channel: nil)
|
125
|
+
launcher.executable_path(channel: channel)
|
123
126
|
end
|
124
127
|
|
125
128
|
private def launcher
|
@@ -146,6 +149,11 @@ class Puppeteer::Puppeteer
|
|
146
149
|
# # ???
|
147
150
|
# end
|
148
151
|
|
152
|
+
# @return [Puppeteer::NetworkConditions]
|
153
|
+
def network_conditions
|
154
|
+
Puppeteer::NetworkConditions
|
155
|
+
end
|
156
|
+
|
149
157
|
# @param args [Array<String>]
|
150
158
|
# @param user_data_dir [String]
|
151
159
|
# @param devtools [Boolean]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
class Puppeteer::TimeoutHelper
|
4
|
+
# @param timeout_ms [String|Integer|nil]
|
5
|
+
# @param default_timeout_ms [Integer]
|
6
|
+
def initialize(task_name, timeout_ms:, default_timeout_ms:)
|
7
|
+
@task_name = task_name
|
8
|
+
@timeout_ms = (timeout_ms || default_timeout_ms).to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_timeout(&block)
|
12
|
+
if @timeout_ms > 0
|
13
|
+
begin
|
14
|
+
Timeout.timeout(@timeout_ms / 1000.0, &block)
|
15
|
+
rescue Timeout::Error
|
16
|
+
raise Puppeteer::TimeoutError.new("waiting for #{@task_name} failed: timeout #{@timeout_ms}ms exceeded")
|
17
|
+
end
|
18
|
+
else
|
19
|
+
block.call
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|