puppeteer-ruby 0.51.0 → 0.52.1

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/docs/api_coverage.md +34 -5
  3. data/lib/puppeteer/browser.rb +75 -0
  4. data/lib/puppeteer/browser_connector.rb +43 -6
  5. data/lib/puppeteer/chrome_target_manager.rb +49 -2
  6. data/lib/puppeteer/chrome_user_data_dir.rb +89 -0
  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/isolated_world.rb +22 -1
  15. data/lib/puppeteer/issue.rb +16 -0
  16. data/lib/puppeteer/js_coverage.rb +14 -1
  17. data/lib/puppeteer/launcher/browser_options.rb +6 -1
  18. data/lib/puppeteer/launcher/chrome.rb +16 -1
  19. data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
  20. data/lib/puppeteer/network_manager.rb +16 -16
  21. data/lib/puppeteer/page.rb +36 -4
  22. data/lib/puppeteer/puppeteer.rb +22 -2
  23. data/lib/puppeteer/remote_object.rb +2 -1
  24. data/lib/puppeteer/target.rb +17 -0
  25. data/lib/puppeteer/version.rb +2 -2
  26. data/lib/puppeteer.rb +2 -0
  27. data/sig/_supplementary.rbs +5 -0
  28. data/sig/puppeteer/browser.rbs +29 -2
  29. data/sig/puppeteer/chrome_user_data_dir.rbs +30 -0
  30. data/sig/puppeteer/element_handle.rbs +5 -0
  31. data/sig/puppeteer/execution_context.rbs +4 -0
  32. data/sig/puppeteer/extension.rbs +37 -0
  33. data/sig/puppeteer/frame.rbs +5 -0
  34. data/sig/puppeteer/issue.rbs +13 -0
  35. data/sig/puppeteer/page.rbs +14 -0
  36. data/sig/puppeteer/puppeteer.rbs +8 -2
  37. data/sig/puppeteer/remote_object.rbs +2 -0
  38. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74f00095ae82e1e4502260b99ff766cc46c758ae5e762050a6e7c46b7249f469
4
- data.tar.gz: a197f92fc8b7ccbe2904c088d52e60409af119d94449a6a4e9f518601c4c5788
3
+ metadata.gz: 70526928098a3ab1bcf1718b6110a6620188a30c77419a04181cb746acf4ed15
4
+ data.tar.gz: 073e09f7dd759b1b2b2d268ec3021cab96c3e56df93353343fd6ded898556d7b
5
5
  SHA512:
6
- metadata.gz: df595ba0c4996b100b1d8b22bb35e0e0ce9b6552d368c11fc25b9c8cf60c1a270b651f6f96e6c1b65b64d32550b71c402a059a0fd4d3a1393a394b65de472aef
7
- data.tar.gz: f82344aba53b730d5fdb7273ef29fdb07d0b8aadc831d680fd5611d55df980955cb5fd5cbdbe78d0de88c70d689f738952187193b4f006386a2170970f928dd2
6
+ metadata.gz: a222036f53e7552d12a8340850369aeed45277f44f1914984ff91168131fbc8ad0a93adf0a3ffe07b98e0f5038a14ee2da6239685bdbf297883bb2702d31df86
7
+ data.tar.gz: 57c5a117800a1b814d77abd3ad41d9b25487b08993e1752327dbac49a3e80474c612a52dd3141fd1025dd4fc96e5758c199a82de470abba73e290a5316db0516
data/docs/api_coverage.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # API coverages
2
- - Puppeteer version: v24.37.0
2
+ - Puppeteer version: v24.42.0
3
3
  - puppeteer-ruby version: 0.51.0
4
4
 
5
5
  ## Puppeteer
@@ -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
@@ -38,7 +39,7 @@
38
39
  * ~~setWindowBounds~~
39
40
  * target
40
41
  * targets
41
- * ~~uninstallExtension~~
42
+ * uninstallExtension => `#uninstall_extension`
42
43
  * userAgent => `#user_agent`
