puppeteer-ruby 0.36.0 → 0.37.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d4af926ca9ad39046dcc4ce9507622bf72c3263b747516ee1ba642c02e8e38d
4
- data.tar.gz: 3eff89952883a47270dee6a9ee7281609dbbe7bac197eada9e6b6dcabbaa1f78
3
+ metadata.gz: 9e46056e6227e597554a407d47c132610af952d42b1cd7c2149e2046e0d8f1bb
4
+ data.tar.gz: 99a5c6fd6885181e32c72086a768c60583738974a3c86a05a693af2b97b51bc3
5
5
  SHA512:
6
- metadata.gz: d83085eb31bb6d0d9f125e4c82fe172f46676bc373bd7652b2be430984570f7facff20bd3b1da20dbcb41beca52a740064b9b6d5cb6f51a69bf8f4f97e699947
7
- data.tar.gz: 141bed0475d5e40a0ddb9ec0cb2a82f44313a14e835a7d5cc271a413dad0c9881658a81f0ec0567d7896a7658539fc131bb5069c1c07c6573f6b633c99a53f8e
6
+ metadata.gz: d4b1032b52bb5a0ad92e57f0bac5fe5208b785679f1d7efff0ab335cb28478f6110979102d0ec4759ef2f86654b4d231e018aa9cdb3982dc1ffa2e985fa4c55f
7
+ data.tar.gz: 42f81fe580fcb85b5c57397962362c3c6b0ba7462fe210c65ee017b9e73c0a3d9d091c03a7b18f6e6d78f3a3c52572a5d0bb8284f1087412344a184e8624170d
data/CHANGELOG.md CHANGED
@@ -1,51 +1,57 @@
1
- ### master [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.36.0...master)]
1
+ ### master [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.0...master)]
2
2
 
3
- * xxx
3
+ - xxx
4
+
5
+ ### 0.37.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.36.0...0.37.0)]
6
+
7
+ New features:
8
+
9
+ - Some features introduced in Puppeteer 10.4
4
10
 
5
11
  ### 0.36.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.35.1...0.36.0)]
6
12
 
7
13
  New features:
8
14
 
9
- * Drag and Drop feature introduced in Puppeteer 10.1
10
- * `Page#emulateNetworkConditions`, `Page#emulateCPUThrottling`
11
- * `Page#exposeFunction`
12
- * Metrics
15
+ - Drag and Drop feature introduced in Puppeteer 10.1
16
+ - `Page#emulateNetworkConditions`, `Page#emulateCPUThrottling`
17
+ - `Page#exposeFunction`
18
+ - Metrics
13
19
 
14
20
  ### 0.35.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.35.0...0.35.1)]
15
21
 
16
22
  New features:
17
23
 
18
- * Allow Rails users to use this library without `require 'puppeteer'`.
24
+ - Allow Rails users to use this library without `require 'puppeteer'`.
19
25
 
20
26
  ### 0.35.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.3...0.35.0)]
21
27
 
22
28
  New features:
23
29
 
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`.
30
+ - 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
31
 
26
32
  ### 0.34.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.2...0.34.3)]
27
33
 
28
34
  Bugfix:
29
35
 
30
- * Fix wait_for_xxx's timeout error type.
36
+ - Fix wait_for_xxx's timeout error type.
31
37
 
32
38
  ### 0.34.2 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.1...0.34.2)]
33
39
 
34
40
  New features:
35
41
 
36
- * Add `Page#bring_to_front`.
42
+ - Add `Page#bring_to_front`.
37
43
 
38
44
  ### 0.34.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.34.0...0.34.1)]
39
45
 
40
46
  Bugfix:
41
47
 
42
- * Fix `Page#pdf` to work without `path` parameter.
48
+ - Fix `Page#pdf` to work without `path` parameter.
43
49
 
44
50
  ### 0.34.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.33.0...0.34.0)]
45
51
 
46
52
  New features:
47
53
 
48
- * Sync API with block
54
+ - Sync API with block
49
55
 
50
56
  ### 0.33.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.32.4...0.33.0)]
51
57
 
@@ -53,170 +59,169 @@ NOTE: Requires Ruby version >= 2.6 explicitly since this version.
53
59
 
54
60
  Bugfix:
55
61
 
56
- * Fix PDF options to work correctly on decimal numbers specified.
62
+ - Fix PDF options to work correctly on decimal numbers specified.
57
63
 
58
64
  ### 0.32.4 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.32.3...0.32.4)]
59
65
 
60
66
  Bugfix:
61
67
 
62
- * Fix `#type_text` to input '<' correctly.
68
+ - Fix `#type_text` to input '<' correctly.
63
69
 
64
70
  ### 0.32.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.32.2...0.32.3)]
65
71
 
66
72
  Bugfix:
67
73
 
68
- * Fix puppeteer-ruby to work on Rails in development mode.
69
-
74
+ - Fix puppeteer-ruby to work on Rails in development mode.
70
75
 
71
76
  ### 0.32.2 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.32.1...0.32.2)]
72
77
 
73
78
  Bugfix:
74
79
 
75
- * Fix full_page option in screenshot.
80
+ - Fix full_page option in screenshot.
76
81
 
77
82
  ### 0.32.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.32.0...0.32.1)]
78
83
 
79
84
  Bugfix:
80
85
 
81
- * Fix WebSocket to work with `wss://...` endpoint (ex. browserless.io)
86
+ - Fix WebSocket to work with `wss://...` endpoint (ex. browserless.io)
82
87
 
