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
@@ -0,0 +1,70 @@
1
+ # rbs_inline: enabled
2
+
3
+ class Puppeteer::Extension
4
+ # @rbs id: String -- Extension id
5
+ # @rbs version: String -- Extension version
6
+ # @rbs name: String -- Extension name
7
+ # @rbs path: String -- Extension path
8
+ # @rbs enabled: bool -- Whether extension is enabled
9
+ # @rbs browser: Puppeteer::Browser -- Browser instance
10
+ # @rbs return: void -- No return value
11
+ def initialize(id:, version:, name:, path:, enabled:, browser:)
12
+ @id = id
13
+ @version = version
14
+ @name = name
15
+ @path = path
16
+ @enabled = enabled
17
+ @browser = browser
18
+ end
19
+
20
+ # @rbs return: String -- Extension id
21
+ attr_reader :id
22
+
23
+ # @rbs return: String -- Extension version
24
+ attr_reader :version
25
+
26
+ # @rbs return: String -- Extension name
27
+ attr_reader :name
28
+
29
+ # @rbs return: String -- Extension path
30
+ attr_reader :path
31
+
32
+ # @rbs return: bool -- Whether extension is enabled
33
+ attr_reader :enabled
34
+
35
+ # @rbs return: Array[Puppeteer::CdpWebWorker] -- Extension workers
36
+ def workers
37
+ extension_prefix = "chrome-extension://#{@id}"
38
+ extension_targets = @browser.targets.select do |target|
39
+ target.type == 'service_worker' && target.url.start_with?(extension_prefix)
40
+ end
41
+ extension_targets.filter_map do |target|
42
+ target.worker
43
+ rescue
44
+ nil
45
+ end
46
+ end
47
+
48
+ # @rbs return: Array[Puppeteer::Page] -- Extension pages
49
+ def pages
50
+ extension_prefix = "chrome-extension://#{@id}"
51
+ extension_targets = @browser.targets.select do |target|
52
+ target_url = target.url
53
+ ['page', 'background_page'].include?(target.type) && target_url.start_with?(extension_prefix)
54
+ end
55
+ extension_targets.filter_map do |target|
56
+ target.as_page
57
+ rescue
58
+ nil
59
+ end
60
+ end
61
+
62
+ # @rbs page: Puppeteer::Page -- Target page
63
+ # @rbs return: void -- No return value
64
+ def trigger_action(page)
65
+ page.browser.send(:connection).send_message('Extensions.triggerAction', {
66
+ id: @id,
67
+ targetId: page._tab_id,
68
+ })
69
+ end
70
+ end
@@ -19,6 +19,7 @@ class Puppeteer::Frame
19
19
  @url = 'about:blank'
20
20
  @lifecycle_events = Set.new
21
21
  @child_frames = Set.new
22
+ @extension_worlds = {}
22
23
  if parent_frame
23
24
  parent_frame._child_frames << self
24
25
  end
@@ -71,7 +72,7 @@ class Puppeteer::Frame
71
72
  @client != @frame_manager.client
72
73
  end
73
74
 
74
- attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :puppeteer_world
75
+ attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :puppeteer_world, :extension_worlds
75
76
  attr_reader :client
76
77
 
77
78
  # @rbs other: Object -- Other object to compare
@@ -229,6 +230,11 @@ class Puppeteer::Frame
229
230
  @url
230
231
  end
231
232
 
233
+ # @rbs return: Array[untyped] -- Extension execution realms for this frame
234
+ def extension_realms
235
+ @extension_worlds.values
236
+ end
237
+
232
238
  # @rbs return: Puppeteer::Frame? -- Parent frame
233
239
  def parent_frame
234
240
  @parent_frame
@@ -425,6 +431,7 @@ class Puppeteer::Frame
425
431
  @detached = true
426
432
  @main_world.detach
427
433
  @puppeteer_world.detach
434
+ @extension_worlds.each_value(&:detach)
428
435
  if @parent_frame
429
436
  @parent_frame._child_frames.delete(self)
430
437
  end
@@ -5,6 +5,7 @@ class Puppeteer::FrameManager
5
5
  using Puppeteer::DefineAsyncMethod
6
6
 
7
7
  UTILITY_WORLD_NAME = '__puppeteer_utility_world__'
