puppeteer-ruby 0.50.1 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/docs/api_coverage.md +38 -6
  3. data/lib/puppeteer/browser.rb +82 -0
  4. data/lib/puppeteer/browser_connector.rb +2 -0
  5. data/lib/puppeteer/browser_context.rb +51 -3
  6. data/lib/puppeteer/chrome_target_manager.rb +53 -6
  7. data/lib/puppeteer/debug_print.rb +9 -13
  8. data/lib/puppeteer/element_handle.rb +16 -0
  9. data/lib/puppeteer/events.rb +3 -0
  10. data/lib/puppeteer/execution_context.rb +37 -15
  11. data/lib/puppeteer/extension.rb +70 -0
  12. data/lib/puppeteer/frame.rb +8 -1
  13. data/lib/puppeteer/frame_manager.rb +62 -2
  14. data/lib/puppeteer/http_request.rb +15 -1
  15. data/lib/puppeteer/http_response.rb +6 -1
  16. data/lib/puppeteer/isolated_world.rb +22 -1
  17. data/lib/puppeteer/issue.rb +16 -0
  18. data/lib/puppeteer/js_coverage.rb +14 -1
  19. data/lib/puppeteer/launcher/browser_options.rb +6 -1
  20. data/lib/puppeteer/launcher/chrome.rb +17 -2
  21. data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
  22. data/lib/puppeteer/locators.rb +56 -37
  23. data/lib/puppeteer/network_manager.rb +16 -16
  24. data/lib/puppeteer/page.rb +90 -7
  25. data/lib/puppeteer/puppeteer.rb +15 -0
  26. data/lib/puppeteer/remote_object.rb +2 -1
  27. data/lib/puppeteer/target.rb +17 -0
  28. data/lib/puppeteer/version.rb +2 -1
  29. data/lib/puppeteer.rb +2 -0
  30. data/puppeteer-ruby.gemspec +1 -1
  31. data/sig/_supplementary.rbs +7 -0
  32. data/sig/puppeteer/browser.rbs +34 -2
  33. data/sig/puppeteer/element_handle.rbs +5 -0
  34. data/sig/puppeteer/execution_context.rbs +4 -0
  35. data/sig/puppeteer/extension.rbs +37 -0
  36. data/sig/puppeteer/frame.rbs +5 -0
  37. data/sig/puppeteer/http_request.rbs +2 -0
  38. data/sig/puppeteer/issue.rbs +13 -0
  39. data/sig/puppeteer/locators.rbs +5 -2
  40. data/sig/puppeteer/page.rbs +20 -0
  41. data/sig/puppeteer/puppeteer.rbs +7 -2
  42. data/sig/puppeteer/remote_object.rbs +2 -0
  43. metadata +8 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e905c9995f718c953c11c9c199e7c945c0d413750becae8bdbc13cc4c0c1e5f
4
- data.tar.gz: 9fec2af23369834b277ec50ef655b4a086b9df67698465f133b1af2ed059b11e
3
+ metadata.gz: dc3601ac8a64edb1a6054663944d5244eb0fcbc774b2d2f73529eef194031689
4
+ data.tar.gz: c364f8b73fc4dc6a591baca37e2e116d3147c5572f42dd026de77e36b0842851
5
5
  SHA512:
6
- metadata.gz: e636edbdf0dcb3731e7b688936f4c04bf7b9f21668ce6ed712d993e2dac41b748f4e49e89b9b43ddfcdd2b37138275bf1b95e5191d7d09cd1d3df664f6838358
7
- data.tar.gz: 1e7478c8a4757743d6e31321af550b0900b04a46785b51ac2bb0203060d1456e8917722ad6631ae86a546b8fccb7f1947b185b7c9007e7720773a1772146557a
6
+ metadata.gz: a4de65d9249823c8532150d9cbeace0670d5e0c35de747193bee4923edb751b3dcc7a46aa6ec5f58393e20fabe9933b12eaca0ac47ead1d91c5793fec8ca0e9b
7
+ data.tar.gz: 4d5d39fe7dac8d74a3f90478f3bae1bd7e9dcb14018518551ca4ca7d59f48ed1e9f9e7205c33956d82591b2985b4fecd5f1eb4f0af8efc03f4a1b3f22a0b2281
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
- - Puppeteer version: v24.35.0
3
- - puppeteer-ruby version: 0.50.1
2
+ - Puppeteer version: v24.42.0
3
+ - puppeteer-ruby version: 0.51.0
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -25,8 +25,9 @@
25
25
  * ~~deleteCookie~~