43
44
  * version
44
45
  * waitForTarget => `#wait_for_target`
@@ -126,7 +127,7 @@
126
127
  * $$eval => `#eval_on_selector_all`
127
128
  * $eval => `#eval_on_selector`
128
129
  * asLocator => `#as_locator`
129
- * ~~autofill~~
130
+ * autofill
130
131
  * ~~backendNodeId~~
131
132
  * boundingBox => `#bounding_box`
132
133
  * boxModel => `#box_model`
@@ -165,6 +166,12 @@
165
166
  * ~~once~~
166
167
  * ~~removeAllListeners~~
167
168
 
169
+ ## Extension
170
+
171
+ * pages
172
+ * triggerAction => `#trigger_action`
173
+ * workers
174
+
168
175
  ## ~~ExtensionTransport~~
169
176
 
170
177
  * ~~close~~
@@ -190,6 +197,7 @@
190
197
  * content
191
198
  * evaluate
192
199
  * evaluateHandle => `#evaluate_handle`
200
+ * extensionRealms => `#extension_realms`
193
201
  * focus
194
202
  * frameElement => `#frame_element`
195
203
  * goto
@@ -344,6 +352,7 @@
344
352
  * evaluateHandle => `#evaluate_handle`
345
353
  * evaluateOnNewDocument => `#evaluate_on_new_document`
346
354
  * exposeFunction => `#expose_function`
355
+ * extensionRealms => `#extension_realms`
347
356
  * focus
348
357
  * frames
349
358
  * ~~getDefaultNavigationTimeout~~
@@ -351,6 +360,7 @@
351
360
  * goBack => `#go_back`
352
361
  * goForward => `#go_forward`
353
362
  * goto
363
+ * ~~hasDevTools~~
354
364
  * hover
355
365
  * isClosed => `#closed?`
356
366
  * isDragInterceptionEnabled => `#drag_interception_enabled?`
@@ -387,6 +397,7 @@
387
397
  * tap
388
398
  * target
389
399
  * title
400
+ * triggerExtensionAction => `#trigger_extension_action`
390
401
  * type => `#type_text`
391
402
  * url
392
403
  * viewport
@@ -405,6 +416,13 @@
405
416
  ## ~~ProtocolError~~
406
417
 
407
418
 
419
+ ## ~~Realm~~
420
+
421
+ * ~~evaluate~~
422
+ * ~~evaluateHandle~~
423
+ * ~~extension~~
424
+ * ~~waitForFunction~~
425
+
408
426
  ## ~~ScreenRecorder~~
409
427
 
410
428
  * ~~stop~~
@@ -420,7 +438,7 @@
420
438
 
421
439
  ## Target
422
440
 
423
- * ~~asPage~~
441
+ * asPage => `#as_page`
424
442
  * browser
425
443
  * browserContext => `#browser_context`
426
444
  * createCDPSession => `#create_cdp_session`
@@ -451,6 +469,17 @@
451
469
  ## ~~UnsupportedOperation~~
452
470
 
453
471
 
472
+ ## ~~WebMCP~~
473
+
474
+ * ~~tools~~
475
+
476
+ ## ~~WebMCPTool~~
477
+
478
+ * ~~execute~~
479
+
480
+ ## ~~WebMCPToolCall~~
481
+
482
+
454
483
  ## WebWorker
455
484
 
456
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')
@@ -376,6 +394,63 @@ class Puppeteer::Browser
376
394
  Version.fetch(@connection).user_agent
377
395
  end
378
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
+
379
454
  # @rbs return: void -- No return value
380
455
  def close
381
456
  @close_callback.call
@@ -1,4 +1,5 @@
1
1
  require_relative './browser'
2
+ require_relative './chrome_user_data_dir'
2
3
  require_relative './launcher/browser_options'
3
4
 
4
5
  class Puppeteer::BrowserConnector