8
+ CHROME_EXTENSION_PREFIX = 'chrome-extension://'
8
9
 
9
10
  # @param {!Puppeteer.CDPSession} client
10
11
  # @param {!Puppeteer.Page} page
@@ -89,6 +90,9 @@ class Puppeteer::FrameManager
89
90
  handle_lifecycle_event(event)
90
91
  end
91
92
  end
93
+ client.on_event('Audits.issueAdded') do |event|
94
+ @page.emit_event(PageEmittedEvents::Issue, Puppeteer::Issue.new(event['issue']))
95
+ end
92
96
  end
93
97
 
94
98
  attr_reader :client, :timeout_settings
@@ -109,7 +113,9 @@ class Puppeteer::FrameManager
109
113
  Puppeteer::AsyncUtils.await_promise_all(
110
114
  client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
111
115
  client.async_send_message('Runtime.enable'),
116
+ @page.browser.issues_enabled? ? client.async_send_message('Audits.enable') : nil,
112
117
  )
118
+ maybe_setup_block_list(client)
113
119
  ensure_isolated_world(client, UTILITY_WORLD_NAME)
114
120
  @network_manager.init unless cdp_session
115
121
  rescue => err
@@ -224,7 +230,11 @@ class Puppeteer::FrameManager
224
230
  session = target.session
225
231
  frame&.send(:update_client, session)
226
232
  setup_listeners(session)
227
- async_init(target.target_info.target_id, session)
233
+ Async do
234
+ async_init(target.target_info.target_id, session).wait
235
+ rescue => err
236
+ debug_puts(err)
237
+ end
228
238
  end
229
239
 
230
240
  # @param event [Hash]
@@ -493,6 +503,7 @@ class Puppeteer::FrameManager
493
503
  # @pram session [Puppeteer::CDPSession]
494
504
  def handle_execution_context_created(context_payload, session)
495
505
  frame = if_present(context_payload.dig('auxData', 'frameId')) { |frame_id| @frames[frame_id] }
506
+ origin = context_payload['origin']
496
507
 
497
508
  world = nil
498
509
  if frame
@@ -508,6 +519,17 @@ class Puppeteer::FrameManager
508
519
  # connections so we might end up creating multiple isolated worlds.
509
520
  # We can use either.
510
521
  world = frame.puppeteer_world
522
+ elsif extension_origin?(origin)
523
+ extension_id = extract_extension_id(origin)
524
+ if extension_id
525
+ world = frame.extension_worlds[extension_id]
526
+ unless world
527
+ world = Puppeteer::IsolaatedWorld.new(frame._client || @client, self, frame, @timeout_settings)
528
+ frame.extension_worlds[extension_id] = world
529
+ end
530
+ world.origin = origin
531
+ world.world_id = extension_id
532
+ end
511
533
  end
512
534
  end
513
535
 
@@ -523,6 +545,19 @@ class Puppeteer::FrameManager
523
545
  @context_id_to_context[key] = context
524
546
  end
525
547
 
548
+ private def extension_origin?(origin)
549
+ origin.is_a?(String) && origin.start_with?(CHROME_EXTENSION_PREFIX)
550
+ end
551
+
552
+ private def extract_extension_id(origin)
553
+ return nil unless extension_origin?(origin)
554
+
555
+ path_part = origin[CHROME_EXTENSION_PREFIX.length..]
556
+ return nil unless path_part
557
+ slash_index = path_part.index('/')
558
+ slash_index ? path_part[0...slash_index] : path_part
559
+ end
560
+
526
561
  # @param execution_context_id [Integer]
527
562
  # @param session [Puppeteer::CDPSEssion]
528
563
  def handle_execution_context_destroyed(execution_context_id, session)
@@ -537,7 +572,7 @@ class Puppeteer::FrameManager
537
572
  def handle_execution_contexts_cleared(session)
538
573
  session_id = session.id
539
574
  @context_id_to_context.select! do |execution_context_id, context|
540
- key_session_id, context_id = execution_context_id.split(':', 2)
575
+ key_session_id, _context_id = execution_context_id.split(':', 2)
541
576
  # Make sure to only clear execution contexts that belong
542
577
  # to the current session.