26
26
  * ~~deleteMatchingCookies~~
27
27
  * disconnect
28
+ * extensions
28
29
  * ~~getWindowBounds~~
29
- * ~~installExtension~~
30
+ * installExtension => `#install_extension`
30
31
  * isConnected => `#connected?`
31
32
  * newPage => `#new_page`
32
33
  * pages
@@ -34,10 +35,11 @@
34
35
  * ~~removeScreen~~
35
36
  * ~~screens~~
36
37
  * ~~setCookie~~
38
+ * setPermission => `#set_permission`
37
39
  * ~~setWindowBounds~~
38
40
  * target
39
41
  * targets
40
- * ~~uninstallExtension~~
42
+ * uninstallExtension => `#uninstall_extension`
41
43
  * userAgent => `#user_agent`
42
44
  * version
43
45
  * waitForTarget => `#wait_for_target`
@@ -55,6 +57,7 @@
55
57
  * overridePermissions => `#override_permissions`
56
58
  * pages
57
59
  * setCookie => `#set_cookie`
60
+ * setPermission => `#set_permission`
58
61
  * targets
59
62
  * waitForTarget => `#wait_for_target`
60
63
 
@@ -124,7 +127,7 @@
124
127
  * $$eval => `#eval_on_selector_all`
125
128
  * $eval => `#eval_on_selector`
126
129
  * asLocator => `#as_locator`
127
- * ~~autofill~~
130
+ * autofill
128
131
  * ~~backendNodeId~~
129
132
  * boundingBox => `#bounding_box`
130
133
  * boxModel => `#box_model`
@@ -163,6 +166,12 @@
163
166
  * ~~once~~
164
167
  * ~~removeAllListeners~~
165
168
 
169
+ ## Extension
170
+
171
+ * pages
172
+ * triggerAction => `#trigger_action`
173
+ * workers
174
+
166
175
  ## ~~ExtensionTransport~~
167
176
 
168
177
  * ~~close~~
@@ -188,6 +197,7 @@
188
197
  * content
189
198
  * evaluate
190
199
  * evaluateHandle => `#evaluate_handle`
200
+ * extensionRealms => `#extension_realms`
191
201
  * focus
192
202
  * frameElement => `#frame_element`
193
203
  * goto
@@ -321,6 +331,7 @@
321
331
  * bringToFront => `#bring_to_front`
322
332
  * browser
323
333
  * browserContext => `#browser_context`
334
+ * captureHeapSnapshot => `#capture_heap_snapshot`
324
335
  * click
325
336
  * close
326
337
  * content
@@ -341,6 +352,7 @@
341
352
  * evaluateHandle => `#evaluate_handle`
342
353
  * evaluateOnNewDocument => `#evaluate_on_new_document`
343
354
  * exposeFunction => `#expose_function`
355
+ * extensionRealms => `#extension_realms`
344
356
  * focus
345
357
  * frames
346
358
  * ~~getDefaultNavigationTimeout~~
@@ -348,6 +360,7 @@
348
360
  * goBack => `#go_back`
349
361
  * goForward => `#go_forward`
350
362
  * goto
363
+ * ~~hasDevTools~~
351
364
  * hover
352
365
  * isClosed => `#closed?`
353
366
  * isDragInterceptionEnabled => `#drag_interception_enabled?`
@@ -384,6 +397,7 @@
384
397
  * tap
385
398
  * target
386
399
  * title
