puppeteer-ruby 0.34.2 → 0.36.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 +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
|