puppeteer-ruby 0.34.3 → 0.37.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|