400
+ * triggerExtensionAction => `#trigger_extension_action`
387
401
  * type => `#type_text`
388
402
  * url
389
403
  * viewport
@@ -402,6 +416,13 @@
402
416
  ## ~~ProtocolError~~
403
417
 
404
418
 
419
+ ## ~~Realm~~
420
+
421
+ * ~~evaluate~~
422
+ * ~~evaluateHandle~~
423
+ * ~~extension~~
424
+ * ~~waitForFunction~~
425
+
405
426
  ## ~~ScreenRecorder~~
406
427
 
407
428
  * ~~stop~~
@@ -417,7 +438,7 @@
417
438
 
418
439
  ## Target
419
440
 
420
- * ~~asPage~~
441
+ * asPage => `#as_page`
421
442
  * browser
422
443
  * browserContext => `#browser_context`
423
444
  * createCDPSession => `#create_cdp_session`
@@ -448,6 +469,17 @@
448
469
  ## ~~UnsupportedOperation~~
449
470
 
450
471
 
472
+ ## ~~WebMCP~~
473
+
474
+ * ~~tools~~
475
+
476
+ ## ~~WebMCPTool~~
477
+
478
+ * ~~execute~~
479
+
480
+ ## ~~WebMCPToolCall~~
481
+
482
+
451
483
  ## WebWorker
452
484
 
453
485
  * close
@@ -14,6 +14,8 @@ class Puppeteer::Browser
14
14
  # @rbs ignore_https_errors: bool -- Ignore HTTPS errors
15
15
  # @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
16
16
  # @rbs network_enabled: bool -- Whether network events are enabled
17
+ # @rbs issues_enabled: bool -- Whether issues events are enabled
18
+ # @rbs block_list: Array[String]? -- URL block list patterns
17
19
  # @rbs process: Puppeteer::BrowserRunner::BrowserProcess? -- Browser process handle
18
20
  # @rbs close_callback: Proc -- Close callback
19
21
  # @rbs target_filter_callback: Proc? -- Target filter callback
@@ -25,6 +27,8 @@ class Puppeteer::Browser
25
27
  ignore_https_errors:,
26
28
  default_viewport:,
27
29
  network_enabled: true,
30
+ issues_enabled: true,
31
+ block_list: nil,
28
32
  process:,
29
33
  close_callback:,
30
34
  target_filter_callback:,
@@ -36,6 +40,8 @@ class Puppeteer::Browser
36
40
  ignore_https_errors: ignore_https_errors,
37
41
  default_viewport: default_viewport,
38
42
  network_enabled: network_enabled,
43
+ issues_enabled: issues_enabled,
44
+ block_list: block_list,
39
45
  process: process,
40
46
  close_callback: close_callback,
41
47
  target_filter_callback: target_filter_callback,
@@ -51,6 +57,8 @@ class Puppeteer::Browser
51
57
  # @rbs ignore_https_errors: bool -- Ignore HTTPS errors
52
58
  # @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
53
59
  # @rbs network_enabled: bool -- Whether network events are enabled
60
+ # @rbs issues_enabled: bool -- Whether issues events are enabled
61
+ # @rbs block_list: Array[String]? -- URL block list patterns
54
62
  # @rbs process: Puppeteer::BrowserRunner::BrowserProcess? -- Browser process handle
55
63
  # @rbs close_callback: Proc -- Close callback
56
64
  # @rbs target_filter_callback: Proc? -- Target filter callback
@@ -62,6 +70,8 @@ class Puppeteer::Browser
62
70
  ignore_https_errors:,
63
71
  default_viewport:,
64
72
  network_enabled: true,
73
+ issues_enabled: true,
74
+ block_list: nil,
65
75
  process:,
66
76
  close_callback:,
67
77
  target_filter_callback:,
@@ -73,6 +83,8 @@ class Puppeteer::Browser
73
83
  @ignore_https_errors = ignore_https_errors
74
84
  @default_viewport = default_viewport
75
85
  @network_enabled = network_enabled
