puppeteer-ruby 0.34.2 → 0.36.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 +28 -1
- data/README.md +9 -11
- data/docs/api_coverage.md +26 -6
- data/lib/puppeteer-ruby.rb +2 -0
- data/lib/puppeteer.rb +1 -1
- data/lib/puppeteer/concurrent_ruby_utils.rb +2 -2
- data/lib/puppeteer/define_async_method.rb +1 -1
- data/lib/puppeteer/devices.rb +2 -0
- data/lib/puppeteer/element_handle.rb +41 -0
- data/lib/puppeteer/launcher.rb +0 -1
- 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/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.rb +132 -88
- data/lib/puppeteer/page/metrics.rb +49 -0
- data/lib/puppeteer/puppeteer.rb +10 -2
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +1 -1
- data/puppeteer-ruby.gemspec +1 -1
- metadata +8 -7
- data/Dockerfile +0 -9
- data/docker-compose.yml +0 -34
- data/lib/puppeteer/launcher/base.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d4af926ca9ad39046dcc4ce9507622bf72c3263b747516ee1ba642c02e8e38d
|
4
|
+
data.tar.gz: 3eff89952883a47270dee6a9ee7281609dbbe7bac197eada9e6b6dcabbaa1f78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d83085eb31bb6d0d9f125e4c82fe172f46676bc373bd7652b2be430984570f7facff20bd3b1da20dbcb41beca52a740064b9b6d5cb6f51a69bf8f4f97e699947
|
7
|
+
data.tar.gz: 141bed0475d5e40a0ddb9ec0cb2a82f44313a14e835a7d5cc271a413dad0c9881658a81f0ec0567d7896a7658539fc131bb5069c1c07c6573f6b633c99a53f8e
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,34 @@
|
|
1
|
-
### master [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.
|
1
|
+
### master [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.36.0...master)]
|
2
2
|
|
3
3
|
* xxx
|
4
4
|
|
5
|
+
### 0.36.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.35.1...0.36.0)]
|
6
|
+
|
7
|
+
New features:
|
8
|
+
|
9
|
+
* Drag and Drop feature introduced in Puppeteer 10.1
|
10
|
+
* `Page#emulateNetworkConditions`, `Page#emulateCPUThrottling`
|
11
|
+
* `Page#exposeFunction`
|
12
|
+
* Metrics
|
13
|
+
|
14
|
+
### 0.35.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.35.0...0.35.1)]
|
15
|
+
|
16
|
+
New features:
|
17
|
+
|
18
|
+
* Allow Rails users to use this library without `require 'puppeteer'`.
|
19
|
+
|
20
|
+
### 0.35.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.3...0.35.0)]
|
21
|
+
|
22
|
+
New features:
|
23
|
+
|
24
|
+
* Add `channel` parameter for Puppeteer.launch. Now `channel: chrome` or `channel: chrome-canary` (chrome-beta, chrome-dev is also available) automatically detects the executablePath of Google Chrome. Windows/macOS users can also use `channel: msedge`.
|
25
|
+
|
26
|
+
### 0.34.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.2...0.34.3)]
|
27
|
+
|
28
|
+
Bugfix:
|
29
|
+
|
30
|
+
* Fix wait_for_xxx's timeout error type.
|
31
|
+
|
5
32
|
### 0.34.2 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.1...0.34.2)]
|
6
33
|
|
7
34
|
New features:
|
data/README.md
CHANGED
@@ -20,31 +20,29 @@ gem 'puppeteer-ruby'
|
|
20
20
|
|
21
21
|
And then execute:
|
22
22
|
|
23
|
-
$ bundle
|
24
|
-
|
25
|
-
Or install it yourself as:
|
26
|
-
|
27
|
-
$ gem install puppeteer-ruby
|
23
|
+
$ bundle install
|
28
24
|
|
29
25
|
### Capture a site
|
30
26
|
|
31
27
|
```ruby
|
32
|
-
require 'puppeteer'
|
28
|
+
require 'puppeteer-ruby'
|
33
29
|
|
34
30
|
Puppeteer.launch(headless: false) do |browser|
|
35
|
-
page = browser.
|
31
|
+
page = browser.new_page
|
36
32
|
page.goto("https://github.com/YusukeIwaki")
|
37
33
|
page.screenshot(path: "YusukeIwaki.png")
|
38
34
|
end
|
39
35
|
```
|
40
36
|
|
37
|
+
NOTE: `require 'puppeteer-ruby'` is not necessary in Rails.
|
38
|
+
|
41
39
|
### Simple scraping
|
42
40
|
|
43
41
|
```ruby
|
44
|
-
require 'puppeteer'
|
42
|
+
require 'puppeteer-ruby'
|
45
43
|
|
46
44
|
Puppeteer.launch(headless: false, slow_mo: 50, args: ['--guest', '--window-size=1280,800']) do |browser|
|
47
|
-
page = browser.
|
45
|
+
page = browser.new_page
|
48
46
|
page.viewport = Puppeteer::Viewport.new(width: 1280, height: 800)
|
49
47
|
page.goto("https://github.com/", wait_until: 'domcontentloaded')
|
50
48
|
|
@@ -69,10 +67,10 @@ end
|
|
69
67
|
### Evaluate JavaScript
|
70
68
|
|
71
69
|
```ruby
|
72
|
-
require 'puppeteer'
|
70
|
+
require 'puppeteer-ruby'
|
73
71
|
|
74
72
|
Puppeteer.launch do |browser|
|
75
|
-
page = browser.
|
73
|
+
page = browser.new_page
|
76
74
|
page.goto 'https://github.com/YusukeIwaki'
|
77
75
|
|
78
76
|
# Get the "viewport" of the page, as reported by the page.
|
data/docs/api_coverage.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# API coverages
|
2
|
-
- Puppeteer version: v10.
|
3
|
-
- puppeteer-ruby version: 0.
|
2
|
+
- Puppeteer version: v10.2.0
|
3
|
+
- puppeteer-ruby version: 0.36.0
|
4
4
|
|
5
5
|
## Puppeteer
|
6
6
|
|
@@ -13,7 +13,7 @@
|
|
13
13
|
* ~~errors~~
|
14
14
|
* executablePath => `#executable_path`
|
15
15
|
* launch
|
16
|
-
*
|
16
|
+
* networkConditions => `#network_conditions`
|
17
17
|
* product
|
18
18
|
* ~~registerCustomQueryHandler~~
|
19
19
|
* ~~unregisterCustomQueryHandler~~
|
@@ -78,18 +78,20 @@
|
|
78
78
|
* content
|
79
79
|
* cookies
|
80
80
|
* coverage
|
81
|
+
* createPDFStream => `#create_pdf_stream`
|
81
82
|
* deleteCookie => `#delete_cookie`
|
82
83
|
* emulate
|
84
|
+
* emulateCPUThrottling => `#emulate_cpu_throttling`
|
83
85
|
* emulateIdleState => `#emulate_idle_state`
|
84
86
|
* emulateMediaFeatures => `#emulate_media_features`
|
85
87
|
* emulateMediaType => `#emulate_media_type`
|
86
|
-
*
|
88
|
+
* emulateNetworkConditions => `#emulate_network_conditions`
|
87
89
|
* emulateTimezone => `#emulate_timezone`
|
88
90
|
* emulateVisionDeficiency => `#emulate_vision_deficiency`
|
89
91
|
* evaluate
|
90
92
|
* evaluateHandle => `#evaluate_handle`
|
91
93
|
* evaluateOnNewDocument => `#evaluate_on_new_document`
|
92
|
-
*
|
94
|
+
* exposeFunction => `#expose_function`
|
93
95
|
* focus
|
94
96
|
* frames
|
95
97
|
* goBack => `#go_back`
|
@@ -97,10 +99,11 @@
|
|
97
99
|
* goto
|
98
100
|
* hover
|
99
101
|
* isClosed => `#closed?`
|
102
|
+
* isDragInterceptionEnabled => `#drag_interception_enabled?`
|
100
103
|
* isJavaScriptEnabled => `#javascript_enabled?`
|
101
104
|
* keyboard
|
102
105
|
* mainFrame => `#main_frame`
|
103
|
-
*
|
106
|
+
* metrics
|
104
107
|
* mouse
|
105
108
|
* pdf
|
106
109
|
* queryObjects => `#query_objects`
|
@@ -113,6 +116,7 @@
|
|
113
116
|
* setCookie => `#set_cookie`
|
114
117
|
* setDefaultNavigationTimeout => `#default_navigation_timeout=`
|
115
118
|
* setDefaultTimeout => `#default_timeout=`
|
119
|
+
* ~~setDragInterception~~
|
116
120
|
* setExtraHTTPHeaders => `#extra_http_headers=`
|
117
121
|
* setGeolocation => `#geolocation=`
|
118
122
|
* setJavaScriptEnabled => `#javascript_enabled=`
|
@@ -162,6 +166,11 @@
|
|
162
166
|
|
163
167
|
* click
|
164
168
|
* down
|
169
|
+
* drag
|
170
|
+
* dragAndDrop => `#drag_and_drop`
|
171
|
+
* dragEnter => `#drag_enter`
|
172
|
+
* dragOver => `#drag_over`
|
173
|
+
* drop
|
165
174
|
* move
|
166
175
|
* up
|
167
176
|
* wheel
|
@@ -260,8 +269,14 @@
|
|
260
269
|
* boundingBox => `#bounding_box`
|
261
270
|
* boxModel => `#box_model`
|
262
271
|
* click
|
272
|
+
* clickablePoint => `#clickable_point`
|
263
273
|
* contentFrame => `#content_frame`
|
264
274
|
* dispose
|
275
|
+
* drag
|
276
|
+
* dragAndDrop => `#drag_and_drop`
|
277
|
+
* dragEnter => `#drag_enter`
|
278
|
+
* dragOver => `#drag_over`
|
279
|
+
* drop
|
265
280
|
* evaluate
|
266
281
|
* evaluateHandle => `#evaluate_handle`
|
267
282
|
* executionContext => `#execution_context`
|
@@ -282,8 +297,12 @@
|
|
282
297
|
## ~~HTTPRequest~~
|
283
298
|
|
284
299
|
* ~~abort~~
|
300
|
+
* ~~abortErrorReason~~
|
285
301
|
* ~~continue~~
|
302
|
+
* ~~continueRequestOverrides~~
|
303
|
+
* ~~enqueueInterceptAction~~
|
286
304
|
* ~~failure~~
|
305
|
+
* ~~finalizeInterceptions~~
|
287
306
|
* ~~frame~~
|
288
307
|
* ~~headers~~
|
289
308
|
* ~~isNavigationRequest~~
|
@@ -293,6 +312,7 @@
|
|
293
312
|
* ~~resourceType~~
|
294
313
|
* ~~respond~~
|
295
314
|
* ~~response~~
|
315
|
+
* ~~responseForRequest~~
|
296
316
|
* ~~url~~
|
297
317
|
|
298
318
|
## ~~HTTPResponse~~
|
data/lib/puppeteer.rb
CHANGED
@@ -5,7 +5,6 @@ module Puppeteer; end
|
|
5
5
|
require 'puppeteer/env'
|
6
6
|
|
7
7
|
# Custom data types.
|
8
|
-
require 'puppeteer/device'
|
9
8
|
require 'puppeteer/events'
|
10
9
|
require 'puppeteer/errors'
|
11
10
|
require 'puppeteer/geolocation'
|
@@ -44,6 +43,7 @@ require 'puppeteer/keyboard'
|
|
44
43
|
require 'puppeteer/launcher'
|
45
44
|
require 'puppeteer/lifecycle_watcher'
|
46
45
|
require 'puppeteer/mouse'
|
46
|
+
require 'puppeteer/network_conditions'
|
47
47
|
require 'puppeteer/network_manager'
|
48
48
|
require 'puppeteer/page'
|
49
49
|
require 'puppeteer/protocol_stream_reader'
|
@@ -3,11 +3,11 @@ module Puppeteer::ConcurrentRubyUtils
|
|
3
3
|
module ConcurrentPromisesFutureExtension
|
4
4
|
# Extension for describing 2 concurrent tasks smartly.
|
5
5
|
#
|
6
|
-
# page.
|
6
|
+
# page.async_wait_for_navigation.with_waiting_for_complete do
|
7
7
|
# page.click('#submit')
|
8
8
|
# end
|
9
9
|
def with_waiting_for_complete(&block)
|
10
|
-
async_block_call = Concurrent::Promises.
|
10
|
+
async_block_call = Concurrent::Promises.delay do
|
11
11
|
block.call
|
12
12
|
rescue => err
|
13
13
|
Logger.new($stderr).warn(err)
|
data/lib/puppeteer/devices.rb
CHANGED
@@ -147,6 +147,47 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
|
|
147
147
|
|
148
148
|
define_async_method :async_click
|
149
149
|
|
150
|
+
class DragInterceptionNotEnabledError < StandardError
|
151
|
+
def initialize
|
152
|
+
super('Drag Interception is not enabled!')
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def drag(x:, y:)
|
157
|
+
unless @page.drag_interception_enabled?
|
158
|
+
raise DragInterceptionNotEnabledError.new
|
159
|
+
end
|
160
|
+
scroll_into_view_if_needed
|
161
|
+
start = clickable_point
|
162
|
+
@page.mouse.drag(start, Point.new(x: x, y: y))
|
163
|
+
end
|
164
|
+
|
165
|
+
def drag_enter(data)
|
166
|
+
scroll_into_view_if_needed
|
167
|
+
target = clickable_point
|
168
|
+
@page.mouse.drag_enter(target, data)
|
169
|
+
end
|
170
|
+
|
171
|
+
def drag_over(data)
|
172
|
+
scroll_into_view_if_needed
|
173
|
+
target = clickable_point
|
174
|
+
@page.mouse.drag_over(target, data)
|
175
|
+
end
|
176
|
+
|
177
|
+
def drop(data)
|
178
|
+
scroll_into_view_if_needed
|
179
|
+
target = clickable_point
|
180
|
+
@page.mouse.drop(target, data)
|
181
|
+
end
|
182
|
+
|
183
|
+
# @param target [ElementHandle]
|
184
|
+
def drag_and_drop(target, delay: nil)
|
185
|
+
scroll_into_view_if_needed
|
186
|
+
start_point = clickable_point
|
187
|
+
target_point = target.clickable_point
|
188
|
+
@page.mouse.drag_and_drop(start_point, target_point, delay: delay)
|
189
|
+
end
|
190
|
+
|
150
191
|
# @return [Array<String>]
|
151
192
|
def select(*values)
|
152
193
|
if nonstring = values.find { |value| !value.is_a?(String) }
|
data/lib/puppeteer/launcher.rb
CHANGED
@@ -2,7 +2,13 @@ require 'tmpdir'
|
|
2
2
|
|
3
3
|
# https://github.com/puppeteer/puppeteer/blob/main/src/node/Launcher.ts
|
4
4
|
module Puppeteer::Launcher
|
5
|
-
class Chrome
|
5
|
+
class Chrome
|
6
|
+
def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
|
7
|
+
@project_root = project_root
|
8
|
+
@preferred_revision = preferred_revision
|
9
|
+
@is_puppeteer_core = is_puppeteer_core
|
10
|
+
end
|
11
|
+
|
6
12
|
# @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions)=} options
|
7
13
|
# @return {!Promise<!Browser>}
|
8
14
|
def launch(options = {})
|
@@ -38,7 +44,12 @@ module Puppeteer::Launcher
|
|
38
44
|
chrome_arguments << "--user-data-dir=#{temporary_user_data_dir}"
|
39
45
|
end
|
40
46
|
|
41
|
-
chrome_executable =
|
47
|
+
chrome_executable =
|
48
|
+
if @launch_options.channel
|
49
|
+
executable_path_for_channel(@launch_options.channel.to_s)
|
50
|
+
else
|
51
|
+
@launch_options.executable_path || executable_path_for_channel('chrome')
|
52
|
+
end
|
42
53
|
use_pipe = chrome_arguments.include?('--remote-debugging-pipe')
|
43
54
|
runner = Puppeteer::BrowserRunner.new(chrome_executable, chrome_arguments, temporary_user_data_dir)
|
44
55
|
runner.start(
|
@@ -201,8 +212,57 @@ module Puppeteer::Launcher
|
|
201
212
|
end
|
202
213
|
|
203
214
|
# @return {string}
|
204
|
-
def executable_path
|
205
|
-
|
215
|
+
def executable_path(channel: nil)
|
216
|
+
if channel
|
217
|
+
executable_path_for_channel(channel.to_s)
|
218
|
+
else
|
219
|
+
executable_path_for_channel('chrome')
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
CHROMIUM_CHANNELS = {
|
224
|
+
windows: {
|
225
|
+
'chrome' => "#{ENV['PROGRAMFILES']}\\Google\\Chrome\\Application\\chrome.exe",
|
226
|
+
'chrome-beta' => "#{ENV['PROGRAMFILES']}\\Google\\Chrome Beta\\Application\\chrome.exe",
|
227
|
+
'chrome-canary' => "#{ENV['PROGRAMFILES']}\\Google\\Chrome SxS\\Application\\chrome.exe",
|
228
|
+
'chrome-dev' => "#{ENV['PROGRAMFILES']}\\Google\\Chrome Dev\\Application\\chrome.exe",
|
229
|
+
'msedge' => "#{ENV['PROGRAMFILES(X86)']}\\Microsoft\\Edge\\Application\\msedge.exe",
|
230
|
+
},
|
231
|
+
darwin: {
|
232
|
+
'chrome' => '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
233
|
+
'chrome-beta' => '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
|
234
|
+
'chrome-canary' => '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
235
|
+
'chrome-dev' => '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
|
236
|
+
'msedge' => '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
237
|
+
},
|
238
|
+
linux: {
|
239
|
+
'chrome' => '/opt/google/chrome/chrome',
|
240
|
+
'chrome-beta' => '/opt/google/chrome-beta/chrome',
|
241
|
+
'chrome-dev' => '/opt/google/chrome-unstable/chrome',
|
242
|
+
},
|
243
|
+
}.freeze
|
244
|
+
|
245
|
+
# @param channel [String]
|
246
|
+
private def executable_path_for_channel(channel)
|
247
|
+
chrome_path_map =
|
248
|
+
if Puppeteer.env.windows?
|
249
|
+
CHROMIUM_CHANNELS[:windows]
|
250
|
+
elsif Puppeteer.env.darwin?
|
251
|
+
CHROMIUM_CHANNELS[:darwin]
|
252
|
+
else
|
253
|
+
CHROMIUM_CHANNELS[:linux]
|
254
|
+
end
|
255
|
+
|
256
|
+
chrome_path = chrome_path_map[channel]
|
257
|
+
unless chrome_path
|
258
|
+
raise ArgumentError.new("Invalid channel: '#{channel}'. Allowed channel is #{chrome_path_map.keys}")
|
259
|
+
end
|
260
|
+
|
261
|
+
unless File.exist?(chrome_path)
|
262
|
+
raise "#{channel} is not installed on this system.\nExpected path: #{chrome_path}"
|
263
|
+
end
|
264
|
+
|
265
|
+
chrome_path
|
206
266
|
end
|
207
267
|
|
208
268
|
def product
|
@@ -2,7 +2,13 @@ require 'tmpdir'
|
|
2
2
|
|
3
3
|
# https://github.com/puppeteer/puppeteer/blob/main/src/node/Launcher.ts
|
4
4
|
module Puppeteer::Launcher
|
5
|
-
class Firefox
|
5
|
+
class Firefox
|
6
|
+
def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
|
7
|
+
@project_root = project_root
|
8
|
+
@preferred_revision = preferred_revision
|
9
|
+
@is_puppeteer_core = is_puppeteer_core
|
10
|
+
end
|
11
|
+
|
6
12
|
# @param {!(Launcher.LaunchOptions & Launcher.ChromeArgOptions & Launcher.BrowserOptions)=} options
|
7
13
|
# @return {!Promise<!Browser>}
|
8
14
|
def launch(options = {})
|
@@ -32,7 +38,12 @@ module Puppeteer::Launcher
|
|
32
38
|
firefox_arguments << temporary_user_data_dir
|
33
39
|
end
|
34
40
|
|
35
|
-
firefox_executable =
|
41
|
+
firefox_executable =
|
42
|
+
if @launch_options.channel
|
43
|
+
executable_path_for_channel(@launch_options.channel.to_s)
|
44
|
+
else
|
45
|
+
@launch_options.executable_path || executable_path_for_channel('nightly')
|
46
|
+
end
|
36
47
|
runner = Puppeteer::BrowserRunner.new(firefox_executable, firefox_arguments, temporary_user_data_dir)
|
37
48
|
runner.start(
|
38
49
|
handle_SIGHUP: @launch_options.handle_SIGHUP?,
|
@@ -123,8 +134,41 @@ module Puppeteer::Launcher
|
|
123
134
|
end
|
124
135
|
|
125
136
|
# @return {string}
|
126
|
-
def executable_path
|
127
|
-
|
137
|
+
def executable_path(channel: nil)
|
138
|
+
if channel
|
139
|
+
executable_path_for_channel(channel.to_s)
|
140
|
+
else
|
141
|
+
executable_path_for_channel('firefox')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
FIREFOX_EXECUTABLE_PATHS = {
|
146
|
+
windows: "#{ENV['PROGRAMFILES']}\\Firefox Nightly\\firefox.exe",
|
147
|
+
darwin: '/Applications/Firefox Nightly.app/Contents/MacOS/firefox',
|
148
|
+
linux: '/usr/bin/firefox',
|
149
|
+
}.freeze
|
150
|
+
|
151
|
+
# @param channel [String]
|
152
|
+
private def executable_path_for_channel(channel)
|
153
|
+
allowed = ['firefox', 'firefox-nightly', 'nightly']
|
154
|
+
unless allowed.include?(channel)
|
155
|
+
raise ArgumentError.new("Invalid channel: '#{channel}'. Allowed channel is #{allowed}")
|
156
|
+
end
|
157
|
+
|
158
|
+
firefox_path =
|
159
|
+
if Puppeteer.env.windows?
|
160
|
+
FIREFOX_EXECUTABLE_PATHS[:windows]
|
161
|
+
elsif Puppeteer.env.darwin?
|
162
|
+
FIREFOX_EXECUTABLE_PATHS[:darwin]
|
163
|
+
else
|
164
|
+
FIREFOX_EXECUTABLE_PATHS[:linux]
|
165
|
+
end
|
166
|
+
|
167
|
+
unless File.exist?(firefox_path)
|
168
|
+
raise "Nightly version of Firefox is not installed on this system.\nExpected path: #{firefox_path}"
|
169
|
+
end
|
170
|
+
|
171
|
+
firefox_path
|
128
172
|
end
|
129
173
|
|
130
174
|
def product
|
@@ -32,6 +32,7 @@ module Puppeteer::Launcher
|
|
32
32
|
# @property {!Object<string, string | undefined>=} env
|
33
33
|
# @property {boolean=} pipe
|
34
34
|
def initialize(options)
|
35
|
+
@channel = options[:channel]
|
35
36
|
@executable_path = options[:executable_path]
|
36
37
|
@ignore_default_args = options[:ignore_default_args] || false
|
37
38
|
@handle_SIGINT = options[:handle_SIGINT] || true
|
@@ -43,7 +44,7 @@ module Puppeteer::Launcher
|
|
43
44
|
@pipe = options[:pipe] || false
|
44
45
|
end
|
45
46
|
|
46
|
-
attr_reader :executable_path, :ignore_default_args, :timeout, :env
|
47
|
+
attr_reader :channel, :executable_path, :ignore_default_args, :timeout, :env
|
47
48
|
|
48
49
|
def handle_SIGINT?
|
49
50
|
@handle_SIGINT
|
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]
|
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
|
@@ -392,6 +415,51 @@ class Puppeteer::Page
|
|
392
415
|
main_frame.add_style_tag(url: url, path: path, content: content)
|
393
416
|
end
|
394
417
|
|
418
|
+
# @param name [String]
|
419
|
+
# @param puppeteer_function [Proc]
|
420
|
+
def expose_function(name, puppeteer_function)
|
421
|
+
if @page_bindings[name]
|
422
|
+
raise ArgumentError.new("Failed to add page binding with name `#{name}` already exists!")
|
423
|
+
end
|
424
|
+
@page_bindings[name] = puppeteer_function
|
425
|
+
|
426
|
+
add_page_binding = <<~JAVASCRIPT
|
427
|
+
function (type, bindingName) {
|
428
|
+
/* Cast window to any here as we're about to add properties to it
|
429
|
+
* via win[bindingName] which TypeScript doesn't like.
|
430
|
+
*/
|
431
|
+
const win = window;
|
432
|
+
const binding = win[bindingName];
|
433
|
+
|
434
|
+
win[bindingName] = (...args) => {
|
435
|
+
const me = window[bindingName];
|
436
|
+
let callbacks = me.callbacks;
|
437
|
+
if (!callbacks) {
|
438
|
+
callbacks = new Map();
|
439
|
+
me.callbacks = callbacks;
|
440
|
+
}
|
441
|
+
const seq = (me.lastSeq || 0) + 1;
|
442
|
+
me.lastSeq = seq;
|
443
|
+
const promise = new Promise((resolve, reject) =>
|
444
|
+
callbacks.set(seq, { resolve, reject })
|
445
|
+
);
|
446
|
+
binding(JSON.stringify({ type, name: bindingName, seq, args }));
|
447
|
+
return promise;
|
448
|
+
};
|
449
|
+
}
|
450
|
+
JAVASCRIPT
|
451
|
+
|
452
|
+
source = JavaScriptFunction.new(add_page_binding, ['exposedFun', name]).source
|
453
|
+
@client.send_message('Runtime.addBinding', name: name)
|
454
|
+
@client.send_message('Page.addScriptToEvaluateOnNewDocument', source: source)
|
455
|
+
|
456
|
+
promises = @frame_manager.frames.map do |frame|
|
457
|
+
frame.async_evaluate("() => #{source}")
|
458
|
+
end
|
459
|
+
await_all(*promises)
|
460
|
+
|
461
|
+
nil
|
462
|
+
end
|
395
463
|
# /**
|
396
464
|
# * @param {string} name
|
397
465
|
# * @param {Function} puppeteerFunction
|
@@ -440,36 +508,10 @@ class Puppeteer::Page
|
|
440
508
|
@frame_manager.network_manager.user_agent = user_agent
|
441
509
|
end
|
442
510
|
|
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
|
-
# }
|
511
|
+
def metrics
|
512
|
+
response = @client.send_message('Performance.getMetrics')
|
513
|
+
Metrics.new(response['metrics'])
|
514
|
+
end
|
473
515
|
|
474
516
|
class PageError < StandardError ; end
|
475
517
|
|
@@ -506,56 +548,51 @@ class Puppeteer::Page
|
|
506
548
|
add_console_message(event['type'], values, event['stackTrace'])
|
507
549
|
end
|
508
550
|
|
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
|
-
# }
|
551
|
+
def handle_binding_called(event)
|
552
|
+
execution_context_id = event['executionContextId']
|
553
|
+
payload =
|
554
|
+
begin
|
555
|
+
JSON.parse(event['payload'])
|
556
|
+
rescue
|
557
|
+
# The binding was either called by something in the page or it was
|
558
|
+
# called before our wrapper was initialized.
|
559
|
+
return
|
560
|
+
end
|
561
|
+
name = payload['name']
|
562
|
+
seq = payload['seq']
|
563
|
+
args = payload['args']
|
535
564
|
|
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
|
-
# }
|
565
|
+
if payload['type'] != 'exposedFun' || !@page_bindings[name]
|
566
|
+
return
|
567
|
+
end
|
548
568
|
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
569
|
+
expression =
|
570
|
+
begin
|
571
|
+
result = @page_bindings[name].call(*args)
|
572
|
+
|
573
|
+
deliver_result = <<~JAVASCRIPT
|
574
|
+
function (name, seq, result) {
|
575
|
+
window[name].callbacks.get(seq).resolve(result);
|
576
|
+
window[name].callbacks.delete(seq);
|
577
|
+
}
|
578
|
+
JAVASCRIPT
|
579
|
+
|
580
|
+
JavaScriptFunction.new(deliver_result, [name, seq, result]).source
|
581
|
+
rescue => err
|
582
|
+
deliver_error = <<~JAVASCRIPT
|
583
|
+
function (name, seq, message) {
|
584
|
+
const error = new Error(message);
|
585
|
+
window[name].callbacks.get(seq).reject(error);
|
586
|
+
window[name].callbacks.delete(seq);
|
587
|
+
}
|
588
|
+
JAVASCRIPT
|
589
|
+
JavaScriptFunction.new(deliver_error, [name, seq, err.message]).source
|
590
|
+
end
|
591
|
+
|
592
|
+
@client.async_send_message('Runtime.evaluate', expression: expression, contextId: execution_context_id).rescue do |error|
|
593
|
+
debug_puts(error)
|
594
|
+
end
|
595
|
+
end
|
559
596
|
|
560
597
|
private def add_console_message(type, args, stack_trace)
|
561
598
|
text_tokens = args.map { |arg| arg.remote_object.value }
|
@@ -631,10 +668,9 @@ class Puppeteer::Page
|
|
631
668
|
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
632
669
|
# @return [Puppeteer::Response]
|
633
670
|
def reload(timeout: nil, wait_until: nil)
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
).first
|
671
|
+
wait_for_navigation(timeout: timeout, wait_until: wait_until) do
|
672
|
+
@client.send_message('Page.reload')
|
673
|
+
end
|
638
674
|
end
|
639
675
|
|
640
676
|
def wait_for_navigation(timeout: nil, wait_until: nil)
|
@@ -760,10 +796,9 @@ class Puppeteer::Page
|
|
760
796
|
entries = history['entries']
|
761
797
|
index = history['currentIndex'] + delta
|
762
798
|
if_present(entries[index]) do |entry|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
)
|
799
|
+
wait_for_navigation(timeout: timeout, wait_until: wait_until) do
|
800
|
+
@client.send_message('Page.navigateToHistoryEntry', entryId: entry['id'])
|
801
|
+
end
|
767
802
|
end
|
768
803
|
end
|
769
804
|
|
@@ -799,6 +834,15 @@ class Puppeteer::Page
|
|
799
834
|
@client.send_message('Emulation.setEmulatedMedia', media: media_type_str)
|
800
835
|
end
|
801
836
|
|
837
|
+
# @param factor [Number|nil] Factor at which the CPU will be throttled (2x, 2.5x. 3x, ...). Passing `nil` disables cpu throttling.
|
838
|
+
def emulate_cpu_throttling(factor)
|
839
|
+
if factor.nil? || factor >= 1
|
840
|
+
@client.send_message('Emulation.setCPUThrottlingRate', rate: factor || 1)
|
841
|
+
else
|
842
|
+
raise ArgumentError.new('Throttling rate should be greater or equal to 1')
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
802
846
|
# @param features [Array]
|
803
847
|
def emulate_media_features(features)
|
804
848
|
if features.nil?
|
@@ -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
|
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]
|
data/lib/puppeteer/version.rb
CHANGED
data/lib/puppeteer/wait_task.rb
CHANGED
@@ -3,7 +3,7 @@ class Puppeteer::WaitTask
|
|
3
3
|
|
4
4
|
class TerminatedError < StandardError; end
|
5
5
|
|
6
|
-
class TimeoutError <
|
6
|
+
class TimeoutError < ::Puppeteer::TimeoutError
|
7
7
|
def initialize(title:, timeout:)
|
8
8
|
super("waiting for #{title} failed: timeout #{timeout}ms exceeded")
|
9
9
|
end
|
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.10.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.19.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.36.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- YusukeIwaki
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-17 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.19.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.19.0
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: rubocop-rspec
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -244,15 +244,14 @@ files:
|
|
244
244
|
- ".rspec"
|
245
245
|
- ".rubocop.yml"
|
246
246
|
- CHANGELOG.md
|
247
|
-
- Dockerfile
|
248
247
|
- Gemfile
|
249
248
|
- LICENSE
|
250
249
|
- README.md
|
251
250
|
- Rakefile
|
252
251
|
- bin/console
|
253
252
|
- bin/setup
|
254
|
-
- docker-compose.yml
|
255
253
|
- docs/api_coverage.md
|
254
|
+
- lib/puppeteer-ruby.rb
|
256
255
|
- lib/puppeteer.rb
|
257
256
|
- lib/puppeteer/aria_query_handler.rb
|
258
257
|
- lib/puppeteer/browser.rb
|
@@ -294,7 +293,6 @@ files:
|
|
294
293
|
- lib/puppeteer/keyboard/key_description.rb
|
295
294
|
- lib/puppeteer/keyboard/us_keyboard_layout.rb
|
296
295
|
- lib/puppeteer/launcher.rb
|
297
|
-
- lib/puppeteer/launcher/base.rb
|
298
296
|
- lib/puppeteer/launcher/browser_options.rb
|
299
297
|
- lib/puppeteer/launcher/chrome.rb
|
300
298
|
- lib/puppeteer/launcher/chrome_arg_options.rb
|
@@ -302,8 +300,11 @@ files:
|
|
302
300
|
- lib/puppeteer/launcher/launch_options.rb
|
303
301
|
- lib/puppeteer/lifecycle_watcher.rb
|
304
302
|
- lib/puppeteer/mouse.rb
|
303
|
+
- lib/puppeteer/network_condition.rb
|
304
|
+
- lib/puppeteer/network_conditions.rb
|
305
305
|
- lib/puppeteer/network_manager.rb
|
306
306
|
- lib/puppeteer/page.rb
|
307
|
+
- lib/puppeteer/page/metrics.rb
|
307
308
|
- lib/puppeteer/page/pdf_options.rb
|
308
309
|
- lib/puppeteer/page/screenshot_options.rb
|
309
310
|
- lib/puppeteer/page/screenshot_task_queue.rb
|
data/Dockerfile
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
FROM circleci/ruby:3.0.0-rc1-buster-node-browsers
|
2
|
-
|
3
|
-
USER root
|
4
|
-
|
5
|
-
RUN wget -O nightly.tar.bz2 "https://download.mozilla.org/?product=firefox-nightly-latest-ssl&os=linux64&lang=en-US" \
|
6
|
-
&& tar xf nightly.tar.bz2 \
|
7
|
-
&& ln -s $(pwd)/firefox/firefox /usr/bin/firefox
|
8
|
-
|
9
|
-
USER circleci
|
data/docker-compose.yml
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
version: "3"
|
2
|
-
services:
|
3
|
-
chrome:
|
4
|
-
tty: true
|
5
|
-
stdin_open: true
|
6
|
-
build: .
|
7
|
-
environment:
|
8
|
-
BUNDLE_PATH: /usr/local/bundle
|
9
|
-
DEBUG: 1
|
10
|
-
CI: 1
|
11
|
-
volumes:
|
12
|
-
- .:/puppeteer-ruby
|
13
|
-
- bundle-data:/usr/local/bundle
|
14
|
-
working_dir: /puppeteer-ruby
|
15
|
-
command: bundle exec rspec
|
16
|
-
|
17
|
-
firefox:
|
18
|
-
tty: true
|
19
|
-
stdin_open: true
|
20
|
-
build: .
|
21
|
-
environment:
|
22
|
-
BUNDLE_PATH: /usr/local/bundle
|
23
|
-
PUPPETEER_PRODUCT_RSPEC: firefox
|
24
|
-
DEBUG: 1
|
25
|
-
CI: 1
|
26
|
-
volumes:
|
27
|
-
- .:/puppeteer-ruby
|
28
|
-
- bundle-data:/usr/local/bundle
|
29
|
-
working_dir: /puppeteer-ruby
|
30
|
-
command: bundle exec rspec spec/integration/
|
31
|
-
|
32
|
-
volumes:
|
33
|
-
bundle-data:
|
34
|
-
driver: local
|
@@ -1,66 +0,0 @@
|
|
1
|
-
module Puppeteer::Launcher
|
2
|
-
class Base
|
3
|
-
# @param {string} projectRoot
|
4
|
-
# @param {string} preferredRevision
|
5
|
-
def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
|
6
|
-
@project_root = project_root
|
7
|
-
@preferred_revision = preferred_revision
|
8
|
-
@is_puppeteer_core = is_puppeteer_core
|
9
|
-
end
|
10
|
-
|
11
|
-
class ExecutablePathNotFound < StandardError; end
|
12
|
-
|
13
|
-
# @returns [String] Chrome Executable file path.
|
14
|
-
# @raise [ExecutablePathNotFound]
|
15
|
-
def resolve_executable_path
|
16
|
-
if !@is_puppeteer_core
|
17
|
-
# puppeteer-core doesn't take into account PUPPETEER_* env variables.
|
18
|
-
executable_path = ENV['PUPPETEER_EXECUTABLE_PATH']
|
19
|
-
if FileTest.exist?(executable_path)
|
20
|
-
return executable_path
|
21
|
-
end
|
22
|
-
raise ExecutablePathNotFound.new(
|
23
|
-
"Tried to use PUPPETEER_EXECUTABLE_PATH env variable to launch browser but did not find any executable at: #{executable_path}",
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
|
-
# temporal logic.
|
28
|
-
if Puppeteer.env.darwin?
|
29
|
-
case self
|
30
|
-
when Chrome
|
31
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
32
|
-
when Firefox
|
33
|
-
'/Applications/Firefox Nightly.app/Contents/MacOS/firefox'
|
34
|
-
end
|
35
|
-
elsif Puppeteer.env.windows?
|
36
|
-
case self
|
37
|
-
when Chrome
|
38
|
-
'C:\Program Files\Google\Chrome\Application\chrome.exe'
|
39
|
-
# 'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
|
40
|
-
when Firefox
|
41
|
-
'C:\Program Files\Firefox Nightly\firefox.exe'
|
42
|
-
end
|
43
|
-
else
|
44
|
-
case self
|
45
|
-
when Chrome
|
46
|
-
'/usr/bin/google-chrome'
|
47
|
-
when Firefox
|
48
|
-
'/usr/bin/firefox'
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# const browserFetcher = new BrowserFetcher(launcher._projectRoot);
|
53
|
-
# if (!launcher._isPuppeteerCore) {
|
54
|
-
# const revision = process.env['PUPPETEER_CHROMIUM_REVISION'];
|
55
|
-
# if (revision) {
|
56
|
-
# const revisionInfo = browserFetcher.revisionInfo(revision);
|
57
|
-
# const missingText = !revisionInfo.local ? 'Tried to use PUPPETEER_CHROMIUM_REVISION env variable to launch browser but did not find executable at: ' + revisionInfo.executablePath : null;
|
58
|
-
# return {executablePath: revisionInfo.executablePath, missingText};
|
59
|
-
# }
|
60
|
-
# }
|
61
|
-
# const revisionInfo = browserFetcher.revisionInfo(launcher._preferredRevision);
|
62
|
-
# const missingText = !revisionInfo.local ? `Browser is not downloaded. Run "npm install" or "yarn install"` : null;
|
63
|
-
# return {executablePath: revisionInfo.executablePath, missingText};
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|