puppeteer-ruby 0.40.7 → 0.41.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 +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
|