puppeteer-ruby 0.35.1 → 0.37.2

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -45
  3. data/README.md +69 -0
  4. data/docs/api_coverage.md +55 -45
  5. data/lib/puppeteer/browser.rb +2 -8
  6. data/lib/puppeteer/browser_context.rb +1 -0
  7. data/lib/puppeteer/browser_runner.rb +1 -1
  8. data/lib/puppeteer/concurrent_ruby_utils.rb +2 -2
  9. data/lib/puppeteer/coverage.rb +11 -2
  10. data/lib/puppeteer/define_async_method.rb +1 -1
  11. data/lib/puppeteer/devices.rb +132 -0
  12. data/lib/puppeteer/dom_world.rb +10 -9
  13. data/lib/puppeteer/element_handle/offset.rb +28 -0
  14. data/lib/puppeteer/element_handle/point.rb +11 -0
  15. data/lib/puppeteer/element_handle.rb +68 -7
  16. data/lib/puppeteer/frame.rb +4 -3
  17. data/lib/puppeteer/frame_manager.rb +2 -2
  18. data/lib/puppeteer/{request.rb → http_request.rb} +150 -21
  19. data/lib/puppeteer/{response.rb → http_response.rb} +2 -2
  20. data/lib/puppeteer/js_coverage.rb +28 -7
  21. data/lib/puppeteer/launcher/launch_options.rb +3 -3
  22. data/lib/puppeteer/lifecycle_watcher.rb +2 -2
  23. data/lib/puppeteer/mouse.rb +54 -1
  24. data/lib/puppeteer/network_condition.rb +12 -0
  25. data/lib/puppeteer/network_conditions.rb +24 -0
  26. data/lib/puppeteer/network_manager.rb +64 -18
  27. data/lib/puppeteer/page/metrics.rb +49 -0
  28. data/lib/puppeteer/page/screenshot_options.rb +3 -1
  29. data/lib/puppeteer/page.rb +166 -134
  30. data/lib/puppeteer/puppeteer.rb +5 -0
  31. data/lib/puppeteer/timeout_helper.rb +22 -0
  32. data/lib/puppeteer/tracing.rb +6 -1
  33. data/lib/puppeteer/version.rb +1 -1
  34. data/lib/puppeteer/wait_task.rb +1 -1
  35. data/lib/puppeteer/web_socket.rb +1 -0
  36. data/lib/puppeteer.rb +17 -14
  37. data/puppeteer-ruby.gemspec +1 -1
  38. metadata +11 -6
@@ -1,3 +1,5 @@
1
+ require_relative './device'
2
+
1
3
  Puppeteer::DEVICES = Hash[
2
4
  [
3
5
  {
@@ -156,6 +158,84 @@ Puppeteer::DEVICES = Hash[
156
158
  isLandscape: true,
157
159
  },
158
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
+ },
159
239
  {
160
240
  name: 'iPad',
161
241
  userAgent:
@@ -1001,6 +1081,58 @@ Puppeteer::DEVICES = Hash[
1001
1081
  isLandscape: true,
1002
1082
  },
1003
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
+ },
1004
1136
  ].map do |json|
1005
1137
  [
1006
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,14 +155,56 @@ 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
 
148
165
  define_async_method :async_click
149
166
 
167
+ class DragInterceptionNotEnabledError < StandardError
168
+ def initialize
169
+ super('Drag Interception is not enabled!')
170
+ end
171
+ end
172
+
173
+ def drag(x:, y:)
174
+ unless @page.drag_interception_enabled?
175
+ raise DragInterceptionNotEnabledError.new
176
+ end
177
+ scroll_into_view_if_needed
178
+ start = clickable_point
179
+ @page.mouse.drag(start, Point.new(x: x, y: y))
180
+ end
181
+
182
+ def drag_enter(data)
183
+ scroll_into_view_if_needed
184
+ target = clickable_point
185
+ @page.mouse.drag_enter(target, data)
186
+ end
187
+
188
+ def drag_over(data)
189
+ scroll_into_view_if_needed
190
+ target = clickable_point
191
+ @page.mouse.drag_over(target, data)
192
+ end
193
+
194
+ def drop(data)
195
+ scroll_into_view_if_needed
196
+ target = clickable_point
197
+ @page.mouse.drop(target, data)
198
+ end
199
+
200
+ # @param target [ElementHandle]
201
+ def drag_and_drop(target, delay: nil)
202
+ scroll_into_view_if_needed
203
+ start_point = clickable_point
204
+ target_point = target.clickable_point
205
+ @page.mouse.drag_and_drop(start_point, target_point, delay: delay)
206
+ end
207
+
150
208
  # @return [Array<String>]
151
209
  def select(*values)
152
210
  if nonstring = values.find { |value| !value.is_a?(String) }
@@ -395,10 +453,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
395
453
  define_async_method :async_Sx
396
454
 
397
455
  # in JS, #isIntersectingViewport.
456
+ # @param threshold [Float|nil]
398
457
  # @return [Boolean]
399
- def intersecting_viewport?
458
+ def intersecting_viewport?(threshold: nil)
459
+ option_threshold = threshold || 0
400
460
  js = <<~JAVASCRIPT
401
- async element => {
461
+ async (element, threshold) => {
402
462
  const visibleRatio = await new Promise(resolve => {
403
463
  const observer = new IntersectionObserver(entries => {
404
464
  resolve(entries[0].intersectionRatio);
@@ -406,11 +466,12 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
406
466
  });
407
467
  observer.observe(element);
408
468
  });
409
- return visibleRatio > 0;
469
+ if (threshold === 1) return visibleRatio === 1;
470
+ else return visibleRatio > threshold;
410
471
  }
411
472
  JAVASCRIPT
412
473
 
413
- evaluate(js)
474
+ evaluate(js, option_threshold)
414
475
  end
415
476
 
416
477
  # @param quad [Array<Point>]
@@ -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::Response]
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
- 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?]
@@ -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::Response]
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::Response]
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::Request
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::Response]
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
- unless @allow_interception
124
- raise InterceptionNotEnabledError.new
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
- post_data: post_data,
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
- unless @allow_interception
170
- raise InterceptionNotEnabledError.new
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
- if @interception_handled
173
- raise AlreadyHandledError.new
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
- unless @allow_interception
228
- raise InterceptionNotEnabledError.new
347
+ assert_interception_allowed
348
+ assert_interception_not_handled
349
+
350
+ if priority.nil?
351
+ abort_impl(error_reason)
229
352
  end
230
- if @interception_handled
231
- raise AlreadyHandledError.new
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::Response
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::Request]
32
+ # @param request [Puppeteer::HTTPRequest]
33
33
  # @param response_payload [Hash]
34
34
  def initialize(client, request, response_payload)
35
35
  @client = client