86
+ @issues_enabled = issues_enabled
87
+ @block_list = block_list
76
88
  @process = process
77
89
  @connection = connection
78
90
  @close_callback = close_callback
@@ -89,7 +101,9 @@ class Puppeteer::Browser
89
101
  connection: connection,
90
102
  target_factory: method(:create_target),
91
103
  target_filter_callback: @target_filter_callback,
104
+ block_list: block_list,
92
105
  )
106
+ @extensions = {}
93
107
  end
94
108
 
95
109
  private def default_target_filter_callback(target_info)
@@ -163,6 +177,10 @@ class Puppeteer::Browser
163
177
  @target_manager
164
178
  end
165
179
 
180
+ private def connection
181
+ @connection
182
+ end
183
+
166
184
  # @rbs return: Puppeteer::BrowserContext -- New incognito browser context
167
185
  def create_incognito_browser_context
168
186
  result = @connection.send_message('Target.createBrowserContext')
@@ -196,6 +214,13 @@ class Puppeteer::Browser
196
214
  @default_context
197
215
  end
198
216
 
217
+ # @rbs origin: String -- Origin URL, or '*' for all origins
218
+ # @rbs permissions: Array[Hash[Symbol | String, untyped]] -- Permission descriptors and states
219
+ # @rbs return: void -- No return value
220
+ def set_permission(origin, *permissions)
221
+ @default_context.set_permission(origin, *permissions)
222
+ end
223
+
199
224
  # @rbs context_id: String? -- Browser context ID
200
225
  # @rbs return: void -- No return value
201
226
  def dispose_context(context_id)
@@ -369,6 +394,63 @@ class Puppeteer::Browser
369
394
  Version.fetch(@connection).user_agent
370
395
  end
371
396
 
397
+ # @rbs page_target_id: String -- Page target id
398
+ # @rbs return: String? -- DevTools target id for page
399
+ def _has_devtools_target(page_target_id)
400
+ result = @connection.send_message('Target.getDevToolsTarget', targetId: page_target_id)
401
+ result['targetId']
402
+ end
403
+
404
+ # @rbs path: String -- Extension path
405
+ # @rbs return: String -- Installed extension id
406
+ def install_extension(path)
407
+ result = @connection.send_message('Extensions.loadUnpacked', path: path)
408
+ extension_id = result['id']
409
+ @extensions.delete(extension_id)
410
+ extension_id
411
+ end
412
+
413
+ # @rbs extension_id: String -- Extension id
414
+ # @rbs return: void -- No return value
415
+ def uninstall_extension(extension_id)
416
+ @connection.send_message('Extensions.uninstall', id: extension_id)
417
+ @extensions.delete(extension_id)
418
+ end
419
+
420
+ # @rbs return: Hash[String, Puppeteer::Extension] -- Installed extensions
421
+ def extensions
422
+ response = @connection.send_message('Extensions.getExtensions')
423
+ extension_map = {}
424
+ response.fetch('extensions', []).each do |payload|
425
+ extension_id = payload['id']
426
+ existing_extension = @extensions[extension_id]
427
+ extension_map[extension_id] =
428
+ if existing_extension
429
+ existing_extension
430
+ else
431
+ Puppeteer::Extension.new(
432
+ id: extension_id,
433
+ version: payload['version'],
434
+ name: payload['name'],
435
+ path: payload['path'],
436
+ enabled: payload['enabled'],
437
+ browser: self,
438
+ )
439
+ end
440
+ end
441
+ @extensions = extension_map
442
+ end
443
+
444
+ # @rbs return: bool -- Whether issue events are enabled
445
+ def issues_enabled?
446
+ @issues_enabled
447
+ end
448
+
449
+ # @rbs return: Array[String]? -- URL block list patterns
450
+ def block_list
451
+ @block_list
452
+ end
453
+
372
454
  # @rbs return: void -- No return value
373
455
  def close
374
456
  @close_callback.call