@@ -7,6 +8,7 @@ class Puppeteer::BrowserConnector
7
8
  @browser_ws_endpoint = options[:browser_ws_endpoint]
8
9
  @browser_url = options[:browser_url]
9
10
  @transport = options[:transport]
11
+ @channel = options[:channel]
10
12
  end
11
13
 
12
14
  # @return [Puppeteer::Browser]
@@ -28,6 +30,8 @@ class Puppeteer::BrowserConnector
28
30
  ignore_https_errors: @browser_options.ignore_https_errors?,
29
31
  default_viewport: @browser_options.default_viewport,
30
32
  network_enabled: @browser_options.network_enabled,
33
+ issues_enabled: @browser_options.issues_enabled,
34
+ block_list: @browser_options.block_list,
31
35
  process: nil,
32
36
  close_callback: -> { connection.send_message('Browser.close') },
33
37
  target_filter_callback: @browser_options.target_filter,
@@ -36,16 +40,24 @@ class Puppeteer::BrowserConnector
36
40
  end
37
41
 
38
42
  private def connection
39
- @connection ||=
40
- if @browser_ws_endpoint && @browser_url.nil? && @transport.nil?
43
+ @connection ||= begin
44
+ connection_options = [@browser_ws_endpoint, @browser_url, @transport, @channel]
45
+ unless connection_options.count { |option| !!option } == 1
46
+ raise ArgumentError.new('Exactly one of browserWSEndpoint, browserURL, transport or channel must be passed to puppeteer.connect')
47
+ end
48
+
49
+ if @transport
50
+ connect_with_transport(@transport)
51
+ elsif @browser_ws_endpoint
41
52
  connect_with_browser_ws_endpoint(@browser_ws_endpoint)
42
- elsif @browser_ws_endpoint.nil? && @browser_url && @transport.nil?
53
+ elsif @browser_url
43
54
  connect_with_browser_url(@browser_url)
44
- elsif @browser_ws_endpoint.nil? && @browser_url.nil? && @transport
45
- connect_with_transport(@transport)
55
+ elsif @channel
56
+ connect_with_channel(@channel)
46
57
  else
47
- raise ArgumentError.new("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect")
58
+ raise ArgumentError.new('Invalid connection options')
48
59
  end
60
+ end
49
61
  end
50
62
 
51
63
  # @return [Puppeteer::Connection]
@@ -70,6 +82,31 @@ class Puppeteer::BrowserConnector
70
82
  connect_with_browser_ws_endpoint(connection_url)
71
83
  end
72
84
 
85
+ # @return [Puppeteer::Connection]
86
+ private def connect_with_channel(channel)
87
+ port_path = File.join(
88
+ Puppeteer::ChromeUserDataDir.resolve_default(channel),
89
+ 'DevToolsActivePort',
90
+ )
91
+
92
+ begin
93
+ file_content = File.read(port_path, mode: 'r:ASCII')
94
+ raw_port, raw_path = file_content.lines.map(&:strip).reject(&:empty?)
95
+ unless raw_port && raw_path
96
+ raise Puppeteer::Error.new("Invalid DevToolsActivePort '#{file_content}' found")
97
+ end
98
+
99
+ port = raw_port.to_i
100
+ if port <= 0 || port > 65_535
101
+ raise Puppeteer::Error.new("Invalid port '#{raw_port}' found")
102
+ end
103
+
104
+ connect_with_browser_ws_endpoint("ws://localhost:#{port}#{raw_path}")
105
+ rescue StandardError
106
+ raise Puppeteer::Error.new("Could not find DevToolsActivePort for #{channel} at #{port_path}")
107
+ end
108
+ end
109
+
73
110
  # @return [Puppeteer::Connection]
74
111
  private def connect_with_transport(transport)