543
578
  if key_session_id != session_id
@@ -554,6 +589,31 @@ class Puppeteer::FrameManager
554
589
  @context_id_to_context[key] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
555
590
  end
556
591
 
592
+ private def maybe_setup_block_list(client)
593
+ block_list = @page.browser.block_list
594
+ return if block_list.nil? || block_list.empty?
595
+
596
+ client.send_message('Network.enable')
597
+ matched_network_conditions = block_list.map do |pattern|
598
+ {
599
+ urlPattern: pattern,
600
+ latency: 0,
601
+ downloadThroughput: -1,
602
+ uploadThroughput: -1,
603
+ }
604
+ end
605
+ client.send_message('Network.emulateNetworkConditionsByRule', {
606
+ matchedNetworkConditions: matched_network_conditions,
607
+ offline: true,
608
+ })
609
+ rescue Puppeteer::Connection::ProtocolError => err
610
+ if err.message.include?('Method not available') || err.message.include?("wasn't found")
611
+ client.send_message('Network.setBlockedURLs', urls: block_list)
612
+ else
613
+ raise
614
+ end
615
+ end
616
+
557
617
  # @param {!Frame} frame
558
618
  private def remove_frame_recursively(frame)
559
619
  frame.child_frames.each do |child|
@@ -62,11 +62,13 @@ class Puppeteer::IsolaatedWorld
62
62
  @ctx_bindings = Set.new
63
63
  @detached = false
64
64
  @context = nil
65
+ @origin = nil
66
+ @world_id = nil
65
67
 
66
68
  @client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
67
69
  end
68
70
 
69
- attr_reader :frame, :task_manager
71
+ attr_reader :frame, :task_manager, :origin, :world_id
70
72
 
71
73
  # only used in Puppeteer::WaitTask#initialize
72
74
  private def _bound_functions