@@ -28,6 +28,8 @@ class Puppeteer::BrowserConnector
28
28
  ignore_https_errors: @browser_options.ignore_https_errors?,
29
29
  default_viewport: @browser_options.default_viewport,
30
30
  network_enabled: @browser_options.network_enabled,
31
+ issues_enabled: @browser_options.issues_enabled,
32
+ block_list: @browser_options.block_list,
31
33
  process: nil,
32
34
  close_callback: -> { connection.send_message('Browser.close') },
33
35
  target_filter_callback: @browser_options.target_filter,
@@ -153,6 +153,44 @@ class Puppeteer::BrowserContext
153
153
  }.compact)
154
154
  end
155
155
 
156
+ # @param origin [String] Use '*' to apply to all origins.
157
+ # @param permissions [Array<Hash>] { permission: { name:, userVisibleOnly:, sysex:, panTiltZoom:, allowWithoutSanitization: }, state: 'granted'|'denied'|'prompt' }
158
+ def set_permission(origin, *permissions)
159
+ permissions.each do |permission|
160
+ descriptor = hash_value(permission, 'permission')
161
+ state = hash_value(permission, 'state')
162
+ unless descriptor.is_a?(Hash)
163
+ raise ArgumentError.new('Each permission entry must include a permission descriptor')
164
+ end
165
+ unless state
166
+ raise ArgumentError.new('Each permission entry must include state')
167
+ end
168
+
169
+ name = hash_value(descriptor, 'name')
170
+ unless name
171
+ raise ArgumentError.new('Permission descriptor must include name')
172
+ end
173
+
174
+ protocol_permission = { name: name.to_s }
175
+ {
176
+ userVisibleOnly: %w[userVisibleOnly user_visible_only],
177
+ sysex: %w[sysex],
178
+ panTiltZoom: %w[panTiltZoom pan_tilt_zoom],
179
+ allowWithoutSanitization: %w[allowWithoutSanitization allow_without_sanitization],
180
+ }.each do |protocol_key, keys|
181
+ value = hash_value(descriptor, *keys)
182
+ protocol_permission[protocol_key] = value unless value.nil?
183
+ end
184
+
185
+ @connection.send_message('Browser.setPermission', {
186
+ origin: origin == '*' ? nil : origin,
187
+ browserContextId: @id,
188
+ permission: protocol_permission,
189
+ setting: state.to_s,
190
+ }.compact)
191
+ end
192
+ end
193
+
156
194
  def clear_permission_overrides
157
195
  if @id
158
196
  @connection.send_message('Browser.resetPermissions', browserContextId: @id)
@@ -214,6 +252,9 @@ class Puppeteer::BrowserContext
214
252
  normalized = normalize_cookie_hash(cookie)
215
253
  partition_key = normalized.delete('partitionKey') || normalized.delete('partition_key')
216
254
  normalized['partitionKey'] = convert_partition_key_for_cdp(partition_key) if partition_key
255
+ same_site = normalized.delete('sameSite') || normalized.delete('same_site')
256
+ converted_same_site = convert_same_site_for_cdp(same_site)
257
+ normalized['sameSite'] = converted_same_site if converted_same_site
217
258
  normalized
218
259
  end