75
112
  Puppeteer::Connection.new(
@@ -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
@@ -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
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require_relative './env'
5
+
6
+ module Puppeteer::ChromeUserDataDir
7
+ CHANNELS = %w[chrome chrome-beta chrome-canary chrome-dev].freeze #: Array[String]
8
+
9
+ MAC_DIR_NAMES = {
10
+ 'chrome' => 'Chrome',
11
+ 'chrome-beta' => 'Chrome Beta',
12
+ 'chrome-canary' => 'Chrome Canary',
13
+ 'chrome-dev' => 'Chrome Dev',
14
+ }.freeze #: Hash[String, String]
15
+
16
+ LINUX_DIR_NAMES = {
17
+ 'chrome' => 'google-chrome',
18
+ 'chrome-beta' => 'google-chrome-beta',
19
+ 'chrome-canary' => 'google-chrome-canary',
20
+ 'chrome-dev' => 'google-chrome-unstable',
21
+ }.freeze #: Hash[String, String]
22
+
23
+ WINDOWS_DIR_NAMES = {
24
+ 'chrome' => ['Google', 'Chrome', 'User Data'],
25
+ 'chrome-beta' => ['Google', 'Chrome Beta', 'User Data'],
26
+ 'chrome-canary' => ['Google', 'Chrome SxS', 'User Data'],
27
+ 'chrome-dev' => ['Google', 'Chrome Dev', 'User Data'],
28
+ }.freeze #: Hash[String, Array[String]]
29
+
30
+ # @rbs channel: (String | Symbol) -- Chrome release channel
31
+ # @rbs platform: Symbol? -- Optional platform override
32
+ # @rbs env: Hash[String, String] -- Environment variables
33
+ # @rbs home: String? -- Home directory override
34
+ # @rbs return: String -- Default user data directory
35
+ def self.resolve_default(channel, platform: nil, env: ENV, home: nil)
36
+ channel = normalize_channel(channel)
37
+ platform ||= current_platform
38
+ home ||= Dir.home
39
+
40
+ case platform.to_sym
41
+ when :windows
42
+ base = env['LOCALAPPDATA']
43
+ base = join_path(:windows, home, 'AppData', 'Local') if base.nil? || base.empty?
44
+ join_path(:windows, base, *WINDOWS_DIR_NAMES.fetch(channel))
45
+ when :darwin
46
+ join_path(:darwin, home, 'Library', 'Application Support', 'Google', MAC_DIR_NAMES.fetch(channel))
47
+ when :linux
48
+ base = env['CHROME_CONFIG_HOME']
49
+ base = env['XDG_CONFIG_HOME'] if base.nil? || base.empty?
50
+ base = join_path(:linux, home, '.config') if base.nil? || base.empty?
51
+ join_path(:linux, base, LINUX_DIR_NAMES.fetch(channel))
52
+ else
53
+ raise ArgumentError.new("Unsupported platform: #{platform}")
54
+ end
55
+ end
56
+
57
+ # @rbs channel: (String | Symbol) -- Chrome release channel
58
+ # @rbs return: String -- Normalized channel
59
+ def self.normalize_channel(channel)
60
+ normalized_channel = channel.to_s
61
+ return normalized_channel if CHANNELS.include?(normalized_channel)
62
+
63
+ raise ArgumentError.new("Invalid channel: '#{channel}'. Allowed channel is #{CHANNELS}")
64
+ end
65
+
66
+ # @rbs return: Symbol -- Current platform
67
+ def self.current_platform
68
+ if Puppeteer.env.windows?
69
+ :windows
70
+ elsif Puppeteer.env.darwin?
71
+ :darwin
72
+ else
73
+ :linux
74
+ end
75
+ end
76
+ private_class_method :current_platform
77
+
78
+ # @rbs platform: Symbol -- Target platform
79
+ # @rbs parts: String -- Path segments
80
+ # @rbs return: String -- Joined path
81
+ def self.join_path(platform, *parts)
82
+ if platform == :windows
83
+ parts.map(&:to_s).reject(&:empty?).join('\\')
84
+ else
85
+ File.join(*parts)
86
+ end
87
+ end
88
+ private_class_method :join_path
89
+ 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)