@@ -109,6 +111,25 @@ class Puppeteer::IsolaatedWorld
109
111
  @task_manager.terminate_all(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
110
112
  end
111
113
 
114
+ # @rbs origin: String -- Origin for this realm
115
+ # @rbs return: String -- Origin
116
+ def origin=(origin)
117
+ @origin = origin
118
+ end
119
+
120
+ # @rbs world_id: String -- World id for this realm
121
+ # @rbs return: String -- World id
122
+ def world_id=(world_id)
123
+ @world_id = world_id
124
+ end
125
+
126
+ # @rbs return: Puppeteer::Extension? -- Owning extension for this realm
127
+ def extension
128
+ return nil unless @world_id.is_a?(String)
129
+
130
+ frame.page.browser.extensions[@world_id]
131
+ end
132
+
112
133
  def detached?
113
134
  @detached
114
135
  end
@@ -0,0 +1,16 @@
1
+ # rbs_inline: enabled
2
+
3
+ class Puppeteer::Issue
4
+ # @rbs issue: Hash[String, untyped] -- CDP issue payload
5
+ # @rbs return: void -- No return value
6
+ def initialize(issue)
7
+ @code = issue['code']
8
+ @details = issue['details']
9
+ end
10
+
11
+ # @rbs return: String -- Issue code
12
+ attr_reader :code
13
+
14
+ # @rbs return: Hash[String, untyped] -- Issue details payload
15
+ attr_reader :details
16
+ end
@@ -26,6 +26,7 @@ class Puppeteer::JSCoverage
26
26
  @enabled = false
27
27
  @script_urls = {}
28
28
  @script_sources = {}
29
+ @script_parsed_tasks = []
29
30
  end
30
31
 
31
32
  def start(
@@ -52,9 +53,10 @@ class Puppeteer::JSCoverage
52
53
  @enabled = true
53
54
  @script_urls.clear
54
55
  @script_sources.clear
56
+ @script_parsed_tasks.clear
55
57
  @event_listeners = []
56
58
  @event_listeners << @client.add_event_listener('Debugger.scriptParsed') do |event|
57
- Async do
59
+ @script_parsed_tasks << Async do
58
60
  Puppeteer::AsyncUtils.future_with_logging { on_script_parsed(event) }.call
59
61
  end
60
62
  end
@@ -95,11 +97,22 @@ class Puppeteer::JSCoverage
95
97
  response = @client.send_message('Debugger.getScriptSource', scriptId: event['scriptId'])
96
98
  @script_urls[event['scriptId']] = url
97
99
  @script_sources[event['scriptId']] = response['scriptSource']
100
+ rescue Puppeteer::Connection::ProtocolError
101
+ # The page can navigate while we are fetching sources for coverage.
102
+ # This matches upstream behavior that ignores these transient failures.
103
+ nil
104
+ end
105
+
106
+ private def drain_script_parsed_tasks
107
+ pending_tasks = @script_parsed_tasks
108
+ @script_parsed_tasks = []
109
+ pending_tasks.each(&:wait)
98
110
  end
99
111
 
100
112
  def stop
101
113
  raise 'JSCoverage is not enabled' unless @enabled
102
114
  @enabled = false
115
+ drain_script_parsed_tasks
103
116
 
104
117
  results = Puppeteer::AsyncUtils.await_promise_all(
105
118
  @client.async_send_message('Profiler.takePreciseCoverage'),
@@ -32,7 +32,12 @@ module Puppeteer::Launcher
32
32
  @default_viewport = options.key?(:default_viewport) ? options[:default_viewport] : Puppeteer::Viewport.new(width: 800, height: 600)
33
33
  @slow_mo = options[:slow_mo] || 0
34
34
  @network_enabled = options.fetch(:network_enabled, true)
35
+ @issues_enabled = options.fetch(:issues_enabled, true)
35
36
  @protocol_timeout = options[:protocol_timeout]
37
+ @block_list = options[:block_list]
38
+ if @block_list && !@block_list.is_a?(Array)
39
+ raise ArgumentError.new('block_list must be an Array of URL patterns')
40
+ end
36
41
 
37
42
  # only for Puppeteer.connect
38
43
  @target_filter = options[:target_filter]
@@ -46,7 +51,7 @@ module Puppeteer::Launcher
46
51
  end
47
52
  end
48
53
 
49
- attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target, :network_enabled, :protocol_timeout
54
+ attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target, :network_enabled, :issues_enabled, :protocol_timeout, :block_list
50
55
 
51
56
  def ignore_https_errors?
52
57
  @ignore_https_errors
@@ -87,6 +87,8 @@ module Puppeteer::Launcher
87
87
  ignore_https_errors: @browser_options.ignore_https_errors?,
88
88
  default_viewport: @browser_options.default_viewport,
89
89
  network_enabled: @browser_options.network_enabled,
90
+ issues_enabled: @browser_options.issues_enabled,
91
+ block_list: @browser_options.block_list,
90
92
  process: runner.proc,
91
93
  close_callback: -> { runner.close },
92
94
  target_filter_callback: nil,
@@ -129,7 +131,6 @@ module Puppeteer::Launcher
129
131
  '--disable-component-update',
130
132
  '--disable-default-apps',
131
133
  '--disable-dev-shm-usage',
132
- '--disable-extensions',
133
134
  # AcceptCHFrame disabled because of crbug.com/1348106.
134
135
  '--disable-features=Translate,BackForwardCache,AcceptCHFrame,MediaRouter,OptimizationHints,IPH_ReadingModePageActionLabel,ReadAnythingOmniboxChip',
135
136
  '--disable-hang-monitor',
@@ -177,6 +178,20 @@ module Puppeteer::Launcher
177
178
  end
178
179
  end
179
180
 
181
+ if chrome_arg_options.enable_extensions
182
+ chrome_arguments << '--enable-unsafe-extension-debugging'
183
+ if chrome_arg_options.enable_extensions.is_a?(Array) && !chrome_arg_options.enable_extensions.empty?
184
+ extension_paths = chrome_arg_options.enable_extensions.map do |path|
185
+ File.expand_path(path)
186
+ end
187
+ joined_paths = extension_paths.join(',')
188
+ chrome_arguments << "--disable-extensions-except=#{joined_paths}"
189
+ chrome_arguments << "--load-extension=#{joined_paths}"
190
+ end
191
+ else
192
+ chrome_arguments << '--disable-extensions'
193
+ end
194
+
180
195
  if chrome_arg_options.args.all? { |arg| arg.start_with?('-') }
181
196
  chrome_arguments << 'about:blank'
182
197
  end
@@ -31,13 +31,14 @@ module Puppeteer::Launcher
31
31
  @user_data_dir = options[:user_data_dir]
32
32
  @devtools = options[:devtools] || false
33
33
  @headless = options[:headless]
34
+ @enable_extensions = options[:enable_extensions] || false
34
35
  if @headless.nil?
35
36
  @headless = !@devtools
36
37
  end
37
38
  @debugging_port = options[:debugging_port] || 0
38
39
  end
39
40
 
40
- attr_reader :args, :user_data_dir, :debugging_port
41
+ attr_reader :args, :user_data_dir, :debugging_port, :enable_extensions
41
42
 
42
43
  def headless?
43
44
  @headless
@@ -54,6 +54,10 @@ class Puppeteer::NetworkManager
54
54
  }
55
55
  end
56
56
 
57
+ def active?
58
+ @offline || @latency != 0 || @download != -1 || @upload != -1
59
+ end
60
+
57
61
  def refresh
58
62
  update_network_conditions
59
63
  end
@@ -102,7 +106,6 @@ class Puppeteer::NetworkManager
102
106
  @user_cache_disabled = nil
103
107
  @internal_network_condition = InternalNetworkCondition.new(method(:send_to_clients))
104
108
  @interception_semaphore = Async::Semaphore.new(1)
105
-
106
109
  add_client(@client)
107
110
  end
108
111
 
@@ -138,10 +141,8 @@ class Puppeteer::NetworkManager
138
141
  "#<Puppeteer::HTTPRequest #{values.join(' ')}>"
139
142
  end
140
143
 
141
- private def apply_to_clients
142
- @clients.each do |client|
143
- yield client
144
- end
144
+ private def apply_to_clients(&block)
145
+ @clients.each(&block)
145
146
  end
146
147
 
147
148
  private def ignore_client_error?(error)
@@ -192,7 +193,7 @@ class Puppeteer::NetworkManager
192
193
  if @protocol_request_interception_enabled
193
194
  safe_send_message(client, 'Fetch.enable',
194
195
  handleAuthRequests: true,
195
- patterns: [{ urlPattern: '*' }],
196
+ patterns: [{ urlPattern: '*' }]
196
197
  )
197
198
  else
198
199
  safe_send_message(client, 'Fetch.disable')
@@ -208,7 +209,9 @@ class Puppeteer::NetworkManager
208
209
  apply_user_agent(client)
209
210
  apply_protocol_cache_disabled(client)
210
211
  apply_protocol_request_interception(client)
211
- safe_send_message(client, 'Network.emulateNetworkConditions', @internal_network_condition.params)
212
+ if @internal_network_condition.active?
213
+ safe_send_message(client, 'Network.emulateNetworkConditions', @internal_network_condition.params)
214
+ end
212
215
  end
213
216
 
214
217
  # @param username [String|NilClass]
@@ -343,11 +346,9 @@ class Puppeteer::NetworkManager
343
346
  private def dispatch_intercepted_request(event, fetch_request_id, client:)
344
347
  if Async::Task.current?
345
348
  Async do
346
- begin
347
- handle_request(event, fetch_request_id, client: client)
348
- rescue => err
349
- debug_puts(err)
350
- end
349
+ handle_request(event, fetch_request_id, client: client)
350
+ rescue => err
351
+ debug_puts(err)
351
352
  end
352
353
  else
353
354
  handle_request(event, fetch_request_id, client: client)
@@ -365,6 +366,7 @@ class Puppeteer::NetworkManager
365
366
  if existing_request &&
366
367
  existing_request.url == event_url &&
367
368
  existing_request.method == event.dig('request', 'method')
369
+
368
370
  if_present(@network_event_manager.request_extra_info(network_request_id).shift) do |extra_info|
369
371
  existing_request.update_headers(extra_info['headers'])
370
372
  end
@@ -474,12 +476,10 @@ class Puppeteer::NetworkManager
474
476
  end
475
477
  end
476
478
 
477
- private def with_interception_lock
479
+ private def with_interception_lock(&block)
478
480
  return yield unless Async::Task.current?
479
481
 
480
- @interception_semaphore.acquire do
481
- yield
482
- end
482
+ @interception_semaphore.acquire(&block)
483
483
  end
484
484
 
485
485
  private def handle_request_without_network_instrumentation(event, client)
@@ -359,6 +359,22 @@ class Puppeteer::Page
359
359
  @target.browser_context
360
360
  end
361
361
 
362
+ # @rbs return: bool -- Whether DevTools is attached to this page
363
+ def has_devtools
364
+ !!browser._has_devtools_target(@target.target_id)
365
+ end
366
+
367
+ # @rbs extension: Puppeteer::Extension -- Extension to trigger
368
+ # @rbs return: void -- No return value
369
+ def trigger_extension_action(extension)
370
+ extension.trigger_action(self)
371
+ end
372
+
373
+ # @rbs return: Array[untyped] -- Extension execution realms on the main frame
374
+ def extension_realms
375
+ main_frame.extension_realms
376
+ end
377
+
362
378
  class TargetCrashedError < Puppeteer::Error; end
363
379
 
364
380
  private def handle_target_crashed
@@ -854,10 +870,7 @@ class Puppeteer::Page
854
870
  end
855
871
 
856
872
  private def add_console_message(type, args, stack_trace)
857
- text_tokens = args.map do |arg|
858
- value = arg.remote_object.value
859
- value.nil? ? arg.to_s : value
860
- end
873
+ text_tokens = args.map { |arg| console_value_from_js_handle(arg) }
861
874
 
862
875
  stack_trace_locations =
863
876
  if stack_trace && stack_trace['callFrames']
@@ -875,6 +888,25 @@ class Puppeteer::Page
875
888
  emit_event(PageEmittedEvents::Console, console_message)
876
889
  end
877
890
 
891
+ private def console_value_from_js_handle(handle)
892
+ remote_object = handle.remote_object
893
+ return remote_object.value unless remote_object.object_id?
894
+
895
+ value_from_remote_object_reference(remote_object)
896
+ end
897
+
898
+ private def value_from_remote_object_reference(remote_object)
899
+ description = remote_object.description.to_s
900
+ if remote_object.sub_type == 'error' && !description.empty?
901
+ newline_index = description.index("\n")
902
+ return newline_index ? description[0...newline_index] : description
903
+ end
904
+
905
+ type = remote_object.sub_type || remote_object.type
906
+ class_name = remote_object.class_name || remote_object.description || 'Object'
907
+ "[#{type} #{class_name}]"
908
+ end
909
+
878
910
  private def handle_dialog_opening(event)
879
911
  dialog_type = event['type']
880
912
  unless %w(alert confirm prompt beforeunload).include?(dialog_type)
@@ -31,6 +31,9 @@ class Puppeteer::Puppeteer
31
31
  # @rbs headless: bool? -- Run browser in headless mode
32
32
  # @rbs ignore_https_errors: bool? -- Ignore HTTPS errors
33
33
  # @rbs network_enabled: bool? -- Enable network domain
34
+ # @rbs issues_enabled: bool? -- Enable issues domain
35
+ # @rbs block_list: Array[String]? -- URL block list patterns
36
+ # @rbs enable_extensions: (bool | Array[String])? -- Enable extensions or load unpacked extensions
34
37
  # @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
35
38
  # @rbs slow_mo: Integer? -- Delay between operations (ms)
36
39
  # @rbs protocol_timeout: Integer? -- CDP protocol timeout in milliseconds
@@ -56,6 +59,9 @@ class Puppeteer::Puppeteer
56
59
  headless: nil,
57
60
  ignore_https_errors: nil,
58
61
  network_enabled: true,
62
+ issues_enabled: true,
63
+ block_list: nil,
64
+ enable_extensions: false,
59
65
  default_viewport: NoViewport.new,
60
66
  slow_mo: nil,
61
67
  protocol_timeout: nil,
@@ -85,6 +91,9 @@ class Puppeteer::Puppeteer
85
91
  headless: headless,
86
92
  ignore_https_errors: ignore_https_errors,
87
93
  network_enabled: network_enabled,
94
+ issues_enabled: issues_enabled,
95
+ block_list: block_list,
96
+ enable_extensions: enable_extensions,
88
97
  default_viewport: default_viewport,
89
98
  slow_mo: slow_mo,
90
99
  protocol_timeout: protocol_timeout,
@@ -131,8 +140,11 @@ class Puppeteer::Puppeteer
131
140
  # @rbs browser_ws_endpoint: String? -- Browser WebSocket endpoint
132
141
  # @rbs browser_url: String? -- Browser HTTP URL for WebSocket discovery
133
142
  # @rbs transport: Puppeteer::WebSocketTransport? -- Pre-connected transport
143
+ # @rbs channel: (String | Symbol)? -- Browser channel
134
144
  # @rbs ignore_https_errors: bool? -- Ignore HTTPS errors
135
145
  # @rbs network_enabled: bool? -- Enable network domain
146
+ # @rbs issues_enabled: bool? -- Enable issues domain
147
+ # @rbs block_list: Array[String]? -- URL block list patterns
136
148
  # @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
137
149
  # @rbs slow_mo: Integer? -- Delay between operations (ms)
138
150
  # @rbs protocol_timeout: Integer? -- CDP protocol timeout in milliseconds
@@ -142,9 +154,12 @@ class Puppeteer::Puppeteer
142
154
  browser_ws_endpoint: nil,
143
155
  browser_url: nil,
144
156
  transport: nil,
157
+ channel: nil,
145
158
  ignore_https_errors: nil,
146
159
  network_enabled: true,
147
- default_viewport: nil,
160
+ issues_enabled: true,
161
+ block_list: nil,
162
+ default_viewport: NoViewport.new,
148
163
  slow_mo: nil,
149
164
  protocol_timeout: nil,
150
165
  &block
@@ -153,12 +168,17 @@ class Puppeteer::Puppeteer
153
168
  browser_ws_endpoint: browser_ws_endpoint,
154
169
  browser_url: browser_url,
155
170
  transport: transport,
171
+ channel: channel&.to_s,
156
172
  ignore_https_errors: ignore_https_errors,
157
173
  network_enabled: network_enabled,
158
- default_viewport: default_viewport,
174
+ issues_enabled: issues_enabled,
175
+ block_list: block_list,
159
176
  slow_mo: slow_mo,
160
177
  protocol_timeout: protocol_timeout,
161
178
  }.compact
179
+ unless default_viewport.is_a?(NoViewport)
180
+ options[:default_viewport] = default_viewport
181
+ end
162
182
  if async_context?
163
183
  browser = Puppeteer::BrowserConnector.new(options).connect_to_browser
164
184
  if block
@@ -16,12 +16,13 @@ class Puppeteer::RemoteObject
16
16
  @object_id = payload['objectId']
17
17
  @type = payload['type']
18
18
  @sub_type = payload['subtype']
19
+ @class_name = payload['className']
19
20
  @unserializable_value = payload['unserializableValue']
20
21
  @value = payload['value']
21
22
  @description = payload['description']
22
23
  end
23
24
 
24
- attr_reader :sub_type, :type, :description
25
+ attr_reader :sub_type, :type, :class_name, :description
25
26
 
26
27
  # @rbs return: bool
27
28
  def object_id?
@@ -142,6 +142,23 @@ class Puppeteer::Target
142
142
  @page
143
143
  end
144
144
 
145
+ # @return [Puppeteer::Page]
146
+ def as_page
147
+ existing_page = page
148
+ return existing_page if existing_page
149
+ return @as_page if @as_page
150
+
151
+ client = @session || @session_factory.call(false)
152
+ client.wait_for_ready if client.respond_to?(:wait_for_ready)
153
+ @as_page = Puppeteer::Page.create(
154
+ client,
155
+ self,
156
+ @ignore_https_errors,
157
+ nil,
158
+ network_enabled: @network_enabled,
159
+ )
160
+ end
161
+
145
162
  # @return [Puppeteer::CdpWebWorker|nil]
146
163
  def worker
147
164
  return nil unless ['service_worker', 'shared_worker'].include?(@target_info.type)
@@ -1,4 +1,4 @@
1
1
  module Puppeteer
2
- VERSION = '0.51.0'
3
- REF_PUPPETEER_VERSION = '24.37.0'
2
+ VERSION = '0.52.1'
3
+ REF_PUPPETEER_VERSION = '24.42.0'
4
4
  end