219
260
  @connection.send_message('Storage.setCookies', {
@@ -277,9 +318,7 @@ class Puppeteer::BrowserContext
277
318
  end
278
319
 
279
320
  private def normalize_cookie_hash(cookie)
280
- cookie.each_with_object({}) do |(key, value), normalized|
281
- normalized[key.to_s] = value
282
- end
321
+ cookie.transform_keys(&:to_s)
283
322
  end
284
323
 
285
324
  private def hash_value(hash, *keys)
@@ -305,6 +344,15 @@ class Puppeteer::BrowserContext
305
344
  }
306
345
  end
307
346
 
347
+ private def convert_same_site_for_cdp(same_site)
348
+ case same_site
349
+ when 'Strict', 'Lax', 'None'
350
+ same_site
351
+ else
352
+ nil
353
+ end
354
+ end
355
+
308
356
  private def convert_partition_key_from_cdp(partition_key)
309
357
  return nil if partition_key.nil?
310
358
  return partition_key if partition_key.is_a?(String)
@@ -2,7 +2,7 @@ class Puppeteer::ChromeTargetManager
2
2
  include Puppeteer::DebugPrint
3
3
  include Puppeteer::EventCallbackable
4
4
 
5
- def initialize(connection:, target_factory:, target_filter_callback:)
5
+ def initialize(connection:, target_factory:, target_filter_callback:, block_list: nil)
6
6
  @discovered_targets_by_target_id = {}
7
7
  @attached_targets_by_target_id = {}
8
8
  @attached_targets_by_session_id = {}
@@ -13,8 +13,13 @@ class Puppeteer::ChromeTargetManager
13
13
  @connection = connection
14
14
  @target_filter_callback = target_filter_callback
15
15
  @target_factory = target_factory
16
+ @block_list = block_list
17
+ if @block_list && !@block_list.is_a?(Array)
18
+ raise ArgumentError.new('block_list must be an Array of URL patterns')
19
+ end
16
20
  @target_interceptors = {}
17
21
  @initialize_promise = Async::Promise.new
22
+ @initial_attach_done = false
18
23
 
19
24
  @connection_event_listeners = []
20
25
  @connection_event_listeners << @connection.add_event_listener(
@@ -52,7 +57,7 @@ class Puppeteer::ChromeTargetManager
52
57
 
53
58
  private def store_existing_targets_for_init
54
59
  @discovered_targets_by_target_id.each do |target_id, target_info|
55
- if @target_filter_callback.call(target_info) && target_info.type != 'browser'
60
+ if @target_filter_callback.call(target_info) && target_info.type != 'browser' && url_allowed?(target_info.url)
56
61
  @target_ids_for_init << target_id
57
62
  end
58
63
  end
@@ -64,6 +69,7 @@ class Puppeteer::ChromeTargetManager
64
69
  flatten: true,
65
70
  autoAttach: true,
66
71
  })
72
+ @initial_attach_done = true
67
73
  finish_initialization_if_ready
68
74
  @initialize_promise.wait
69
75
  end
@@ -149,11 +155,11 @@ class Puppeteer::ChromeTargetManager
149
155
  target_info = @discovered_targets_by_target_id.delete(target_id)
150
156
  finish_initialization_if_ready(target_id)
151
157
 
152
- if target_info.type == 'service_worker' && @attached_targets_by_target_id.has_key?(target_id)
158
+ if target_info&.type == 'service_worker'
153
159
  # Special case for service workers: report TargetGone event when
154
160
  # the worker is destroyed.
155
161
  target = @attached_targets_by_target_id.delete(target_id)
156
- emit_event(TargetManagerEmittedEvents::TargetGone, target)
162
+ emit_event(TargetManagerEmittedEvents::TargetGone, target) if target
157
163
  end
158
164
  end
159
165
 
@@ -161,11 +167,11 @@ class Puppeteer::ChromeTargetManager
161
167
  target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
162
168
  @discovered_targets_by_target_id[target_info.target_id] = target_info
163
169
 
164
- if @ignored_targets.include?(target_info.target_id) || !@attached_targets_by_target_id.has_key?(target_info.target_id) || !target_info.attached
170
+ if @ignored_targets.include?(target_info.target_id) || !target_info.attached
165
171
  return
166
172
  end
167
173
  original_target = @attached_targets_by_target_id[target_info.target_id]
168
- emit_event(TargetManagerEmittedEvents::TargetChanged, original_target, target_info)
174
+ emit_event(TargetManagerEmittedEvents::TargetChanged, original_target, target_info) if original_target
169
175
  end
170
176
 
171
177
  class SessionNotCreatedError < Puppeteer::Error ; end
@@ -204,6 +210,12 @@ class Puppeteer::ChromeTargetManager
204
210
 
