puppeteer-ruby 0.36.0 → 0.37.3
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 +72 -49
- data/README.md +70 -1
- data/docs/api_coverage.md +39 -34
- data/lib/puppeteer/browser.rb +2 -8
- data/lib/puppeteer/browser_context.rb +1 -0
- data/lib/puppeteer/browser_runner.rb +1 -1
- data/lib/puppeteer/coverage.rb +11 -2
- data/lib/puppeteer/devices.rb +130 -0
- data/lib/puppeteer/dom_world.rb +10 -9
- data/lib/puppeteer/element_handle/offset.rb +28 -0
- data/lib/puppeteer/element_handle/point.rb +11 -0
- data/lib/puppeteer/element_handle.rb +27 -7
- data/lib/puppeteer/executable_path_finder.rb +28 -0
- data/lib/puppeteer/frame.rb +4 -3
- data/lib/puppeteer/frame_manager.rb +2 -2
- data/lib/puppeteer/{request.rb → http_request.rb} +150 -21
- data/lib/puppeteer/{response.rb → http_response.rb} +2 -2
- data/lib/puppeteer/js_coverage.rb +28 -7
- data/lib/puppeteer/launcher/chrome.rb +20 -3
- data/lib/puppeteer/launcher/firefox.rb +10 -3
- data/lib/puppeteer/launcher/launch_options.rb +3 -3
- data/lib/puppeteer/lifecycle_watcher.rb +2 -2
- data/lib/puppeteer/network_manager.rb +17 -7
- data/lib/puppeteer/page/screenshot_options.rb +3 -1
- data/lib/puppeteer/page.rb +30 -42
- data/lib/puppeteer/timeout_helper.rb +22 -0
- data/lib/puppeteer/tracing.rb +6 -1
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +1 -1
- data/lib/puppeteer/web_socket.rb +1 -0
- data/lib/puppeteer.rb +17 -13
- data/puppeteer-ruby.gemspec +1 -1
- metadata +9 -6
data/lib/puppeteer/devices.rb
CHANGED
@@ -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],
|
data/lib/puppeteer/dom_world.rb
CHANGED
@@ -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 (
|
272
|
-
|
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
|
-
|
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
|
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>]
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Puppeteer::ExecutablePathFinder
|
2
|
+
# @param executable_names [Array<String>] executable file names to find.
|
3
|
+
def initialize(*executable_names)
|
4
|
+
@executable_names = executable_names
|
5
|
+
end
|
6
|
+
|
7
|
+
def find_executables_in_path
|
8
|
+
Enumerator.new do |result|
|
9
|
+
@executable_names.each do |name|
|
10
|
+
# Find the first existing path.
|
11
|
+
paths.each do |path|
|
12
|
+
candidate = File.join(path, name)
|
13
|
+
next unless File.exist?(candidate)
|
14
|
+
result << candidate
|
15
|
+
break
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_first
|
22
|
+
find_executables_in_path.first
|
23
|
+
end
|
24
|
+
|
25
|
+
private def paths
|
26
|
+
ENV['PATH'].split(File::PATH_SEPARATOR)
|
27
|
+
end
|
28
|
+
end
|
data/lib/puppeteer/frame.rb
CHANGED
@@ -28,7 +28,7 @@ class Puppeteer::Frame
|
|
28
28
|
# @param rederer [String]
|
29
29
|
# @param timeout [number|nil]
|
30
30
|
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
31
|
-
# @return [Puppeteer::
|
31
|
+
# @return [Puppeteer::HTTPResponse]
|
32
32
|
def goto(url, referer: nil, timeout: nil, wait_until: nil)
|
33
33
|
@frame_manager.navigate_frame(self, url, referer: referer, timeout: timeout, wait_until: wait_until)
|
34
34
|
end
|
@@ -157,8 +157,9 @@ class Puppeteer::Frame
|
|
157
157
|
# @param path [String?]
|
158
158
|
# @param content [String?]
|
159
159
|
# @param type [String?]
|
160
|
-
|
161
|
-
|
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?]
|
@@ -82,7 +82,7 @@ class Puppeteer::FrameManager
|
|
82
82
|
# @param frame [Puppeteer::Frame]
|
83
83
|
# @param url [String]
|
84
84
|
# @param {!{referer?: string, timeout?: number, waitUntil?: string|!Array<string>}=} options
|
85
|
-
# @return [Puppeteer::
|
85
|
+
# @return [Puppeteer::HTTPResponse]
|
86
86
|
def navigate_frame(frame, url, referer: nil, timeout: nil, wait_until: nil)
|
87
87
|
assert_no_legacy_navigation_options(wait_until: wait_until)
|
88
88
|
|
@@ -132,7 +132,7 @@ class Puppeteer::FrameManager
|
|
132
132
|
|
133
133
|
# @param timeout [number|nil]
|
134
134
|
# @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
|
135
|
-
# @return [Puppeteer::
|
135
|
+
# @return [Puppeteer::HTTPResponse]
|
136
136
|
def wait_for_frame_navigation(frame, timeout: nil, wait_until: nil)
|
137
137
|
assert_no_legacy_navigation_options(wait_until: wait_until)
|
138
138
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Puppeteer::
|
1
|
+
class Puppeteer::HTTPRequest
|
2
2
|
include Puppeteer::DebugPrint
|
3
3
|
include Puppeteer::IfPresent
|
4
4
|
|
@@ -16,7 +16,7 @@ class Puppeteer::Request
|
|
16
16
|
@request.instance_variable_get(:@interception_id)
|
17
17
|
end
|
18
18
|
|
19
|
-
# @param response [Puppeteer::
|
19
|
+
# @param response [Puppeteer::HTTPResponse]
|
20
20
|
def response=(response)
|
21
21
|
@request.instance_variable_set(:@response, response)
|
22
22
|
end
|
@@ -56,6 +56,12 @@ class Puppeteer::Request
|
|
56
56
|
@post_data = event['request']['postData']
|
57
57
|
@frame = frame
|
58
58
|
@redirect_chain = redirect_chain
|
59
|
+
@continue_request_overrides = {}
|
60
|
+
@current_strategy = 'none'
|
61
|
+
@current_priority = nil
|
62
|
+
@intercept_actions = []
|
63
|
+
@initiator = event['initiator']
|
64
|
+
|
59
65
|
@headers = {}
|
60
66
|
event['request']['headers'].each do |key, value|
|
61
67
|
@headers[key.downcase] = value
|
@@ -68,6 +74,76 @@ class Puppeteer::Request
|
|
68
74
|
attr_reader :internal
|
69
75
|
attr_reader :url, :resource_type, :method, :post_data, :headers, :response, :frame
|
70
76
|
|
77
|
+
private def assert_interception_allowed
|
78
|
+
unless @allow_interception
|
79
|
+
raise InterceptionNotEnabledError.new
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private def assert_interception_not_handled
|
84
|
+
if @interception_handled
|
85
|
+
raise AlreadyHandledError.new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @returns the `ContinueRequestOverrides` that will be used
|
90
|
+
# if the interception is allowed to continue (ie, `abort()` and
|
91
|
+
# `respond()` aren't called).
|
92
|
+
def continue_request_overrides
|
93
|
+
assert_interception_allowed
|
94
|
+
@continue_request_overrides
|
95
|
+
end
|
96
|
+
|
97
|
+
# @returns The `ResponseForRequest` that gets used if the
|
98
|
+
# interception is allowed to respond (ie, `abort()` is not called).
|
99
|
+
def response_for_request
|
100
|
+
assert_interception_allowed
|
101
|
+
@response_for_request
|
102
|
+
end
|
103
|
+
|
104
|
+
# @returns the most recent reason for aborting the request
|
105
|
+
def abort_error_reason
|
106
|
+
assert_interception_allowed
|
107
|
+
@abort_error_reason
|
108
|
+
end
|
109
|
+
|
110
|
+
# @returns An array of the current intercept resolution strategy and priority
|
111
|
+
# `[strategy,priority]`. Strategy is one of: `abort`, `respond`, `continue`,
|
112
|
+
# `disabled`, `none`, or `already-handled`.
|
113
|
+
def intercept_resolution
|
114
|
+
if !@allow_interception
|
115
|
+
['disabled']
|
116
|
+
elsif @interception_handled
|
117
|
+
['already-handled']
|
118
|
+
else
|
119
|
+
[@current_strategy, @current_priority]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Adds an async request handler to the processing queue.
|
124
|
+
# Deferred handlers are not guaranteed to execute in any particular order,
|
125
|
+
# but they are guarnateed to resolve before the request interception
|
126
|
+
# is finalized.
|
127
|
+
#
|
128
|
+
# @param pending_handler [Proc]
|
129
|
+
def enqueue_intercept_action(pending_handler)
|
130
|
+
@intercept_actions << pending_handler
|
131
|
+
end
|
132
|
+
|
133
|
+
# Awaits pending interception handlers and then decides how to fulfill
|
134
|
+
# the request interception.
|
135
|
+
def finalize_interceptions
|
136
|
+
@intercept_actions.each(&:call)
|
137
|
+
case @intercept_resolution
|
138
|
+
when :abort
|
139
|
+
abort_impl(**@abort_error_reason)
|
140
|
+
when :respond
|
141
|
+
respond_impl(**@response_for_request)
|
142
|
+
when :continue
|
143
|
+
continue_impl(@continue_request_overrides)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
71
147
|
def navigation_request?
|
72
148
|
@is_navigation_request
|
73
149
|
end
|
@@ -116,24 +192,43 @@ class Puppeteer::Request
|
|
116
192
|
# end
|
117
193
|
#
|
118
194
|
# @param error_code [String|Symbol]
|
119
|
-
def continue(url: nil, method: nil, post_data: nil, headers: nil)
|
195
|
+
def continue(url: nil, method: nil, post_data: nil, headers: nil, priority: nil)
|
120
196
|
# Request interception is not supported for data: urls.
|
121
197
|
return if @url.start_with?('data:')
|
122
198
|
|
123
|
-
|
124
|
-
|
125
|
-
end
|
126
|
-
if @interception_handled
|
127
|
-
raise AlreadyHandledError.new
|
128
|
-
end
|
129
|
-
@interception_handled = true
|
199
|
+
assert_interception_allowed
|
200
|
+
assert_interception_not_handled
|
130
201
|
|
131
202
|
overrides = {
|
132
203
|
url: url,
|
133
204
|
method: method,
|
134
|
-
|
205
|
+
postData: post_data,
|
135
206
|
headers: headers_to_array(headers),
|
136
207
|
}.compact
|
208
|
+
|
209
|
+
if priority.nil?
|
210
|
+
continue_impl(overrides)
|
211
|
+
return
|
212
|
+
end
|
213
|
+
|
214
|
+
@continue_request_overrides = overrides
|
215
|
+
if @current_priority.nil? || priority > @current_priority
|
216
|
+
@current_strategy = :continue
|
217
|
+
@current_priority = priority
|
218
|
+
return
|
219
|
+
end
|
220
|
+
|
221
|
+
if priority == @current_priority
|
222
|
+
if @current_strategy == :abort || @current_strategy == :respond
|
223
|
+
return
|
224
|
+
end
|
225
|
+
@current_strategy = :continue
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
private def continue_impl(overrides)
|
230
|
+
@interception_handled = true
|
231
|
+
|
137
232
|
begin
|
138
233
|
@client.send_message('Fetch.continueRequest',
|
139
234
|
requestId: @interception_id,
|
@@ -162,16 +257,40 @@ class Puppeteer::Request
|
|
162
257
|
# @param headers [Hash<String, String>]
|
163
258
|
# @param content_type [String]
|
164
259
|
# @param body [String]
|
165
|
-
def respond(status: nil, headers: nil, content_type: nil, body: nil)
|
260
|
+
def respond(status: nil, headers: nil, content_type: nil, body: nil, priority: nil)
|
166
261
|
# Mocking responses for dataURL requests is not currently supported.
|
167
262
|
return if @url.start_with?('data:')
|
168
263
|
|
169
|
-
|
170
|
-
|
264
|
+
assert_interception_allowed
|
265
|
+
assert_interception_not_handled
|
266
|
+
|
267
|
+
if priority.nil?
|
268
|
+
respond_impl(status: status, headers: headers, content_type: content_type, body: body)
|
269
|
+
return
|
171
270
|
end
|
172
|
-
|
173
|
-
|
271
|
+
|
272
|
+
@response_for_request = {
|
273
|
+
status: status,
|
274
|
+
headers: headers,
|
275
|
+
content_type: content_type,
|
276
|
+
body: body,
|
277
|
+
}
|
278
|
+
|
279
|
+
if @current_priority.nil? || priority > @current_priority
|
280
|
+
@current_strategy = :respond
|
281
|
+
@current_priority = priority
|
282
|
+
return
|
283
|
+
end
|
284
|
+
|
285
|
+
if priority == @current_priority
|
286
|
+
if @current_strategy == :abort
|
287
|
+
return
|
288
|
+
end
|
289
|
+
@current_strategy = :respond
|
174
290
|
end
|
291
|
+
end
|
292
|
+
|
293
|
+
private def respond_impl(status: nil, headers: nil, content_type: nil, body: nil)
|
175
294
|
@interception_handled = true
|
176
295
|
|
177
296
|
mock_response_headers = {}
|
@@ -191,6 +310,7 @@ class Puppeteer::Request
|
|
191
310
|
responseHeaders: headers_to_array(mock_response_headers),
|
192
311
|
body: if_present(body) { |mock_body| Base64.strict_encode64(mock_body) },
|
193
312
|
}.compact
|
313
|
+
|
194
314
|
begin
|
195
315
|
@client.send_message('Fetch.fulfillRequest',
|
196
316
|
requestId: @interception_id,
|
@@ -216,7 +336,7 @@ class Puppeteer::Request
|
|
216
336
|
# end
|
217
337
|
#
|
218
338
|
# @param error_code [String|Symbol]
|
219
|
-
def abort(error_code: :failed)
|
339
|
+
def abort(error_code: :failed, priority: nil)
|
220
340
|
# Request interception is not supported for data: urls.
|
221
341
|
return if @url.start_with?('data:')
|
222
342
|
|
@@ -224,12 +344,21 @@ class Puppeteer::Request
|
|
224
344
|
unless error_reason
|
225
345
|
raise ArgumentError.new("Unknown error code: #{error_code}")
|
226
346
|
end
|
227
|
-
|
228
|
-
|
347
|
+
assert_interception_allowed
|
348
|
+
assert_interception_not_handled
|
349
|
+
|
350
|
+
if priority.nil?
|
351
|
+
abort_impl(error_reason)
|
229
352
|
end
|
230
|
-
|
231
|
-
|
353
|
+
@abort_error_reason = error_reason
|
354
|
+
|
355
|
+
if @current_priority.nil? || priority > @current_priority
|
356
|
+
@current_strategy = :abort
|
357
|
+
@current_priority = priority
|
232
358
|
end
|
359
|
+
end
|
360
|
+
|
361
|
+
private def abort_impl(error_reason)
|
233
362
|
@interception_handled = true
|
234
363
|
|
235
364
|
begin
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
|
-
class Puppeteer::
|
3
|
+
class Puppeteer::HTTPResponse
|
4
4
|
include Puppeteer::IfPresent
|
5
5
|
|
6
6
|
class Redirected < StandardError
|
@@ -29,7 +29,7 @@ class Puppeteer::Response
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# @param client [Puppeteer::CDPSession]
|
32
|
-
# @param request [Puppeteer::
|
32
|
+
# @param request [Puppeteer::HTTPRequest]
|
33
33
|
# @param response_payload [Hash]
|
34
34
|
def initialize(client, request, response_payload)
|
35
35
|
@client = client
|