83
88
  ### 0.32.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.31.6...0.32.0)]
84
89
 
85
90
  New features:
86
91
 
87
- * Tracing
88
- * JS/CSS coverages
92
+ - Tracing
93
+ - JS/CSS coverages
89
94
 
90
95
  Improvement:
91
96
 
92
- * Increase stability [#92](https://github.com/YusukeIwaki/puppeteer-ruby/pull/92)
97
+ - Increase stability [#92](https://github.com/YusukeIwaki/puppeteer-ruby/pull/92)
93
98
 
94
99
  ### 0.31.6 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.31.5...0.31.6)]
95
100
 
96
101
  Improvement:
97
102
 
98
- * Increase stability [#87](https://github.com/YusukeIwaki/puppeteer-ruby/pull/87)
103
+ - Increase stability [#87](https://github.com/YusukeIwaki/puppeteer-ruby/pull/87)
99
104
 
100
105
  ### 0.31.5 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.31.4...0.31.5)]
101
106
 
102
107
  Bugfix:
103
108
 
104
- * Fix file uploading to work without crash.
109
+ - Fix file uploading to work without crash.
105
110
 
106
111
  ### 0.31.4 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.31.3...0.31.4)]
107
112
 
108
113
  Bugfix:
109
114
 
110
- * Fix PDF options (format, margin, omit_background) to work.
115
+ - Fix PDF options (format, margin, omit_background) to work.
111
116
 
112
117
  ### 0.31.3 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.31.1...0.31.3)]
113
118
 
114
119
  Bugfix:
115
120
 
116
- * Fix `wait_for_selector` to work. *It is strongly recommended to update for 0.29.0-0.31.x users.*
121
+ - Fix `wait_for_selector` to work. _It is strongly recommended to update for 0.29.0-0.31.x users._
117
122
 
118
123
  ### 0.31.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.31.0...0.31.1)]
119
124
 
120
125
  Bugfix:
121
126
 
122
- * Fix `Page#pdf` to work on Windows.
127
+ - Fix `Page#pdf` to work on Windows.
123
128
 
124
129
  ### 0.31.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.30.0...0.31.0)]
125
130
 
126
131
  New features:
127
132
 
128
- * Now puppeteer-ruby is compatible with Windows
133
+ - Now puppeteer-ruby is compatible with Windows
129
134
 
130
135
  Bugfix:
131
136
 
132
- * Fix `Page#add_script_tag` and `Page#add_style_tag` to work
137
+ - Fix `Page#add_script_tag` and `Page#add_style_tag` to work
133
138
 
134
139
  ### 0.30.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.29.0...0.30.0)]
135
140
 
136
141
  New features:
137
142
 
138
- * S, SS, Seval, SSeval is renamed to query_selector, query_selector_all, eval_on_selector, eval_on_selector_all
143
+ - S, SS, Seval, SSeval is renamed to query_selector, query_selector_all, eval_on_selector, eval_on_selector_all
139
144
 
140
145
  ### 0.29.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.28.1...0.29.0)]
141
146
 
142
147
  New features:
143
148
 
144
- * Add `AriaQueryHandler`. Now we can use "aria/...." for selectors.
149
+ - Add `AriaQueryHandler`. Now we can use "aria/...." for selectors.
145
150
 
146
151
  ### 0.28.1 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.27...0.28.1)]
147
152
 
148
153
  New features:
149
154
 
150
- * Add `Page#emulate_idle_state`
151
- * Change versioning rule.
155
+ - Add `Page#emulate_idle_state`
156
+ - Change versioning rule.
152
157
 
153
158
  ### 0.0.27 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.26...0.0.27)]
154
159
 
155
160
  New features:
156
161
 
157
- * Now puppeteer-ruby is Ruby 3.0 compatible!
162
+ - Now puppeteer-ruby is Ruby 3.0 compatible!
158
163
 
159
164
  ### 0.0.26 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.25...0.0.26)]
160
165
 
161
166
  Bugfix:
162
167
 
163
- * Fix `Page#screenshot` working correctly with `quality` parameter.
168
+ - Fix `Page#screenshot` working correctly with `quality` parameter.
164
169
 
165
170
  ### 0.0.25 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.23...0.0.25)]
166
171
 
167
172
  New feature:
168
173
 
169
- * **Cookie** feature: `Page#set_cookie`, `Page#cookies`
174
+ - **Cookie** feature: `Page#set_cookie`, `Page#cookies`
170
175
 
171
176
  ### 0.0.23 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.22...0.0.23)]
172
177
 
173
178
  New feature:
174
179
 
175
- * **GeoLocation** feature
176
- * grant/clear permission
180
+ - **GeoLocation** feature
181
+ - grant/clear permission
177
182
 
178
183
  Bugfix/Improvement:
179
184
 
180
- * Refactoring for events ([#31](https://github.com/YusukeIwaki/puppeteer-ruby/pull/31))
181
- * Improve SEND/RECV handling in CDPSession ([#34](https://github.com/YusukeIwaki/puppeteer-ruby/pull/34))
185
+ - Refactoring for events ([#31](https://github.com/YusukeIwaki/puppeteer-ruby/pull/31))
186
+ - Improve SEND/RECV handling in CDPSession ([#34](https://github.com/YusukeIwaki/puppeteer-ruby/pull/34))
182
187
 
183
188
  ### 0.0.22 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.21...0.0.22)]
184
189
 
185
190
  Bugfix
186
191
 
187
- * Make `Puppeteer#default_args` to work
188
- * Respect Firefox launch options
189
- * Respect `default_viewport: nil`
192
+ - Make `Puppeteer#default_args` to work
193
+ - Respect Firefox launch options
194
+ - Respect `default_viewport: nil`
190
195
 
191
196
  ### 0.0.21 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.20...0.0.21)]
192
197
 
193
198
  Bugfix/Improvement:
194
199
 
195
- * Update DeviceDescriptors (list of emulatable devices)
196
- * Fix bug on inputing "(" ([#25](https://github.com/YusukeIwaki/puppeteer-ruby/pull/25))
200
+ - Update DeviceDescriptors (list of emulatable devices)
201
+ - Fix bug on inputing "(" ([#25](https://github.com/YusukeIwaki/puppeteer-ruby/pull/25))
197
202
 
198
203
  ### 0.0.20 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.19...0.0.20)]
199
204
 
200
205
  New feature
201
206
 
202
- * Dialog-handling feature
207
+ - Dialog-handling feature
203
208
 
204
209
  ### 0.0.19 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.18...0.0.19)]
205
210
 
206
211
  New feature
207
212
 
208
- * **Firefox support**
213
+ - **Firefox support**
209
214
 
210
215
  Bugfix/Improvement
211
216
 
212
- * Allow `Page#keyboard` with block ([#18](https://github.com/YusukeIwaki/puppeteer-ruby/pull/18))
217
+ - Allow `Page#keyboard` with block ([#18](https://github.com/YusukeIwaki/puppeteer-ruby/pull/18))
213
218
 
214
219
  ### 0.0.18 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.0.17...0.0.18)]
215
220
 
216
221
  New feature
217
222
 
218
- * **Firefox support**
223
+ - **Firefox support**
219
224
 
220
225
  Bugfix/Improvement
221
226
 
222
- * Allow `Page#keyboard` with block ([#18](https://github.com/YusukeIwaki/puppeteer-ruby/pull/18))
227
+ - Allow `Page#keyboard` with block ([#18](https://github.com/YusukeIwaki/puppeteer-ruby/pull/18))
data/README.md CHANGED
@@ -156,6 +156,75 @@ RSpec.describe 'hotel.testplanisphere.dev', type: :feature do
156
156
 
157
157
  The detailed step of configuration can be found [here](https://github.com/YusukeIwaki/puppeteer-ruby-example/tree/master/_with_capybara-rspec).
158
158
 
159
+ ## :bulb: Use Puppeteer methods simply without Capybara::DSL
160
+
161
+ We can also use puppeteer-ruby as it is without Capybara DSL. When you want to just test a Rails application simply with Puppeteer, refer this section.
162
+
163
+ Also, if you have trouble with handling flaky/unstable testcases in existing feature/system specs, consider replacing Capybara::DSL with raw puppeteer-ruby codes like `page.wait_for_selector(...)` or `page.wait_for_navigation { ... }`.
164
+
165
+ Capybara prepares test server even when Capybara DSL is not used.
166
+
167
+ Sample configuration is shown below. You can use it by putting the file at `spec/support/puppeteer_ruby.rb` or another location where RSpec loads on initialization.
168
+
169
+ ```ruby
170
+ RSpec.configure do |config|
171
+ require 'capybara'
172
+
173
+ # This driver only requests Capybara to launch test server.
174
+ # Remark that no Capybara::DSL is available with this driver.
175
+ class CapybaraNullDriver < Capybara::Driver::Base
176
+ def needs_server?
177
+ true
178
+ end
179
+ end
180
+
181
+ Capybara.register_driver(:null) { CapybaraNullDriver.new }
182
+
183
+ config.around(driver: :null) do |example|
184
+ Capybara.current_driver = :null
185
+
186
+ # Rails server is launched here,
187
+ # (at the first time of accessing Capybara.current_session.server)
188
+ @base_url = Capybara.current_session.server.base_url
189
+
190
+ require 'puppeteer'
191
+ launch_options = {
192
+ # Use launch options as you like.
193
+ channel: :chrome,
194
+ headless: false,
195
+ }
196
+ Puppeteer.launch(**launch_options) do |browser|
197
+ @puppeteer_page = browser.new_page
198
+ example.run
199
+ end
200
+
201
+ Capybara.reset_sessions!
202
+ Capybara.use_default_driver
203
+ end
204
+ end
205
+ ```
206
+
207
+ Now, we can work with integration test using `Puppeteer::Page` in puppeteer-ruby.
208
+
209
+ ```ruby
210
+ RSpec.describe 'Sample integration tests', driver: :null do
211
+ let(:page) { @puppeteer_page }
212
+ let(:base_url) { @base_url }
213
+
214
+ it 'should work with Puppeteer' do
215
+ # null driver only launches server, and Capybara::DSL is unavailable.
216
+ expect { visit '/' }.to raise_error(/NotImplementedError/)
217
+
218
+ page.goto("#{base_url}/")
219
+
220
+ # Automation with Puppeteer
221
+ h1_text = page.eval_on_selector('h1', '(el) => el.textContent')
222
+ expect(h1_text).to eq('It works!')
223
+ end
224
+ end
225
+ ```
226
+
227
+
159
228
  ## API
160
229
 
161
230
  https://yusukeiwaki.github.io/puppeteer-ruby-docs/
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
- - Puppeteer version: v10.2.0
3
- - puppeteer-ruby version: 0.36.0
2
+ - Puppeteer version: v10.4.0
3
+ - puppeteer-ruby version: 0.37.0
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -136,6 +136,7 @@
136
136
  * waitForFileChooser => `#wait_for_file_chooser`
137
137
  * waitForFunction => `#wait_for_function`
138
138
  * waitForNavigation => `#wait_for_navigation`
139
+ * ~~waitForNetworkIdle~~
139
140
  * waitForRequest => `#wait_for_request`
140
141
  * waitForResponse => `#wait_for_response`
141
142
  * waitForSelector => `#wait_for_selector`
@@ -223,7 +223,7 @@ class Puppeteer::Browser
223
223
  # @param predicate [Proc(Puppeteer::Target -> Boolean)]
224
224
  # @return [Puppeteer::Target]
225
225
  def wait_for_target(predicate:, timeout: nil)
226
- timeout_in_sec = (timeout || 30000).to_i / 1000.0
226
+ timeout_helper = Puppeteer::TimeoutHelper.new('target', timeout_ms: timeout, default_timeout_ms: 30000)
227
227
  existing_target = targets.find { |target| predicate.call(target) }
228
228
  return existing_target if existing_target
229
229
 
@@ -241,15 +241,9 @@ class Puppeteer::Browser
241
241
  end
242
242
 
243
243
  begin
244
- if timeout_in_sec > 0
245
- Timeout.timeout(timeout_in_sec) do
246
- target_promise.value!
247
- end
248
- else
244
+ timeout_helper.with_timeout do
249
245
  target_promise.value!
250
246
  end
251
- rescue Timeout::Error
252
- raise Puppeteer::TimeoutError.new("waiting for target failed: timeout #{timeout}ms exceeded")
253
247
  ensure
254
248
  remove_event_listener(*event_listening_ids)
255
249
  end
@@ -74,6 +74,7 @@ class Puppeteer::BrowserContext
74
74
  'clipboard-read' => 'clipboardReadWrite',
75
75
  'clipboard-write' => 'clipboardReadWrite',
76
76
  'payment-handler' => 'paymentHandler',
77
+ 'persistent-storage' => 'durableStorage',
77
78
  'idle-detection' => 'idleDetection',
78
79
  # chrome-specific permissions we have.
79
80
  'midi-sysex' => 'midiSysex',
@@ -5,10 +5,14 @@ class Puppeteer::Coverage
5
5
  @css = Puppeteer::CSSCoverage.new(client)
6
6
  end
7
7
 
8
- def start_js_coverage(reset_on_navigation: nil, report_anonymous_scripts: nil)
8
+ def start_js_coverage(
9
+ reset_on_navigation: nil,
10
+ report_anonymous_scripts: nil,
11
+ include_raw_script_coverage: nil)
9
12
  @js.start(
10
13
  reset_on_navigation: reset_on_navigation,
11
14
  report_anonymous_scripts: report_anonymous_scripts,
15
+ include_raw_script_coverage: include_raw_script_coverage,
12
16
  )
13
17
  end
14
18
 
@@ -16,7 +20,11 @@ class Puppeteer::Coverage
16
20
  @js.stop
17
21
  end
18
22
 
19
- def js_coverage(reset_on_navigation: nil, report_anonymous_scripts: nil, &block)
23
+ def js_coverage(
24
+ reset_on_navigation: nil,
25
+ report_anonymous_scripts: nil,
26
+ include_raw_script_coverage: nil,
27
+ &block)
20
28
  unless block
21
29
  raise ArgumentError.new('Block must be given')
22
30
  end
@@ -24,6 +32,7 @@ class Puppeteer::Coverage
24
32
  start_js_coverage(
25
33
  reset_on_navigation: reset_on_navigation,
26
34
  report_anonymous_scripts: report_anonymous_scripts,
35
+ include_raw_script_coverage: include_raw_script_coverage,
27
36
  )
28
37
  block.call
29
38
  stop_js_coverage
@@ -158,6 +158,84 @@ Puppeteer::DEVICES = Hash[
158
158
  isLandscape: true,
159
159
  },
160
160
  },
161
+ {
162
+ name: 'Galaxy S8',
163
+ userAgent:
164
+ 'Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36',
165
+ viewport: {
166
+ width: 360,
167
+ height: 740,
168
+ deviceScaleFactor: 3,
169
+ isMobile: true,
170
+ hasTouch: true,
171
+ isLandscape: false,
172
+ },
173
+ },
174
+ {
175
+ name: 'Galaxy S8 landscape',
176
+ userAgent:
177
+ 'Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36',
178
+ viewport: {
179
+ width: 740,
180
+ height: 360,
181
+ deviceScaleFactor: 3,
182
+ isMobile: true,
183
+ hasTouch: true,
184
+ isLandscape: true,
185
+ },
186
+ },
187
+ {
188
+ name: 'Galaxy S9+',
189
+ userAgent:
190
+ 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36',
191
+ viewport: {
192
+ width: 320,
193
+ height: 658,
194
+ deviceScaleFactor: 4.5,
195
+ isMobile: true,
196
+ hasTouch: true,
197
+ isLandscape: false,
198
+ },
199
+ },
200
+ {
201
+ name: 'Galaxy S9+ landscape',
202
+ userAgent:
203
+ 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Mobile Safari/537.36',
204
+ viewport: {
205
+ width: 658,
206
+ height: 320,
207
+ deviceScaleFactor: 4.5,
208
+ isMobile: true,
209
+ hasTouch: true,
210
+ isLandscape: true,
211
+ },
212
+ },
213
+ {
214
+ name: 'Galaxy Tab S4',
215
+ userAgent:
216
+ 'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Safari/537.36',
217
+ viewport: {
218
+ width: 712,
219
+ height: 1138,
220
+ deviceScaleFactor: 2.25,
221
+ isMobile: true,
222
+ hasTouch: true,
223
+ isLandscape: false,
224
+ },
225
+ },
226
+ {
227
+ name: 'Galaxy Tab S4 landscape',
228
+ userAgent:
229
+ 'Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.80 Safari/537.36',
230
+ viewport: {
231
+ width: 1138,
232
+ height: 712,
233
+ deviceScaleFactor: 2.25,
234
+ isMobile: true,
235
+ hasTouch: true,
236
+ isLandscape: true,
237
+ },
238
+ },
161
239
  {
162
240
  name: 'iPad',
163
241
  userAgent:
@@ -1003,6 +1081,58 @@ Puppeteer::DEVICES = Hash[
1003
1081
  isLandscape: true,
1004
1082
  },
1005
1083
  },
1084
+ {
1085
+ name: 'Pixel 3',
1086
+ userAgent:
1087
+ 'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36',
1088
+ viewport: {
1089
+ width: 393,
1090
+ height: 786,
1091
+ deviceScaleFactor: 2.75,
1092
+ isMobile: true,
1093
+ hasTouch: true,
1094
+ isLandscape: false,
1095
+ },
1096
+ },
1097
+ {
1098
+ name: 'Pixel 3 landscape',
1099
+ userAgent:
1100
+ 'Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.158 Mobile Safari/537.36',
1101
+ viewport: {
1102
+ width: 786,
1103
+ height: 393,
1104
+ deviceScaleFactor: 2.75,
1105
+ isMobile: true,
1106
+ hasTouch: true,
1107
+ isLandscape: true,
1108
+ },
1109
+ },
1110
+ {
1111
+ name: 'Pixel 4',
1112
+ userAgent:
1113
+ 'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36',
1114
+ viewport: {
1115
+ width: 353,
1116
+ height: 745,
1117
+ deviceScaleFactor: 3,
1118
+ isMobile: true,
1119
+ hasTouch: true,
1120
+ isLandscape: false,
1121
+ },
1122
+ },
1123
+ {
1124
+ name: 'Pixel 4 landscape',
1125
+ userAgent:
1126
+ 'Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Mobile Safari/537.36',
1127
+ viewport: {
1128
+ width: 745,
1129
+ height: 353,
1130
+ deviceScaleFactor: 3,
1131
+ isMobile: true,
1132
+ hasTouch: true,
1133
+ isLandscape: true,
1134
+ },
1135
+ },
1006
1136
  ].map do |json|
1007
1137
  [
1008
1138
  json[:name],
@@ -233,12 +233,13 @@ class Puppeteer::DOMWorld
233
233
  # @param url [String?]
234
234
  # @param path [String?]
235
235
  # @param content [String?]
236
+ # @param id [String?]
236
237
  # @param type [String?]
237
- def add_script_tag(url: nil, path: nil, content: nil, type: nil)
238
+ def add_script_tag(url: nil, path: nil, content: nil, id: nil, type: nil)
238
239
  if url
239
240
  begin
240
241
  return execution_context.
241
- evaluate_handle(ADD_SCRIPT_URL, url, type || '').
242
+ evaluate_handle(ADD_SCRIPT_URL, url, id, type || '').
242
243
  as_element
243
244
  rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
244
245
  raise "Loading script from #{url} failed"
@@ -251,13 +252,13 @@ class Puppeteer::DOMWorld
251
252
  contents = File.read(path)
252
253
  contents += "//# sourceURL=#{path.gsub(/\n/, '')}"
253
254
  return execution_context.
254
- evaluate_handle(ADD_SCRIPT_CONTENT, contents, type || '').
255
+ evaluate_handle(ADD_SCRIPT_CONTENT, contents, id, type || 'text/javascript').
255
256
  as_element
256
257
  end
257
258
 
258
259
  if content
259
260
  return execution_context.
260
- evaluate_handle(ADD_SCRIPT_CONTENT, content, type || '').
261
+ evaluate_handle(ADD_SCRIPT_CONTENT, content, id, type || 'text/javascript').
261
262
  as_element
262
263
  end
263
264
 
@@ -265,11 +266,11 @@ class Puppeteer::DOMWorld
265
266
  end
266
267
 
267
268
  ADD_SCRIPT_URL = <<~JAVASCRIPT
268
- async (url, type) => {
269
+ async (url, id, type) => {
269
270
  const script = document.createElement('script');
270
271
  script.src = url;
271
- if (type)
272
- script.type = type;
272
+ if (id) script.id = id;
273
+ if (type) script.type = type;
273
274
  const promise = new Promise((res, rej) => {
274
275
  script.onload = res;
275
276
  script.onerror = rej;
@@ -281,11 +282,11 @@ class Puppeteer::DOMWorld
281
282
  JAVASCRIPT
282
283
 
283
284
  ADD_SCRIPT_CONTENT = <<~JAVASCRIPT
284
- (content, type) => {
285
- if (type === undefined) type = 'text/javascript';
285
+ (content, id, type) => {
286
286
  const script = document.createElement('script');
287
287
  script.type = type;
288
288
  script.text = content;
289
+ if (id) script.id = id;
289
290
  let error = null;
290
291
  script.onerror = e => error = e;
291
292
  document.head.appendChild(script);
@@ -0,0 +1,28 @@
1
+ class Puppeteer::ElementHandle < Puppeteer::JSHandle
2
+ # A class to represent (x, y)-offset coordinates
3
+ class Offset
4
+ def initialize(x:, y:)
5
+ @x = x
6
+ @y = y
7
+ end
8
+
9
+ def self.from(offset)
10
+ case offset
11
+ when nil
12
+ nil
13
+ when Hash
14
+ if offset[:x] && offset[:y]
15
+ Offset.new(x: offset[:x], y: offset[:y])
16
+ else
17
+ raise ArgumentError.new('offset parameter must have x, y coordinates')
18
+ end
19
+ when Offset
20
+ offset
21
+ else
22
+ raise ArgumentError.new('Offset.from(Hash|Offset)')
23
+ end
24
+ end
25
+
26
+ attr_reader :x, :y
27
+ end
28
+ end
@@ -21,6 +21,17 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
21
21
  )
22
22
  end
23
23
 
24
+ def ==(other)
25
+ case other
26
+ when Hash
27
+ @x == other[:x] && @y == other[:y]
28
+ when Point
29
+ @x == other.x && @y == other.y
30
+ else
31
+ super
32
+ end
33
+ end
34
+
24
35
  attr_reader :x, :y
25
36
  end
26
37
  end
@@ -1,5 +1,6 @@
1
1
  require_relative './element_handle/bounding_box'
2
2
  require_relative './element_handle/box_model'
3
+ require_relative './element_handle/offset'
3
4
  require_relative './element_handle/point'
4
5
 
5
6
  class Puppeteer::ElementHandle < Puppeteer::JSHandle
@@ -79,7 +80,9 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
79
80
  end
80
81
  end
81
82
 
82
- def clickable_point
83
+ def clickable_point(offset = nil)
84
+ offset_param = Offset.from(offset)
85
+
83
86
  result =
84
87
  begin
85
88
  @remote_object.content_quads(@client)
@@ -105,6 +108,19 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
105
108
  raise ElementNotVisibleError.new
106
109
  end
107
110
 
111
+ if offset_param
112
+ # Return the point of the first quad identified by offset.
113
+ quad = quads.first
114
+ min_x = quad.map(&:x).min
115
+ min_y = quad.map(&:y).min
116
+ if min_x && min_y
117
+ return Point.new(
118
+ x: min_x + offset_param.x,
119
+ y: min_y + offset_param.y,
120
+ )
121
+ end
122
+ end
123
+
108
124
  # Return the middle point of the first quad.
109
125
  quads.first.reduce(:+) / 4
110
126
  end
@@ -139,9 +155,10 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
139
155
  # @param delay [Number]
140
156
  # @param button [String] "left"|"right"|"middle"
141
157
  # @param click_count [Number]
142
- def click(delay: nil, button: nil, click_count: nil)
158
+ # @param offset [Hash]
159
+ def click(delay: nil, button: nil, click_count: nil, offset: nil)
143
160
  scroll_into_view_if_needed
144
- point = clickable_point
161
+ point = clickable_point(offset)
145
162
  @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
146
163
  end
147
164
 
@@ -436,10 +453,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
436
453
  define_async_method :async_Sx
437
454
 
438
455
  # in JS, #isIntersectingViewport.
456
+ # @param threshold [Float|nil]
439
457
  # @return [Boolean]
440
- def intersecting_viewport?
458
+ def intersecting_viewport?(threshold: nil)
459
+ option_threshold = threshold || 0
441
460
  js = <<~JAVASCRIPT
442
- async element => {
461
+ async (element, threshold) => {
443
462
  const visibleRatio = await new Promise(resolve => {
444
463
  const observer = new IntersectionObserver(entries => {
445
464
  resolve(entries[0].intersectionRatio);
@@ -447,11 +466,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
447
466
  });
448
467
  observer.observe(element);
449
468
  });
450
- return visibleRatio > 0;
469
+ if (threshold === 1) return visibleRatio === 1;
470
+ else return visibleRatio > threshold;
451
471
  }
452
472
  JAVASCRIPT
453
473
 
454
- evaluate(js)
474
+ evaluate(js, option_threshold)
455
475
  end
456
476
 
457
477
  # @param quad [Array<Point>]
@@ -157,8 +157,9 @@ class Puppeteer::Frame
157
157
  # @param path [String?]
158
158
  # @param content [String?]
159
159
  # @param type [String?]
160
- def add_script_tag(url: nil, path: nil, content: nil, type: nil)
161
- @main_world.add_script_tag(url: url, path: path, content: content, type: type)
160
+ # @param id [String?]
161
+ def add_script_tag(url: nil, path: nil, content: nil, type: nil, id: nil)
162
+ @main_world.add_script_tag(url: url, path: path, content: content, type: type, id: id)
162
163
  end
163
164
 
164
165
  # @param url [String?]
@@ -12,6 +12,14 @@ class Puppeteer::JSCoverage
12
12
  attr_reader :url, :ranges, :text
13
13
  end
14
14
 
15
+ class ItemWithRawScriptCoverage < Item
16
+ def initialize(url:, ranges:, text:, raw_script_coverage:)
17
+ super(url: url, ranges: ranges, text: text)
18
+ @raw_script_coverage = raw_script_coverage
19
+ end
20
+ attr_reader :raw_script_coverage
21
+ end
22
+
15
23
  # @param client [Puppeteer::CDPSession]
16
24
  def initialize(client)
17
25
  @client = client
@@ -20,7 +28,10 @@ class Puppeteer::JSCoverage
20
28
  @script_sources = {}
21
29
  end
22
30
 
23
- def start(reset_on_navigation: nil, report_anonymous_scripts: nil)
31
+ def start(
32
+ reset_on_navigation: nil,
33
+ report_anonymous_scripts: nil,
34
+ include_raw_script_coverage: nil)
24
35
  raise 'JSCoverage is already enabled' if @enabled
25
36
 
26
37
  @reset_on_navigation =
@@ -30,6 +41,7 @@ class Puppeteer::JSCoverage
30
41
  true
31
42
  end
32
43
  @report_anonymous_scripts = report_anonymous_scripts || false
44
+ @include_raw_script_coverage = include_raw_script_coverage || false
33
45
  @enabled = true
34
46
  @script_urls.clear
35
47
  @script_sources.clear
@@ -43,7 +55,7 @@ class Puppeteer::JSCoverage
43
55
  await_all(
44
56
  @client.async_send_message('Profiler.enable'),
45
57
  @client.async_send_message('Profiler.startPreciseCoverage',
46
- callCount: false,
58
+ callCount: @include_raw_script_coverage,
47
59
  detailed: true,
48
60
  ),
49
61
  @client.async_send_message('Debugger.enable'),
@@ -107,11 +119,20 @@ class Puppeteer::JSCoverage
107
119
  end
108
120
  end
109
121
 
110
- coverage << Item.new(
111
- url: url,
112
- ranges: convert_to_disjoint_ranges(flatten_ranges),
113
- text: text,
114
- )
122
+ if @include_raw_script_coverage
123
+ coverage << ItemWithRawScriptCoverage.new(
124
+ url: url,
125
+ ranges: convert_to_disjoint_ranges(flatten_ranges),
126
+ text: text,
127
+ raw_script_coverage: entry,
128
+ )
129
+ else
130
+ coverage << Item.new(
131
+ url: url,
132
+ ranges: convert_to_disjoint_ranges(flatten_ranges),
133
+ text: text,
134
+ )
135
+ end
115
136
  end
116
137
 
117
138
  coverage
@@ -15,7 +15,7 @@ class Puppeteer::Page
15
15
  # @params options [Hash]
16
16
  def initialize(options)
17
17
  if options[:type]
18
- unless [:png, :jpeg].include?(options[:type].to_sym)
18
+ unless [:png, :jpeg, :webp].include?(options[:type].to_sym)
19
19
  raise ArgumentError.new("Unknown options.type value: #{options[:type]}")
20
20
  end
21
21
  @type = options[:type]
@@ -25,6 +25,8 @@ class Puppeteer::Page
25
25
  @type = 'png'
26
26
  elsif mime_types.include?('image/jpeg')
27
27
  @type = 'jpeg'
28
+ elsif mime_types.include?('image/webp')
29
+ @type = 'webp'
28
30
  else
29
31
  raise ArgumentError.new("Unsupported screenshot mime type resolved: #{mime_types}, path: #{options[:path]}")
30
32
  end
@@ -404,8 +404,9 @@ class Puppeteer::Page
404
404
  # @param path [String?]
405
405
  # @param content [String?]
406
406
  # @param type [String?]
407
- def add_script_tag(url: nil, path: nil, content: nil, type: nil)
408
- main_frame.add_script_tag(url: url, path: path, content: content, type: type)
407
+ # @param id [String?]
408
+ def add_script_tag(url: nil, path: nil, content: nil, type: nil, id: nil)
409
+ main_frame.add_script_tag(url: url, path: path, content: content, type: type, id: id)
409
410
  end
410
411
 
411
412
  # @param url [String?]
@@ -965,7 +966,7 @@ class Puppeteer::Page
965
966
  main_frame.title
966
967
  end
967
968
 
968
- # @param type [String] "png"|"jpeg"
969
+ # @param type [String] "png"|"jpeg"|"webp"
969
970
  # @param path [String]
970
971
  # @param full_page [Boolean]
971
972
  # @param clip [Hash]
@@ -1059,11 +1060,20 @@ class Puppeteer::Page
1059
1060
 
1060
1061
  # @return [Enumerable<String>]
1061
1062
  def create_pdf_stream(options = {})
1063
+ timeout_helper = Puppeteer::TimeoutHelper.new('Page.printToPDF',
1064
+ timeout_ms: options[:timeout],
1065
+ default_timeout_ms: 30000)
1062
1066
  pdf_options = PDFOptions.new(options)
1063
1067
  omit_background = options[:omit_background]
1064
1068
  set_transparent_background_color if omit_background
1065
- result = @client.send_message('Page.printToPDF', pdf_options.page_print_args)
1066
- reset_default_background_color if omit_background
1069
+ result =
1070
+ begin
1071
+ timeout_helper.with_timeout do
1072
+ @client.send_message('Page.printToPDF', pdf_options.page_print_args)
1073
+ end
1074
+ ensure
1075
+ reset_default_background_color if omit_background
1076
+ end
1067
1077
 
1068
1078
  Puppeteer::ProtocolStreamReader.new(
1069
1079
  client: @client,
@@ -0,0 +1,22 @@
1
+ require 'timeout'
2
+
3
+ class Puppeteer::TimeoutHelper
4
+ # @param timeout_ms [String|Integer|nil]
5
+ # @param default_timeout_ms [Integer]
6
+ def initialize(task_name, timeout_ms:, default_timeout_ms:)
7
+ @task_name = task_name
8
+ @timeout_ms = (timeout_ms || default_timeout_ms).to_i
9
+ end
10
+
11
+ def with_timeout(&block)
12
+ if @timeout_ms > 0
13
+ begin
14
+ Timeout.timeout(@timeout_ms / 1000.0, &block)
15
+ rescue Timeout::Error
16
+ raise Puppeteer::TimeoutError.new("waiting for #{@task_name} failed: timeout #{@timeout_ms}ms exceeded")
17
+ end
18
+ else
19
+ block.call
20
+ end
21
+ end
22
+ end
@@ -27,11 +27,16 @@ class Puppeteer::Tracing
27
27
  option_categories << 'disabled-by-default-devtools.screenshot'
28
28
  end
29
29
 
30
+ ex_cat = option_categories.select { |cat| cat.start_with?('-') }.map { |cat| cat[1..-1] }
31
+ in_cat = option_categories.reject { |cat| cat.start_with?('-') }
30
32
  @path = path
31
33
  @recording = true
32
34
  @client.send_message('Tracing.start',
33
35
  transferMode: 'ReturnAsStream',
34
- categories: option_categories.join(','),
36
+ traceConfig: {
37
+ excludedCategories: ex_cat,
38
+ includedCategories: in_cat,
39
+ },
35
40
  )
36
41
  end
37
42
 
@@ -1,3 +1,3 @@
1
1
  module Puppeteer
2
- VERSION = '0.36.0'
2
+ VERSION = '0.37.0'
3
3
  end
@@ -55,6 +55,7 @@ class Puppeteer::WebSocket
55
55
  def initialize(url:, max_payload_size:)
56
56
  @impl = DriverImpl.new(url)
57
57
  @driver = ::WebSocket::Driver.client(@impl, max_length: max_payload_size)
58
+ @driver.set_header('User-Agent', "Puppeteer #{Puppeteer::VERSION}")
58
59
 
59
60
  setup
60
61
  @driver.start
data/lib/puppeteer.rb CHANGED
@@ -54,6 +54,7 @@ require 'puppeteer/request'
54
54
  require 'puppeteer/response'
55
55
  require 'puppeteer/target'
56
56
  require 'puppeteer/tracing'
57
+ require 'puppeteer/timeout_helper'
57
58
  require 'puppeteer/timeout_settings'
58
59
  require 'puppeteer/touch_screen'
59
60
  require 'puppeteer/version'
@@ -66,17 +67,19 @@ require 'puppeteer/element_handle'
66
67
 
67
68
  # ref: https://github.com/puppeteer/puppeteer/blob/master/lib/Puppeteer.js
68
69
  module Puppeteer
69
- module_function def method_missing(method, *args, **kwargs, &block)
70
- @puppeteer ||= ::Puppeteer::Puppeteer.new(
71
- project_root: __dir__,
72
- preferred_revision: '706915',
73
- is_puppeteer_core: true,
74
- )
75
-
76
- if kwargs.empty? # for Ruby < 2.7
77
- @puppeteer.public_send(method, *args, &block)
78
- else
79
- @puppeteer.public_send(method, *args, **kwargs, &block)
70
+ @puppeteer ||= ::Puppeteer::Puppeteer.new(
71
+ project_root: __dir__,
72
+ preferred_revision: '706915',
73
+ is_puppeteer_core: true,
74
+ ).tap do |instance|
75
+ instance.public_methods(false).each do |method_name|
76
+ define_singleton_method(method_name) do |*args, **kwargs, &block|
77
+ if kwargs.empty? # for Ruby < 2.7
78
+ @puppeteer.public_send(method_name, *args, &block)
79
+ else
80
+ @puppeteer.public_send(method_name, *args, **kwargs, &block)
81
+ end
82
+ end
80
83
  end
81
84
  end
82
85
  end
@@ -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.19.0'
35
+ spec.add_development_dependency 'rubocop', '~> 1.21.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.36.0
4
+ version: 0.37.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-08-17 00:00:00.000000000 Z
11
+ date: 2021-09-29 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.19.0
173
+ version: 1.21.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.19.0
180
+ version: 1.21.0
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: rubocop-rspec
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -274,6 +274,7 @@ files:
274
274
  - lib/puppeteer/element_handle.rb
275
275
  - lib/puppeteer/element_handle/bounding_box.rb
276
276
  - lib/puppeteer/element_handle/box_model.rb
277
+ - lib/puppeteer/element_handle/offset.rb
277
278
  - lib/puppeteer/element_handle/point.rb
278
279
  - lib/puppeteer/emulation_manager.rb
279
280
  - lib/puppeteer/env.rb
@@ -315,6 +316,7 @@ files:
315
316
  - lib/puppeteer/request.rb
316
317
  - lib/puppeteer/response.rb
317
318
  - lib/puppeteer/target.rb
319
+ - lib/puppeteer/timeout_helper.rb
318
320
  - lib/puppeteer/timeout_settings.rb
319
321
  - lib/puppeteer/touch_screen.rb
320
322
  - lib/puppeteer/tracing.rb