205
211
  return unless @connection.auto_attached?(target_info.target_id)
206
212
 
213
+ if !@initial_attach_done && !url_allowed?(target_info.url)
214
+ finish_initialization_if_ready(target_info.target_id)
215
+ silent_detach.call
216
+ return
217
+ end
218
+
207
219
  # Special case for service workers: being attached to service workers will
208
220
  # prevent them from ever being destroyed. Therefore, we silently detach
209
221
  # from service workers unless the connection was manually created via
@@ -270,6 +282,7 @@ class Puppeteer::ChromeTargetManager
270
282
  flatten: true,
271
283
  autoAttach: true,
272
284
  }))
285
+ maybe_setup_network_conditions(session)
273
286
  Puppeteer::AsyncUtils.await(session.async_send_message('Runtime.runIfWaitingForDebugger'))
274
287
  rescue => err
275
288
  Logger.new($stderr).warn(err)
@@ -292,4 +305,38 @@ class Puppeteer::ChromeTargetManager
292
305
  @attached_targets_by_target_id.delete(target.target_id)
293
306
  emit_event(TargetManagerEmittedEvents::TargetGone, target)
294
307
  end
308
+
309
+ private def url_allowed?(url)
310
+ return true if @block_list.nil? || @block_list.empty?
311
+ return true if url.nil? || url.empty? || url == 'about:blank'
312
+
313
+ @block_list.none? do |pattern|
314
+ File.fnmatch?(pattern, url, File::FNM_EXTGLOB)
315
+ rescue ArgumentError
316
+ false
317
+ end
318
+ end
319
+
320
+ private def maybe_setup_network_conditions(session)
321
+ return if @block_list.nil? || @block_list.empty?
322
+
323
+ matched_network_conditions = @block_list.map do |pattern|
324
+ {
325
+ urlPattern: pattern,
326
+ latency: 0,
327
+ downloadThroughput: -1,
328
+ uploadThroughput: -1,
329
+ }
330
+ end
331
+ Puppeteer::AsyncUtils.await(session.async_send_message('Network.enable'))
332
+ Puppeteer::AsyncUtils.await(session.async_send_message('Network.emulateNetworkConditionsByRule', {
333
+ matchedNetworkConditions: matched_network_conditions,
334
+ offline: true,
335
+ }))
336
+ rescue => err
337
+ message = err.message.to_s.downcase
338
+ return if message.include?('target closed') || message.include?('session closed') || message.include?('not found')
339
+
340
+ raise
341
+ end
295
342
  end
@@ -1,20 +1,16 @@
1
1
  require 'logger'
2
2
 
3
3
  module Puppeteer::DebugPrint
4
- if Puppeteer.env.debug?
5
- def debug_puts(*args, **kwargs)
6
- @__debug_logger ||= Logger.new($stdout)
7
- @__debug_logger.debug(*args, **kwargs)
8
- end
4
+ def debug_puts(*args, **kwargs)
5
+ return unless Puppeteer.env.debug?
9
6
 
10
- def debug_print(*args)
11
- print(*args)
12
- end
13
- else
14
- def debug_puts(*args, **kwargs)
15
- end
7
+ @__debug_logger ||= Logger.new($stdout)
8
+ @__debug_logger.debug(*args, **kwargs)
9
+ end
10
+
11
+ def debug_print(*args)
12
+ return unless Puppeteer.env.debug?
16
13
 
17
- def debug_print(*args)
18
- end
14
+ print(*args)
19
15
  end
20
16
  end
@@ -540,6 +540,22 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
540
540
  end
541
541
  end
542
542
 
543
+ # @rbs credit_card: Hash[Symbol | String, untyped]? -- Credit card autofill payload
544
+ # @rbs address: Hash[Symbol | String, untyped]? -- Address autofill payload
545
+ # @rbs return: void -- No return value
546
+ def autofill(credit_card: nil, address: nil)
547
+ node_info = @remote_object.node_info(@client)
548
+ field_id = node_info.dig('node', 'backendNodeId')
549
+ @client.send_message('Autofill.trigger', {
550
+ fieldId: field_id,
551
+ frameId: @frame.id,
552
+ card: credit_card,
553
+ address: address,
554
+ }.compact)
555
+ end
556
+
557
+ define_async_method :async_autofill
558
+
543
559
  # @rbs block: Proc? -- Optional block for Object#tap usage
