puppeteer-ruby 0.40.7 → 0.41.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 +5 -1
- data/docs/api_coverage.md +3 -2
- data/lib/puppeteer/browser.rb +34 -2
- data/lib/puppeteer/browser_context.rb +3 -1
- data/lib/puppeteer/element_handle.rb +116 -14
- data/lib/puppeteer/execution_context.rb +1 -1
- data/lib/puppeteer/frame.rb +9 -0
- data/lib/puppeteer/frame_manager.rb +23 -13
- data/lib/puppeteer/launcher/browser_options.rb +12 -1
- data/lib/puppeteer/launcher/chrome.rb +7 -1
- data/lib/puppeteer/launcher/firefox.rb +4 -0
- data/lib/puppeteer/lifecycle_watcher.rb +9 -10
- data/lib/puppeteer/page/screenshot_options.rb +63 -9
- data/lib/puppeteer/page.rb +31 -13
- data/lib/puppeteer/target.rb +11 -6
- data/lib/puppeteer/version.rb +1 -1
- data/puppeteer-ruby.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccfb160c40e2ef032c0da17ab4b1cc9fe610d4caf42f236042ea0757c9e270e7
|
4
|
+
data.tar.gz: 95a3a457aa850116bc3062e9507ef53bd10e1d451ebeb4703cf9a6f5d663b9c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b65da9f89cc8cfeea05e4e0835f919aab382f47e380d27c1e0a439c70cb0b0b7d9e8519a3361ad545e0c5115216d698740e46f20c96960cf96496ee22ac374ea
|
7
|
+
data.tar.gz: 81c846c795efd81bc10af84b65e02a6dfc3a52d9be2cc5f21cc7bc313e84fcd4ef3fed692bc1bc616389bc43f2a06a6944c2353aa8f8c3a614bb8a4c8ef2097c
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
-
### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.
|
1
|
+
### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.41.0...main)]
|
2
2
|
|
3
3
|
- xxx
|
4
4
|
|
5
|
+
### 0.41.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.40.7...0.41.0)]
|
6
|
+
|
7
|
+
- Port Puppeteer v14.0-v15.2 features, including `ElementHandle#wait_for_xpath`
|
8
|
+
|
5
9
|
### 0.40.7 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.40.6...0.40.7)]
|
6
10
|
|
7
11
|
- Port Puppeteer v13.6-v13.7 features.
|
data/docs/api_coverage.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# API coverages
|
2
|
-
- Puppeteer version:
|
3
|
-
- puppeteer-ruby version: 0.
|
2
|
+
- Puppeteer version: v15.2.0
|
3
|
+
- puppeteer-ruby version: 0.41.0
|
4
4
|
|
5
5
|
## Puppeteer
|
6
6
|
|
@@ -297,6 +297,7 @@
|
|
297
297
|
* type => `#type_text`
|
298
298
|
* uploadFile => `#upload_file`
|
299
299
|
* waitForSelector => `#wait_for_selector`
|
300
|
+
* waitForXPath => `#wait_for_xpath`
|
300
301
|
|
301
302
|
## HTTPRequest
|
302
303
|
|
data/lib/puppeteer/browser.rb
CHANGED
@@ -13,7 +13,14 @@ class Puppeteer::Browser
|
|
13
13
|
# @param {?Puppeteer.Viewport} defaultViewport
|
14
14
|
# @param process [Puppeteer::BrowserRunner::BrowserProcess|NilClass]
|
15
15
|
# @param {function()=} closeCallback
|
16
|
-
def self.create(connection:,
|
16
|
+
def self.create(connection:,
|
17
|
+
context_ids:,
|
18
|
+
ignore_https_errors:,
|
19
|
+
default_viewport:,
|
20
|
+
process:,
|
21
|
+
close_callback:,
|
22
|
+
target_filter_callback:,
|
23
|
+
is_page_target_callback:)
|
17
24
|
browser = Puppeteer::Browser.new(
|
18
25
|
connection: connection,
|
19
26
|
context_ids: context_ids,
|
@@ -21,6 +28,8 @@ class Puppeteer::Browser
|
|
21
28
|
default_viewport: default_viewport,
|
22
29
|
process: process,
|
23
30
|
close_callback: close_callback,
|
31
|
+
target_filter_callback: target_filter_callback,
|
32
|
+
is_page_target_callback: is_page_target_callback,
|
24
33
|
)
|
25
34
|
connection.send_message('Target.setDiscoverTargets', discover: true)
|
26
35
|
browser
|
@@ -32,12 +41,21 @@ class Puppeteer::Browser
|
|
32
41
|
# @param {?Puppeteer.Viewport} defaultViewport
|
33
42
|
# @param {?Puppeteer.ChildProcess} process
|
34
43
|
# @param {(function():Promise)=} closeCallback
|
35
|
-
def initialize(connection:,
|
44
|
+
def initialize(connection:,
|
45
|
+
context_ids:,
|
46
|
+
ignore_https_errors:,
|
47
|
+
default_viewport:,
|
48
|
+
process:,
|
49
|
+
close_callback:,
|
50
|
+
target_filter_callback:,
|
51
|
+
is_page_target_callback:)
|
36
52
|
@ignore_https_errors = ignore_https_errors
|
37
53
|
@default_viewport = default_viewport
|
38
54
|
@process = process
|
39
55
|
@connection = connection
|
40
56
|
@close_callback = close_callback
|
57
|
+
@target_filter_callback = target_filter_callback || method(:default_target_filter_callback)
|
58
|
+
@is_page_target_callback = is_page_target_callback || method(:default_is_page_target_callback)
|
41
59
|
|
42
60
|
@default_context = Puppeteer::BrowserContext.new(@connection, self, nil)
|
43
61
|
@contexts = {}
|
@@ -54,6 +72,16 @@ class Puppeteer::Browser
|
|
54
72
|
@connection.on_event('Target.targetInfoChanged', &method(:handle_target_info_changed))
|
55
73
|
end
|
56
74
|
|
75
|
+
private def default_target_filter_callback(target_info)
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
private def default_is_page_target_callback(target_info)
|
80
|
+
['page', 'background_page', 'webview'].include?(target_info.type)
|
81
|
+
end
|
82
|
+
|
83
|
+
attr_reader :is_page_target_callback
|
84
|
+
|
57
85
|
# @param event_name [Symbol] either of :disconnected, :targetcreated, :targetchanged, :targetdestroyed
|
58
86
|
def on(event_name, &block)
|
59
87
|
unless BrowserEmittedEvents.values.include?(event_name.to_s)
|
@@ -119,12 +147,16 @@ class Puppeteer::Browser
|
|
119
147
|
if @targets[target_info.target_id]
|
120
148
|
raise TargetAlreadyExistError.new
|
121
149
|
end
|
150
|
+
|
151
|
+
return unless @target_filter_callback.call(target_info)
|
152
|
+
|
122
153
|
target = Puppeteer::Target.new(
|
123
154
|
target_info: target_info,
|
124
155
|
browser_context: context,
|
125
156
|
session_factory: -> { @connection.create_session(target_info) },
|
126
157
|
ignore_https_errors: @ignore_https_errors,
|
127
158
|
default_viewport: @default_viewport,
|
159
|
+
is_page_target_callback: @is_page_target_callback,
|
128
160
|
)
|
129
161
|
@targets[target_info.target_id] = target
|
130
162
|
if_present(@wait_for_creating_targets.delete(target_info.target_id)) do |promise|
|
@@ -50,7 +50,9 @@ class Puppeteer::BrowserContext
|
|
50
50
|
|
51
51
|
# @return {!Promise<!Array<!Puppeteer.Page>>}
|
52
52
|
def pages
|
53
|
-
targets.select { |target|
|
53
|
+
targets.select { |target|
|
54
|
+
target.type == 'page' || (target.type == 'other' && @browser.is_page_target_callback&.call(target.target_info))
|
55
|
+
}.map(&:page).reject { |page| !page }
|
54
56
|
end
|
55
57
|
|
56
58
|
def incognito?
|
@@ -76,6 +76,81 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
76
76
|
|
77
77
|
define_async_method :async_wait_for_selector
|
78
78
|
|
79
|
+
# Wait for the `xpath` within the element. If at the moment of calling the
|
80
|
+
# method the `xpath` already exists, the method will return immediately. If
|
81
|
+
# the `xpath` doesn't appear after the `timeout` milliseconds of waiting, the
|
82
|
+
# function will throw.
|
83
|
+
#
|
84
|
+
# If `xpath` starts with `//` instead of `.//`, the dot will be appended automatically.
|
85
|
+
#
|
86
|
+
# This method works across navigation
|
87
|
+
# ```js
|
88
|
+
# const puppeteer = require('puppeteer');
|
89
|
+
# (async () => {
|
90
|
+
# const browser = await puppeteer.launch();
|
91
|
+
# const page = await browser.newPage();
|
92
|
+
# let currentURL;
|
93
|
+
# page
|
94
|
+
# .waitForXPath('//img')
|
95
|
+
# .then(() => console.log('First URL with image: ' + currentURL));
|
96
|
+
# for (currentURL of [
|
97
|
+
# 'https://example.com',
|
98
|
+
# 'https://google.com',
|
99
|
+
# 'https://bbc.com',
|
100
|
+
# ]) {
|
101
|
+
# await page.goto(currentURL);
|
102
|
+
# }
|
103
|
+
# await browser.close();
|
104
|
+
# })();
|
105
|
+
# ```
|
106
|
+
# @param xpath - A
|
107
|
+
# {@link https://developer.mozilla.org/en-US/docs/Web/XPath | xpath} of an
|
108
|
+
# element to wait for
|
109
|
+
# @param options - Optional waiting parameters
|
110
|
+
# @returns Promise which resolves when element specified by xpath string is
|
111
|
+
# added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
|
112
|
+
# not found in DOM.
|
113
|
+
# @remarks
|
114
|
+
# The optional Argument `options` have properties:
|
115
|
+
#
|
116
|
+
# - `visible`: A boolean to wait for element to be present in DOM and to be
|
117
|
+
# visible, i.e. to not have `display: none` or `visibility: hidden` CSS
|
118
|
+
# properties. Defaults to `false`.
|
119
|
+
#
|
120
|
+
# - `hidden`: A boolean wait for element to not be found in the DOM or to be
|
121
|
+
# hidden, i.e. have `display: none` or `visibility: hidden` CSS properties.
|
122
|
+
# Defaults to `false`.
|
123
|
+
#
|
124
|
+
# - `timeout`: A number which is maximum time to wait for in milliseconds.
|
125
|
+
# Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default
|
126
|
+
# value can be changed by using the {@link Page.setDefaultTimeout} method.
|
127
|
+
def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
|
128
|
+
frame = @context.frame
|
129
|
+
|
130
|
+
secondary_world = frame.secondary_world
|
131
|
+
adopted_root = secondary_world.execution_context.adopt_element_handle(self)
|
132
|
+
param_xpath =
|
133
|
+
if xpath.start_with?('//')
|
134
|
+
".#{xpath}"
|
135
|
+
else
|
136
|
+
xpath
|
137
|
+
end
|
138
|
+
unless param_xpath.start_with?('.//')
|
139
|
+
adopted_root.dispose
|
140
|
+
raise ArgumentError.new("Unsupported xpath expression: #{xpath}")
|
141
|
+
end
|
142
|
+
handle = secondary_world.wait_for_xpath(param_xpath, visible: visible, hidden: hidden, timeout: timeout, root: adopted_root)
|
143
|
+
adopted_root.dispose
|
144
|
+
return nil unless handle
|
145
|
+
|
146
|
+
main_world = frame.main_world
|
147
|
+
result = main_world.execution_context.adopt_element_handle(handle)
|
148
|
+
handle.dispose
|
149
|
+
result
|
150
|
+
end
|
151
|
+
|
152
|
+
define_async_method :async_wait_for_xpath
|
153
|
+
|
79
154
|
def as_element
|
80
155
|
self
|
81
156
|
end
|
@@ -309,22 +384,35 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
309
384
|
end
|
310
385
|
|
311
386
|
fn = <<~JAVASCRIPT
|
312
|
-
(element,
|
387
|
+
(element, vals) => {
|
388
|
+
const values = new Set(vals);
|
313
389
|
if (element.nodeName.toLowerCase() !== 'select') {
|
314
390
|
throw new Error('Element is not a <select> element.');
|
315
391
|
}
|
316
392
|
|
317
|
-
const
|
318
|
-
element.
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
393
|
+
const selectedValues = new Set();
|
394
|
+
if (!element.multiple) {
|
395
|
+
for (const option of element.options) {
|
396
|
+
option.selected = false;
|
397
|
+
}
|
398
|
+
for (const option of element.options) {
|
399
|
+
if (values.has(option.value)) {
|
400
|
+
option.selected = true;
|
401
|
+
selectedValues.add(option.value);
|
402
|
+
break;
|
403
|
+
}
|
404
|
+
}
|
405
|
+
} else {
|
406
|
+
for (const option of element.options) {
|
407
|
+
option.selected = values.has(option.value);
|
408
|
+
if (option.selected) {
|
409
|
+
selectedValues.add(option.value);
|
410
|
+
}
|
323
411
|
}
|
324
412
|
}
|
325
413
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
326
414
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
327
|
-
return
|
415
|
+
return [...selectedValues.values()];
|
328
416
|
}
|
329
417
|
JAVASCRIPT
|
330
418
|
evaluate(fn, values)
|
@@ -337,10 +425,6 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
337
425
|
raise ArgumentError.new('Multiple file uploads only work with <input type=file multiple>')
|
338
426
|
end
|
339
427
|
|
340
|
-
if error_path = file_paths.find { |file_path| !File.exist?(file_path) }
|
341
|
-
raise ArgumentError.new("#{error_path} does not exist or is not readable")
|
342
|
-
end
|
343
|
-
|
344
428
|
backend_node_id = @remote_object.node_info(@client)["node"]["backendNodeId"]
|
345
429
|
|
346
430
|
# The zero-length array is a special case, it seems that DOM.setFileInputFiles does
|
@@ -421,7 +505,15 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
421
505
|
end
|
422
506
|
end
|
423
507
|
|
424
|
-
def screenshot(type: nil,
|
508
|
+
def screenshot(type: nil,
|
509
|
+
path: nil,
|
510
|
+
full_page: nil,
|
511
|
+
clip: nil,
|
512
|
+
quality: nil,
|
513
|
+
omit_background: nil,
|
514
|
+
encoding: nil,
|
515
|
+
capture_beyond_viewport: nil,
|
516
|
+
from_surface: nil)
|
425
517
|
needs_viewport_reset = false
|
426
518
|
|
427
519
|
box = bounding_box
|
@@ -465,7 +557,17 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
465
557
|
}
|
466
558
|
end
|
467
559
|
|
468
|
-
@page.screenshot(
|
560
|
+
@page.screenshot(
|
561
|
+
type: type,
|
562
|
+
path: path,
|
563
|
+
full_page:
|
564
|
+
full_page,
|
565
|
+
clip: clip,
|
566
|
+
quality: quality,
|
567
|
+
omit_background: omit_background,
|
568
|
+
encoding: encoding,
|
569
|
+
capture_beyond_viewport: capture_beyond_viewport,
|
570
|
+
from_surface: from_surface)
|
469
571
|
ensure
|
470
572
|
if needs_viewport_reset
|
471
573
|
@page.viewport = viewport
|
@@ -2,7 +2,7 @@ class Puppeteer::ExecutionContext
|
|
2
2
|
include Puppeteer::IfPresent
|
3
3
|
using Puppeteer::DefineAsyncMethod
|
4
4
|
|
5
|
-
EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__'
|
5
|
+
EVALUATION_SCRIPT_URL = 'pprt://__puppeteer_evaluation_script__'
|
6
6
|
SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m
|
7
7
|
|
8
8
|
# @param client [Puppeteer::CDPSession]
|
data/lib/puppeteer/frame.rb
CHANGED
@@ -10,6 +10,7 @@ class Puppeteer::Frame
|
|
10
10
|
@parent_frame = parent_frame
|
11
11
|
@id = frame_id
|
12
12
|
@detached = false
|
13
|
+
@has_started_loading = false
|
13
14
|
|
14
15
|
@loader_id = ''
|
15
16
|
@lifecycle_events = Set.new
|
@@ -46,6 +47,10 @@ class Puppeteer::Frame
|
|
46
47
|
|
47
48
|
attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :secondary_world
|
48
49
|
|
50
|
+
def has_started_loading?
|
51
|
+
@has_started_loading
|
52
|
+
end
|
53
|
+
|
49
54
|
# @param url [String]
|
50
55
|
# @param rederer [String]
|
51
56
|
# @param timeout [number|nil]
|
@@ -316,6 +321,10 @@ class Puppeteer::Frame
|
|
316
321
|
@lifecycle_events << name
|
317
322
|
end
|
318
323
|
|
324
|
+
def handle_loading_started
|
325
|
+
@has_started_loading = true
|
326
|
+
end
|
327
|
+
|
319
328
|
def handle_loading_stopped
|
320
329
|
@lifecycle_events << 'DOMContentLoaded'
|
321
330
|
@lifecycle_events << 'load'
|
@@ -43,6 +43,9 @@ class Puppeteer::FrameManager
|
|
43
43
|
client.on_event('Page.frameDetached') do |event|
|
44
44
|
handle_frame_detached(event['frameId'], event['reason'])
|
45
45
|
end
|
46
|
+
client.on_event('Page.frameStartedLoading') do |event|
|
47
|
+
handle_frame_started_loading(event['frameId'])
|
48
|
+
end
|
46
49
|
client.on_event('Page.frameStoppedLoading') do |event|
|
47
50
|
handle_frame_stopped_loading(event['frameId'])
|
48
51
|
end
|
@@ -71,11 +74,17 @@ class Puppeteer::FrameManager
|
|
71
74
|
private def init(cdp_session = nil)
|
72
75
|
client = cdp_session || @client
|
73
76
|
|
74
|
-
|
77
|
+
promises = [
|
75
78
|
client.async_send_message('Page.enable'),
|
76
79
|
client.async_send_message('Page.getFrameTree'),
|
77
|
-
|
78
|
-
|
80
|
+
cdp_session&.async_send_message('Target.setAutoAttach', {
|
81
|
+
autoAttach: true,
|
82
|
+
waitForDebuggerOnStart: false,
|
83
|
+
flatten: true,
|
84
|
+
}),
|
85
|
+
].compact
|
86
|
+
results = await_all(*promises)
|
87
|
+
frame_tree = results[1]['frameTree']
|
79
88
|
handle_frame_tree(client, frame_tree)
|
80
89
|
await_all(
|
81
90
|
client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
|
@@ -112,13 +121,12 @@ class Puppeteer::FrameManager
|
|
112
121
|
option_timeout = timeout || @timeout_settings.navigation_timeout
|
113
122
|
|
114
123
|
watcher = Puppeteer::LifecycleWatcher.new(self, frame, option_wait_until, option_timeout)
|
115
|
-
ensure_new_document_navigation = false
|
116
124
|
|
117
125
|
begin
|
118
126
|
navigate = future do
|
119
127
|
result = @client.send_message('Page.navigate', navigate_params)
|
120
128
|
loader_id = result['loaderId']
|
121
|
-
|
129
|
+
|
122
130
|
if result['errorText']
|
123
131
|
raise NavigationError.new("#{result['errorText']} at #{url}")
|
124
132
|
end
|
@@ -128,14 +136,9 @@ class Puppeteer::FrameManager
|
|
128
136
|
watcher.timeout_or_termination_promise,
|
129
137
|
)
|
130
138
|
|
131
|
-
document_navigation_promise =
|
132
|
-
if ensure_new_document_navigation
|
133
|
-
watcher.new_document_navigation_promise
|
134
|
-
else
|
135
|
-
watcher.same_document_navigation_promise
|
136
|
-
end
|
137
139
|
await_any(
|
138
|
-
|
140
|
+
watcher.new_document_navigation_promise,
|
141
|
+
watcher.same_document_navigation_promise,
|
139
142
|
watcher.timeout_or_termination_promise,
|
140
143
|
)
|
141
144
|
rescue Puppeteer::TimeoutError => err
|
@@ -201,7 +204,14 @@ class Puppeteer::FrameManager
|
|
201
204
|
emit_event(FrameManagerEmittedEvents::LifecycleEvent, frame)
|
202
205
|
end
|
203
206
|
|
204
|
-
# @param
|
207
|
+
# @param frame_id [String]
|
208
|
+
def handle_frame_started_loading(frame_id)
|
209
|
+
frame = @frames[frame_id]
|
210
|
+
return if !frame
|
211
|
+
frame.handle_loading_started
|
212
|
+
end
|
213
|
+
|
214
|
+
# @param frame_id [String]
|
205
215
|
def handle_frame_stopped_loading(frame_id)
|
206
216
|
frame = @frames[frame_id]
|
207
217
|
return if !frame
|
@@ -31,9 +31,20 @@ module Puppeteer::Launcher
|
|
31
31
|
# `default_viewport: nil` must be respected here.
|
32
32
|
@default_viewport = options.key?(:default_viewport) ? options[:default_viewport] : Puppeteer::Viewport.new(width: 800, height: 600)
|
33
33
|
@slow_mo = options[:slow_mo] || 0
|
34
|
+
|
35
|
+
# only for Puppeteer.connect
|
36
|
+
@target_filter = options[:target_filter]
|
37
|
+
if @target_filter && !@target_filter.respond_to?(:call)
|
38
|
+
raise ArgumentError.new('target_filter must be a Proc (target_info => true/false)')
|
39
|
+
end
|
40
|
+
|
41
|
+
@is_page_target = options[:is_page_target]
|
42
|
+
if @is_page_target && !@is_page_target.respond_to?(:call)
|
43
|
+
raise ArgumentError.new('is_page_target must be a Proc (target_info => true/false)')
|
44
|
+
end
|
34
45
|
end
|
35
46
|
|
36
|
-
attr_reader :default_viewport, :slow_mo
|
47
|
+
attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target
|
37
48
|
|
38
49
|
def ignore_https_errors?
|
39
50
|
@ignore_https_errors
|
@@ -87,6 +87,8 @@ module Puppeteer::Launcher
|
|
87
87
|
default_viewport: @browser_options.default_viewport,
|
88
88
|
process: runner.proc,
|
89
89
|
close_callback: -> { runner.close },
|
90
|
+
target_filter_callback: nil,
|
91
|
+
is_page_target_callback: nil,
|
90
92
|
)
|
91
93
|
rescue
|
92
94
|
runner.kill
|
@@ -122,7 +124,9 @@ module Puppeteer::Launcher
|
|
122
124
|
'--disable-default-apps',
|
123
125
|
'--disable-dev-shm-usage',
|
124
126
|
'--disable-extensions',
|
125
|
-
|
127
|
+
# TODO: remove AvoidUnnecessaryBeforeUnloadCheckSync below
|
128
|
+
# once crbug.com/1324138 is fixed and released.
|
129
|
+
'--disable-features=Translate,BackForwardCache,AvoidUnnecessaryBeforeUnloadCheckSync',
|
126
130
|
'--disable-hang-monitor',
|
127
131
|
'--disable-ipc-flooding-protection',
|
128
132
|
'--disable-popup-blocking',
|
@@ -205,6 +209,8 @@ module Puppeteer::Launcher
|
|
205
209
|
default_viewport: @browser_options.default_viewport,
|
206
210
|
process: nil,
|
207
211
|
close_callback: -> { connection.send_message('Browser.close') },
|
212
|
+
target_filter_callback: @browser_options.target_filter,
|
213
|
+
is_page_target_callback: @browser_options.is_page_target,
|
208
214
|
)
|
209
215
|
end
|
210
216
|
|
@@ -85,6 +85,8 @@ module Puppeteer::Launcher
|
|
85
85
|
default_viewport: @browser_options.default_viewport,
|
86
86
|
process: runner.proc,
|
87
87
|
close_callback: -> { runner.close },
|
88
|
+
target_filter_callback: nil,
|
89
|
+
is_page_target_callback: nil,
|
88
90
|
)
|
89
91
|
rescue
|
90
92
|
runner.kill
|
@@ -132,6 +134,8 @@ module Puppeteer::Launcher
|
|
132
134
|
default_viewport: @browser_options.default_viewport,
|
133
135
|
process: nil,
|
134
136
|
close_callback: -> { connection.send_message('Browser.close') },
|
137
|
+
target_filter_callback: nil,
|
138
|
+
is_page_target_callback: nil,
|
135
139
|
)
|
136
140
|
end
|
137
141
|
|
@@ -43,7 +43,7 @@ class Puppeteer::LifecycleWatcher
|
|
43
43
|
if expected_lifecycle.any? { |event| !frame.lifecycle_events.include?(event) }
|
44
44
|
return false
|
45
45
|
end
|
46
|
-
if frame.child_frames.any? { |child| !completed?(child) }
|
46
|
+
if frame.child_frames.any? { |child| child.has_started_loading? && !completed?(child) }
|
47
47
|
return false
|
48
48
|
end
|
49
49
|
true
|
@@ -65,7 +65,6 @@ class Puppeteer::LifecycleWatcher
|
|
65
65
|
@expected_lifecycle = ExpectedLifecycle.new(wait_until)
|
66
66
|
@frame_manager = frame_manager
|
67
67
|
@frame = frame
|
68
|
-
@initial_loader_id = frame.loader_id
|
69
68
|
@timeout = timeout
|
70
69
|
|
71
70
|
@listener_ids = {}
|
@@ -77,6 +76,7 @@ class Puppeteer::LifecycleWatcher
|
|
77
76
|
check_lifecycle_complete
|
78
77
|
end,
|
79
78
|
@frame_manager.add_event_listener(FrameManagerEmittedEvents::FrameNavigatedWithinDocument, &method(:navigated_within_document)),
|
79
|
+
@frame_manager.add_event_listener(FrameManagerEmittedEvents::FrameNavigated, &method(:navigated)),
|
80
80
|
@frame_manager.add_event_listener(FrameManagerEmittedEvents::FrameSwapped, &method(:handle_frame_swapped)),
|
81
81
|
@frame_manager.add_event_listener(FrameManagerEmittedEvents::FrameDetached, &method(:handle_frame_detached)),
|
82
82
|
]
|
@@ -143,6 +143,12 @@ class Puppeteer::LifecycleWatcher
|
|
143
143
|
check_lifecycle_complete
|
144
144
|
end
|
145
145
|
|
146
|
+
private def navigated(frame)
|
147
|
+
return if frame != @frame
|
148
|
+
@new_document_navigation = true
|
149
|
+
check_lifecycle_complete
|
150
|
+
end
|
151
|
+
|
146
152
|
private def handle_frame_swapped(frame)
|
147
153
|
return if frame != @frame
|
148
154
|
@swapped = true
|
@@ -153,17 +159,10 @@ class Puppeteer::LifecycleWatcher
|
|
153
159
|
# We expect navigation to commit.
|
154
160
|
return unless @expected_lifecycle.completed?(@frame)
|
155
161
|
@lifecycle_promise.fulfill(true) if @lifecycle_promise.pending?
|
156
|
-
if @frame.loader_id == @initial_loader_id && !@has_same_document_navigation
|
157
|
-
if @swapped
|
158
|
-
@swapped = false
|
159
|
-
@new_document_navigation_promise.fulfill(true)
|
160
|
-
end
|
161
|
-
return
|
162
|
-
end
|
163
162
|
if @has_same_document_navigation && @same_document_navigation_promise.pending?
|
164
163
|
@same_document_navigation_promise.fulfill(true)
|
165
164
|
end
|
166
|
-
if @
|
165
|
+
if (@swapped || @new_document_navigation) && @new_document_navigation_promise.pending?
|
167
166
|
@new_document_navigation_promise.fulfill(true)
|
168
167
|
end
|
169
168
|
end
|
@@ -2,15 +2,49 @@ require 'mime/types'
|
|
2
2
|
|
3
3
|
class Puppeteer::Page
|
4
4
|
# /**
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
5
|
+
# * @defaultValue 'png'
|
6
|
+
# */
|
7
|
+
# type?: 'png' | 'jpeg' | 'webp';
|
8
|
+
# /**
|
9
|
+
# * The file path to save the image to. The screenshot type will be inferred
|
10
|
+
# * from file extension. If path is a relative path, then it is resolved
|
11
|
+
# * relative to current working directory. If no path is provided, the image
|
12
|
+
# * won't be saved to the disk.
|
13
|
+
# */
|
14
|
+
# path?: string;
|
15
|
+
# /**
|
16
|
+
# * When true, takes a screenshot of the full page.
|
17
|
+
# * @defaultValue false
|
18
|
+
# */
|
19
|
+
# fullPage?: boolean;
|
20
|
+
# /**
|
21
|
+
# * An object which specifies the clipping region of the page.
|
22
|
+
# */
|
23
|
+
# clip?: ScreenshotClip;
|
24
|
+
# /**
|
25
|
+
# * Quality of the image, between 0-100. Not applicable to `png` images.
|
26
|
+
# */
|
27
|
+
# quality?: number;
|
28
|
+
# /**
|
29
|
+
# * Hides default white background and allows capturing screenshots with transparency.
|
30
|
+
# * @defaultValue false
|
31
|
+
# */
|
32
|
+
# omitBackground?: boolean;
|
33
|
+
# /**
|
34
|
+
# * Encoding of the image.
|
35
|
+
# * @defaultValue 'binary'
|
36
|
+
# */
|
37
|
+
# encoding?: 'base64' | 'binary';
|
38
|
+
# /**
|
39
|
+
# * Capture the screenshot beyond the viewport.
|
40
|
+
# * @defaultValue true
|
41
|
+
# */
|
42
|
+
# captureBeyondViewport?: boolean;
|
43
|
+
# /**
|
44
|
+
# * Capture the screenshot from the surface, rather than the view.
|
45
|
+
# * @defaultValue true
|
46
|
+
# */
|
47
|
+
# fromSurface?: boolean;
|
14
48
|
class ScreenshotOptions
|
15
49
|
# @params options [Hash]
|
16
50
|
def initialize(options)
|
@@ -65,6 +99,18 @@ class Puppeteer::Page
|
|
65
99
|
@clip = options[:clip]
|
66
100
|
@omit_background = options[:omit_background]
|
67
101
|
@encoding = options[:encoding]
|
102
|
+
@capture_beyond_viewport =
|
103
|
+
if options[:capture_beyond_viewport].nil?
|
104
|
+
true
|
105
|
+
else
|
106
|
+
options[:capture_beyond_viewport]
|
107
|
+
end
|
108
|
+
@from_surface =
|
109
|
+
if options[:from_surface].nil?
|
110
|
+
true
|
111
|
+
else
|
112
|
+
options[:from_surface]
|
113
|
+
end
|
68
114
|
end
|
69
115
|
|
70
116
|
attr_reader :type, :quality, :path, :clip, :encoding
|
@@ -76,5 +122,13 @@ class Puppeteer::Page
|
|
76
122
|
def omit_background?
|
77
123
|
@omit_background
|
78
124
|
end
|
125
|
+
|
126
|
+
def capture_beyond_viewport?
|
127
|
+
@capture_beyond_viewport
|
128
|
+
end
|
129
|
+
|
130
|
+
def from_surface?
|
131
|
+
@from_surface
|
132
|
+
end
|
79
133
|
end
|
80
134
|
end
|
data/lib/puppeteer/page.rb
CHANGED
@@ -814,6 +814,10 @@ class Puppeteer::Page
|
|
814
814
|
predicate
|
815
815
|
end
|
816
816
|
|
817
|
+
frames.each do |frame|
|
818
|
+
return frame if frame_predicate.call(frame)
|
819
|
+
end
|
820
|
+
|
817
821
|
wait_for_frame_manager_event(
|
818
822
|
FrameManagerEmittedEvents::FrameAttached,
|
819
823
|
FrameManagerEmittedEvents::FrameNavigated,
|
@@ -1021,7 +1025,15 @@ class Puppeteer::Page
|
|
1021
1025
|
# @param quality [Integer]
|
1022
1026
|
# @param omit_background [Boolean]
|
1023
1027
|
# @param encoding [String]
|
1024
|
-
def screenshot(type: nil,
|
1028
|
+
def screenshot(type: nil,
|
1029
|
+
path: nil,
|
1030
|
+
full_page: nil,
|
1031
|
+
clip: nil,
|
1032
|
+
quality: nil,
|
1033
|
+
omit_background: nil,
|
1034
|
+
encoding: nil,
|
1035
|
+
capture_beyond_viewport: nil,
|
1036
|
+
from_surface: nil)
|
1025
1037
|
options = {
|
1026
1038
|
type: type,
|
1027
1039
|
path: path,
|
@@ -1030,6 +1042,8 @@ class Puppeteer::Page
|
|
1030
1042
|
quality: quality,
|
1031
1043
|
omit_background: omit_background,
|
1032
1044
|
encoding: encoding,
|
1045
|
+
capture_beyond_viewport: capture_beyond_viewport,
|
1046
|
+
from_surface: from_surface,
|
1033
1047
|
}.compact
|
1034
1048
|
screenshot_options = ScreenshotOptions.new(options)
|
1035
1049
|
|
@@ -1058,18 +1072,20 @@ class Puppeteer::Page
|
|
1058
1072
|
# Overwrite clip for full page at all times.
|
1059
1073
|
clip = { x: 0, y: 0, width: width, height: height, scale: 1 }
|
1060
1074
|
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1075
|
+
unless screenshot_options.capture_beyond_viewport?
|
1076
|
+
screen_orientation =
|
1077
|
+
if @viewport&.landscape?
|
1078
|
+
{ angle: 90, type: 'landscapePrimary' }
|
1079
|
+
else
|
1080
|
+
{ angle: 0, type: 'portraitPrimary' }
|
1081
|
+
end
|
1082
|
+
@client.send_message('Emulation.setDeviceMetricsOverride',
|
1083
|
+
mobile: @viewport&.mobile? || false,
|
1084
|
+
width: width,
|
1085
|
+
height: height,
|
1086
|
+
deviceScaleFactor: @viewport&.device_scale_factor || 1,
|
1087
|
+
screenOrientation: screen_orientation)
|
1088
|
+
end
|
1073
1089
|
end
|
1074
1090
|
|
1075
1091
|
should_set_default_background = screenshot_options.omit_background? && format == 'png'
|
@@ -1078,6 +1094,8 @@ class Puppeteer::Page
|
|
1078
1094
|
format: format,
|
1079
1095
|
quality: screenshot_options.quality,
|
1080
1096
|
clip: clip,
|
1097
|
+
captureBeyondViewport: screenshot_options.capture_beyond_viewport?,
|
1098
|
+
fromSurface: screenshot_options.from_surface?,
|
1081
1099
|
}.compact
|
1082
1100
|
result = @client.send_message('Page.captureScreenshot', screenshot_params)
|
1083
1101
|
reset_default_background_color if should_set_default_background
|
data/lib/puppeteer/target.rb
CHANGED
@@ -18,14 +18,19 @@ class Puppeteer::Target
|
|
18
18
|
# @param {!function():!Promise<!Puppeteer.CDPSession>} sessionFactory
|
19
19
|
# @param {boolean} ignoreHTTPSErrors
|
20
20
|
# @param {?Puppeteer.Viewport} defaultViewport
|
21
|
-
def initialize(target_info:,
|
21
|
+
def initialize(target_info:,
|
22
|
+
browser_context:,
|
23
|
+
session_factory:,
|
24
|
+
ignore_https_errors:,
|
25
|
+
default_viewport:,
|
26
|
+
is_page_target_callback:)
|
22
27
|
@target_info = target_info
|
23
28
|
@browser_context = browser_context
|
24
29
|
@target_id = target_info.target_id
|
25
30
|
@session_factory = session_factory
|
26
31
|
@ignore_https_errors = ignore_https_errors
|
27
32
|
@default_viewport = default_viewport
|
28
|
-
|
33
|
+
@is_page_target_callback = is_page_target_callback
|
29
34
|
|
30
35
|
# /** @type {?Promise<!Puppeteer.Page>} */
|
31
36
|
# this._pagePromise = null;
|
@@ -37,14 +42,14 @@ class Puppeteer::Target
|
|
37
42
|
end
|
38
43
|
@is_closed_promise = resolvable_future
|
39
44
|
|
40
|
-
@is_initialized = @target_info
|
45
|
+
@is_initialized = !@is_page_target_callback.call(@target_info) || !@target_info.url.empty?
|
41
46
|
|
42
47
|
if @is_initialized
|
43
48
|
@initialize_callback_promise.fulfill(true)
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
47
|
-
attr_reader :target_id, :initialized_promise, :is_closed_promise
|
52
|
+
attr_reader :target_id, :target_info, :initialized_promise, :is_closed_promise
|
48
53
|
|
49
54
|
def closed_callback
|
50
55
|
@is_closed_promise.fulfill(true)
|
@@ -83,7 +88,7 @@ class Puppeteer::Target
|
|
83
88
|
end
|
84
89
|
|
85
90
|
def page
|
86
|
-
if
|
91
|
+
if @is_page_target_callback.call(@target_info) && @page.nil?
|
87
92
|
client = @session_factory.call
|
88
93
|
@page = Puppeteer::Page.create(client, self, @ignore_https_errors, @default_viewport)
|
89
94
|
end
|
@@ -145,7 +150,7 @@ class Puppeteer::Target
|
|
145
150
|
def handle_target_info_changed(target_info)
|
146
151
|
@target_info = target_info
|
147
152
|
|
148
|
-
if !@is_initialized && (@target_info
|
153
|
+
if !@is_initialized && (!@is_page_target_callback.call(@target_info) || !@target_info.url.empty?)
|
149
154
|
@is_initialized = true
|
150
155
|
@initialize_callback_promise.fulfill(true)
|
151
156
|
end
|
data/lib/puppeteer/version.rb
CHANGED
data/puppeteer-ruby.gemspec
CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency 'rollbar'
|
33
33
|
spec.add_development_dependency 'rspec', '~> 3.11.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.31.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.41.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- YusukeIwaki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -170,14 +170,14 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 1.
|
173
|
+
version: 1.31.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.31.0
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: rubocop-rspec
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|