544
560
  # @rbs return: Puppeteer::ElementHandle | nil -- Element handle or nil
545
561
  def tap(&block)
@@ -135,6 +135,9 @@ module PageEmittedEvents ; end
135
135
  # Emitted when a frame is navigated to a new URL. Will contain a {@link Frame}.
136
136
  FrameNavigated: 'framenavigated',
137
137
 
138
+ # Emitted when a DevTools issue is reported.
139
+ Issue: 'issue',
140
+
138
141
  # Emitted when the JavaScript
139
142
  # {https://developer.mozilla.org/en-US/docs/Web/Events/load | load} event is dispatched.
140
143
  Load: 'load',
@@ -52,13 +52,18 @@ class Puppeteer::ExecutionContext
52
52
  # @param context_id [String]
53
53
  # @return [Object|JSHandle]
54
54
  def evaluate_with(client:, context_id:)
55
- result = client.send_message('Runtime.evaluate',
56
- expression: expression_with_source_url,
57
- contextId: context_id,
58
- returnByValue: @return_by_value,
59
- awaitPromise: true,
60
- userGesture: true,
61
- )
55
+ result =
56
+ begin
57
+ client.send_message('Runtime.evaluate',
58
+ expression: expression_with_source_url,
59
+ contextId: context_id,
60
+ returnByValue: @return_by_value,
61
+ awaitPromise: true,
62
+ userGesture: true,
63
+ )
64
+ rescue Puppeteer::Connection::ProtocolError => err
65
+ raise @execution_context.send(:rewrite_evaluation_error, err)
66
+ end
62
67
  # }).catch(rewriteError);
63
68
 
64
69
  exception_details = result['exceptionDetails']
@@ -110,14 +115,19 @@ class Puppeteer::ExecutionContext
110
115
  # Original puppeteer implementation take it into consideration.
111
116
  # But we don't support the syntax here.
112
117
 
113
- result = client.send_message('Runtime.callFunctionOn',
114
- functionDeclaration: function_declaration,
115
- executionContextId: context_id,
116
- arguments: converted_args,
117
- returnByValue: @return_by_value,
118
- awaitPromise: true,
119
- userGesture: true,
120
- ) # .catch(rewriteError);
118
+ result =
119
+ begin
120
+ client.send_message('Runtime.callFunctionOn',
121
+ functionDeclaration: function_declaration,
122
+ executionContextId: context_id,
123
+ arguments: converted_args,
124
+ returnByValue: @return_by_value,
125
+ awaitPromise: true,
126
+ userGesture: true,
127
+ )
128
+ rescue Puppeteer::Connection::ProtocolError => err
129
+ raise @execution_context.send(:rewrite_evaluation_error, err)
130
+ end # .catch(rewriteError);
121
131
 
122
132
  exception_details = result['exceptionDetails']
123
133
  if exception_details
@@ -337,6 +347,18 @@ class Puppeteer::ExecutionContext
337
347
  )
338
348
  end
339
349
 
350
+ # @param error [Puppeteer::Connection::ProtocolError]
351
+ # @return [StandardError]
352
+ private def rewrite_evaluation_error(error)
353
+ message = error.message
354
+ if message.end_with?('Cannot find context with specified id') ||
355
+ message.end_with?('Inspected target navigated or closed')
356
+ return Puppeteer::Error.new('Execution context was destroyed, most likely because of a navigation.')
357
+ end
358
+
359
+ error
360
+ end
361
+
340
362
  # @rbs page_function: String -- JavaScript code to check
341
363
  # @rbs return: bool -- Whether the code represents a function
342
364
  private def function_string?(page_function)