puppeteer-ruby 0.45.6 → 0.50.0.alpha5

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/AGENTS.md +169 -0
  4. data/CLAUDE/README.md +41 -0
  5. data/CLAUDE/architecture.md +253 -0
  6. data/CLAUDE/cdp_protocol.md +230 -0
  7. data/CLAUDE/concurrency.md +216 -0
  8. data/CLAUDE/porting_puppeteer.md +575 -0
  9. data/CLAUDE/rbs_type_checking.md +101 -0
  10. data/CLAUDE/spec_migration_plans.md +1041 -0
  11. data/CLAUDE/testing.md +278 -0
  12. data/CLAUDE.md +242 -0
  13. data/README.md +8 -0
  14. data/Rakefile +7 -0
  15. data/Steepfile +28 -0
  16. data/docs/api_coverage.md +105 -56
  17. data/lib/puppeteer/aria_query_handler.rb +3 -2
  18. data/lib/puppeteer/async_utils.rb +214 -0
  19. data/lib/puppeteer/browser.rb +98 -56
  20. data/lib/puppeteer/browser_connector.rb +18 -3
  21. data/lib/puppeteer/browser_context.rb +196 -3
  22. data/lib/puppeteer/browser_runner.rb +18 -10
  23. data/lib/puppeteer/cdp_session.rb +67 -23
  24. data/lib/puppeteer/chrome_target_manager.rb +65 -40
  25. data/lib/puppeteer/connection.rb +55 -36
  26. data/lib/puppeteer/console_message.rb +9 -1
  27. data/lib/puppeteer/console_patch.rb +47 -0
  28. data/lib/puppeteer/css_coverage.rb +5 -3
  29. data/lib/puppeteer/custom_query_handler.rb +80 -33
  30. data/lib/puppeteer/define_async_method.rb +31 -37
  31. data/lib/puppeteer/dialog.rb +47 -14
  32. data/lib/puppeteer/element_handle.rb +231 -62
  33. data/lib/puppeteer/emulation_manager.rb +1 -1
  34. data/lib/puppeteer/env.rb +1 -1
  35. data/lib/puppeteer/errors.rb +25 -2
  36. data/lib/puppeteer/event_callbackable.rb +15 -0
  37. data/lib/puppeteer/events.rb +4 -0
  38. data/lib/puppeteer/execution_context.rb +148 -3
  39. data/lib/puppeteer/file_chooser.rb +6 -0
  40. data/lib/puppeteer/frame.rb +162 -91
  41. data/lib/puppeteer/frame_manager.rb +69 -48
  42. data/lib/puppeteer/http_request.rb +114 -38
  43. data/lib/puppeteer/http_response.rb +24 -7
  44. data/lib/puppeteer/isolated_world.rb +64 -41
  45. data/lib/puppeteer/js_coverage.rb +5 -3
  46. data/lib/puppeteer/js_handle.rb +58 -16
  47. data/lib/puppeteer/keyboard.rb +30 -17
  48. data/lib/puppeteer/launcher/browser_options.rb +3 -1
  49. data/lib/puppeteer/launcher/chrome.rb +8 -5
  50. data/lib/puppeteer/launcher/launch_options.rb +7 -2
  51. data/lib/puppeteer/launcher.rb +4 -8
  52. data/lib/puppeteer/lifecycle_watcher.rb +38 -22
  53. data/lib/puppeteer/mouse.rb +273 -64
  54. data/lib/puppeteer/network_event_manager.rb +7 -0
  55. data/lib/puppeteer/network_manager.rb +393 -112
  56. data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
  57. data/lib/puppeteer/page.rb +568 -226
  58. data/lib/puppeteer/puppeteer.rb +171 -64
  59. data/lib/puppeteer/query_handler_manager.rb +112 -16
  60. data/lib/puppeteer/reactor_runner.rb +247 -0
  61. data/lib/puppeteer/remote_object.rb +127 -47
  62. data/lib/puppeteer/target.rb +74 -27
  63. data/lib/puppeteer/task_manager.rb +3 -1
  64. data/lib/puppeteer/timeout_helper.rb +6 -10
  65. data/lib/puppeteer/touch_handle.rb +39 -0
  66. data/lib/puppeteer/touch_screen.rb +72 -22
  67. data/lib/puppeteer/tracing.rb +3 -3
  68. data/lib/puppeteer/version.rb +1 -1
  69. data/lib/puppeteer/wait_task.rb +264 -101
  70. data/lib/puppeteer/web_socket.rb +2 -2
  71. data/lib/puppeteer/web_socket_transport.rb +91 -27
  72. data/lib/puppeteer/web_worker.rb +175 -0
  73. data/lib/puppeteer.rb +20 -4
  74. data/puppeteer-ruby.gemspec +15 -11
  75. data/sig/_external.rbs +8 -0
  76. data/sig/_supplementary.rbs +314 -0
  77. data/sig/puppeteer/browser.rbs +166 -0
  78. data/sig/puppeteer/cdp_session.rbs +64 -0
  79. data/sig/puppeteer/dialog.rbs +41 -0
  80. data/sig/puppeteer/element_handle.rbs +305 -0
  81. data/sig/puppeteer/execution_context.rbs +87 -0
  82. data/sig/puppeteer/frame.rbs +226 -0
  83. data/sig/puppeteer/http_request.rbs +214 -0
  84. data/sig/puppeteer/http_response.rbs +89 -0
  85. data/sig/puppeteer/js_handle.rbs +64 -0
  86. data/sig/puppeteer/keyboard.rbs +40 -0
  87. data/sig/puppeteer/mouse.rbs +113 -0
  88. data/sig/puppeteer/page.rbs +515 -0
  89. data/sig/puppeteer/puppeteer.rbs +98 -0
  90. data/sig/puppeteer/remote_object.rbs +78 -0
  91. data/sig/puppeteer/touch_handle.rbs +21 -0
  92. data/sig/puppeteer/touch_screen.rbs +35 -0
  93. data/sig/puppeteer/web_worker.rbs +83 -0
  94. metadata +116 -45
  95. data/CHANGELOG.md +0 -397
  96. data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
  97. data/lib/puppeteer/firefox_target_manager.rb +0 -157
  98. data/lib/puppeteer/launcher/firefox.rb +0 -453
@@ -1,9 +1,12 @@
1
+ # rbs_inline: enabled
2
+
3
+ require 'base64'
1
4
  require 'json'
2
5
 
3
6
  class Puppeteer::HTTPResponse
4
7
  include Puppeteer::IfPresent
5
8
 
6
- class Redirected < StandardError
9
+ class Redirected < Puppeteer::Error
7
10
  def initialize
8
11
  super('Response body is unavailable for redirect responses')
9
12
  end
@@ -36,13 +39,13 @@ class Puppeteer::HTTPResponse
36
39
  @client = client
37
40
  @request = request
38
41
 
39
- @body_loaded_promise = resolvable_future
42
+ @body_loaded_promise = Async::Promise.new
40
43
  @remote_address = RemoteAddress.new(
41
44
  ip: response_payload['remoteIPAddress'],
42
45
  port: response_payload['remotePort'],
43
46
  )
44
47
 
45
- @status_text = parse_štatus_text_from_extra_info(extra_info) || response_payload['statusText']
48
+ @status_text = parse_status_text_from_extra_info(extra_info) || response_payload['statusText']
46
49
  @url = request.url
47
50
  @from_disk_cache = !!response_payload['fromDiskCache']
48
51
  @from_service_worker = !!response_payload['fromServiceWorker']
@@ -56,13 +59,14 @@ class Puppeteer::HTTPResponse
56
59
  @security_details = if_present(response_payload['securityDetails']) do |security_payload|
57
60
  SecurityDetails.new(security_payload)
58
61
  end
62
+ @timing = response_payload['timing']
59
63
 
60
64
  @internal = InternalAccessor.new(self)
61
65
  end
62
66
 
63
67
  attr_reader :internal
64
68
 
65
- attr_reader :remote_address, :url, :status, :status_text, :headers, :security_details, :request
69
+ attr_reader :remote_address, :url, :status, :status_text, :headers, :security_details, :request, :timing
66
70
 
67
71
  def inspect
68
72
  values = %i[remote_address url status status_text headers security_details request].map do |sym|
@@ -72,7 +76,7 @@ class Puppeteer::HTTPResponse
72
76
  "#<Puppeteer::HTTPRequest #{values.join(' ')}>"
73
77
  end
74
78
 
75
- private def parse_štatus_text_from_extra_info(extra_info)
79
+ private def parse_status_text_from_extra_info(extra_info)
76
80
  return nil if !extra_info || !extra_info['headersText']
77
81
  first_line = extra_info['headersText'].split("\r").first
78
82
  return nil unless first_line
@@ -88,34 +92,47 @@ class Puppeteer::HTTPResponse
88
92
  @status == 0 || (@status >= 200 && @status <= 299)
89
93
  end
90
94
 
95
+ # @rbs return: String -- Response body as binary string
91
96
  def buffer
92
- await @body_loaded_promise
93
- response = @client.send_message('Network.getResponseBody', requestId: @request.internal.request_id)
97
+ @body_loaded_promise.wait
98
+ response = @request.client.send_message('Network.getResponseBody', requestId: @request.internal.request_id)
94
99
  if response['base64Encoded']
95
100
  Base64.decode64(response['body'])
96
101
  else
97
102
  response['body']
98
103
  end
104
+ rescue Puppeteer::Connection::ProtocolError => err
105
+ if err.message.include?('No resource with given identifier found')
106
+ raise Puppeteer::Error.new(
107
+ 'Could not load response body for this request. This might happen if the request is a preflight request.',
108
+ )
109
+ end
110
+ raise
99
111
  end
100
112
 
101
113
  # @param text [String]
114
+ # @rbs return: String -- Response body as text
102
115
  def text
103
116
  buffer
104
117
  end
105
118
 
106
119
  # @param json [Hash]
120
+ # @rbs return: Hash[untyped, untyped] -- Parsed JSON
107
121
  def json
108
122
  JSON.parse(text)
109
123
  end
110
124
 
125
+ # @rbs return: bool -- Whether response was served from cache
111
126
  def from_cache?
112
127
  @from_disk_cache || @request.internal.from_memory_cache?
113
128
  end
114
129
 
130
+ # @rbs return: bool -- Whether response was served from service worker
115
131
  def from_service_worker?
116
132
  @from_service_worker
117
133
  end
118
134
 
135
+ # @rbs return: Puppeteer::Frame -- Frame associated with the request
119
136
  def frame
120
137
  @request.frame
121
138
  end
@@ -56,11 +56,12 @@ class Puppeteer::IsolaatedWorld
56
56
  @frame_manager = frame_manager
57
57
  @frame = frame
58
58
  @timeout_settings = timeout_settings
59
- @context_promise = resolvable_future
59
+ @context_promise = Async::Promise.new
60
60
  @task_manager = Puppeteer::TaskManager.new
61
61
  @bound_functions = {}
62
62
  @ctx_bindings = Set.new
63
63
  @detached = false
64
+ @context = nil
64
65
 
65
66
  @client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
66
67
  end
@@ -76,8 +77,9 @@ class Puppeteer::IsolaatedWorld
76
77
  def context=(context)
77
78
  if context
78
79
  @ctx_bindings.clear
80
+ @context = context
79
81
  unless @context_promise.resolved?
80
- @context_promise.fulfill(context)
82
+ @context_promise.resolve(context)
81
83
  end
82
84
  @task_manager.async_rerun_all
83
85
  else
@@ -85,9 +87,17 @@ class Puppeteer::IsolaatedWorld
85
87
  end
86
88
  end
87
89
 
88
- def delete_context(execution_context_id)
90
+ def delete_context(context_or_id)
89
91
  @document = nil
90
- @context_promise = resolvable_future
92
+ if context_or_id
93
+ if context_or_id.is_a?(Puppeteer::ExecutionContext)
94
+ return unless @context.equal?(context_or_id)
95
+ elsif @context && @context.respond_to?(:_context_id, true)
96
+ return unless @context.send(:_context_id).to_s == context_or_id.to_s
97
+ end
98
+ end
99
+ @context = nil
100
+ @context_promise = Async::Promise.new
91
101
  end
92
102
 
93
103
  def has_context?
@@ -99,14 +109,20 @@ class Puppeteer::IsolaatedWorld
99
109
  @task_manager.terminate_all(Puppeteer::WaitTask::TerminatedError.new('waitForFunction failed: frame got detached.'))
100
110
  end
101
111
 
102
- class DetachedError < StandardError; end
112
+ def detached?
113
+ @detached
114
+ end
115
+
116
+ class DetachedError < Puppeteer::Error; end
103
117
 
104
118
  # @return {!Promise<!Puppeteer.ExecutionContext>}
105
119
  def execution_context
106
120
  if @detached
107
121
  raise DetachedError.new("Execution Context is not available in detached frame \"#{@frame.url}\" (are you trying to evaluate?)")
108
122
  end
109
- @context_promise.value!
123
+ return @context if @context
124
+
125
+ @context = @context_promise.wait
110
126
  end
111
127
 
112
128
  # @param {Function|string} pageFunction
@@ -135,13 +151,13 @@ class Puppeteer::IsolaatedWorld
135
151
  # sometimes execution_context.evaluate_handle('document') returns null object.
136
152
  # D, [2020-04-24T02:17:51.023631 #220] DEBUG -- : RECV << {"id"=>20, "result"=>{"result"=>{"type"=>"object", "subtype"=>"null", "value"=>nil}}, "sessionId"=>"78E9CF1E14D81294E320E7C20E5CDE06"}
137
153
  # retry if so.
138
- Timeout.timeout(3) do
154
+ Puppeteer::AsyncUtils.async_timeout(3000, -> do
139
155
  loop do
140
156
  handle = execution_context.evaluate_handle('document')
141
157
  return handle if handle.is_a?(Puppeteer::ElementHandle)
142
158
  end
143
- end
144
- rescue Timeout::Error
159
+ end).wait
160
+ rescue Async::TimeoutError
145
161
  raise 'Bug of puppeteer-ruby...'
146
162
  end
147
163
 
@@ -179,8 +195,8 @@ class Puppeteer::IsolaatedWorld
179
195
  # `$$()` in JavaScript.
180
196
  # @param {string} selector
181
197
  # @return {!Promise<!Array<!Puppeteer.ElementHandle>>}
182
- def query_selector_all(selector)
183
- document.query_selector_all(selector)
198
+ def query_selector_all(selector, isolate: nil)
199
+ document.query_selector_all(selector, isolate: isolate)
184
200
  end
185
201
  alias_method :SS, :query_selector_all
186
202
 
@@ -188,12 +204,18 @@ class Puppeteer::IsolaatedWorld
188
204
  def content
189
205
  evaluate(<<-JAVASCRIPT)
190
206
  () => {
191
- let retVal = '';
192
- if (document.doctype)
193
- retVal = new XMLSerializer().serializeToString(document.doctype);
194
- if (document.documentElement)
195
- retVal += document.documentElement.outerHTML;
196
- return retVal;
207
+ let content = '';
208
+ for (const node of document.childNodes) {
209
+ switch (node) {
210
+ case document.documentElement:
211
+ content += document.documentElement.outerHTML;
212
+ break;
213
+ default:
214
+ content += new XMLSerializer().serializeToString(node);
215
+ break;
216
+ }
217
+ }
218
+ return content;
197
219
  }
198
220
  JAVASCRIPT
199
221
  end
@@ -218,7 +240,7 @@ class Puppeteer::IsolaatedWorld
218
240
 
219
241
  watcher = Puppeteer::LifecycleWatcher.new(@frame_manager, @frame, option_wait_until, option_timeout)
220
242
  begin
221
- await_any(
243
+ Puppeteer::AsyncUtils.await_promise_race(
222
244
  watcher.timeout_or_termination_promise,
223
245
  watcher.lifecycle_promise,
224
246
  )
@@ -238,9 +260,7 @@ class Puppeteer::IsolaatedWorld
238
260
  return execution_context.
239
261
  evaluate_handle(ADD_SCRIPT_URL, url, id, type || '').
240
262
  as_element
241
- rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
242
- raise "Loading script from #{url} failed"
243
- rescue Puppeteer::Connection::ProtocolError # for Firefox
263
+ rescue Puppeteer::ExecutionContext::EvaluationError, Puppeteer::Connection::ProtocolError
244
264
  raise "Loading script from #{url} failed"
245
265
  end
246
266
  end
@@ -300,9 +320,7 @@ class Puppeteer::IsolaatedWorld
300
320
  if url
301
321
  begin
302
322
  return execution_context.evaluate_handle(ADD_STYLE_URL, url).as_element
303
- rescue Puppeteer::ExecutionContext::EvaluationError # for Chrome
304
- raise "Loading style from #{url} failed"
305
- rescue Puppeteer::Connection::ProtocolError # for Firefox
323
+ rescue Puppeteer::ExecutionContext::EvaluationError, Puppeteer::Connection::ProtocolError
306
324
  raise "Loading style from #{url} failed"
307
325
  end
308
326
  end
@@ -350,7 +368,7 @@ class Puppeteer::IsolaatedWorld
350
368
  }
351
369
  JAVASCRIPT
352
370
 
353
- class ElementNotFoundError < StandardError
371
+ class ElementNotFoundError < Puppeteer::Error
354
372
  def initialize(selector)
355
373
  super("No node found for selector: #{selector}")
356
374
  end
@@ -359,10 +377,11 @@ class Puppeteer::IsolaatedWorld
359
377
  # @param selector [String]
360
378
  # @param delay [Number]
361
379
  # @param button [String] "left"|"right"|"middle"
362
- # @param click_count [Number]
363
- def click(selector, delay: nil, button: nil, click_count: nil)
380
+ # @param click_count [Number] Deprecated: use count (click_count only sets clickCount)
381
+ # @param count [Number]
382
+ def click(selector, delay: nil, button: nil, click_count: nil, count: nil)
364
383
  handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
365
- handle.click(delay: delay, button: button, click_count: click_count)
384
+ handle.click(delay: delay, button: button, click_count: click_count, count: count)
366
385
  handle.dispose
367
386
  end
368
387
 
@@ -382,6 +401,11 @@ class Puppeteer::IsolaatedWorld
382
401
  # await handle.hover();
383
402
  # await handle.dispose();
384
403
  # }
404
+ def hover(selector)
405
+ handle = query_selector(selector) or raise ElementNotFoundError.new(selector)
406
+ handle.hover
407
+ handle.dispose
408
+ end
385
409
 
386
410
  # @param selector [String]
387
411
  # @return [Array<String>]
@@ -480,26 +504,25 @@ class Puppeteer::IsolaatedWorld
480
504
  # @param visible [Boolean] Wait for element visible (not 'display: none' nor 'visibility: hidden') on true. default to false.
481
505
  # @param hidden [Boolean] Wait for element invisible ('display: none' nor 'visibility: hidden') on true. default to false.
482
506
  # @param timeout [Integer]
483
- private def wait_for_selector_in_page(query_one, root, selector, visible: nil, hidden: nil, timeout: nil, binding_function: nil)
507
+ private def wait_for_selector_in_page(query_one, root, selector, visible: nil, hidden: nil, timeout: nil, polling: nil, binding_function: nil)
484
508
  option_wait_for_visible = visible || false
485
509
  option_wait_for_hidden = hidden || false
486
- option_timeout = timeout || @timeout_settings.timeout
510
+ option_timeout = timeout.nil? ? @timeout_settings.timeout : timeout
487
511
  option_root = root
488
512
 
489
- polling =
490
- if option_wait_for_visible || option_wait_for_hidden
491
- 'raf'
492
- else
493
- 'mutation'
494
- end
513
+ option_polling = polling || 'mutation'
514
+ if option_wait_for_visible || option_wait_for_hidden
515
+ option_polling = 'raf'
516
+ end
495
517
  title = "selector #{selector}#{option_wait_for_hidden ? 'to be hidden' : ''}"
496
518
 
497
519
  selector_predicate = make_predicate_string(
498
520
  predicate_arg_def: '(root, selector, waitForVisible, waitForHidden)',
499
521
  predicate_query_handler: query_one,
500
522
  async: true,
501
- predicate_body: <<~JAVASCRIPT
502
- const node = await predicateQueryHandler(root, selector)
523
+ predicate_body: <<~JAVASCRIPT,
524
+ const resolvedRoot = root || document;
525
+ const node = await predicateQueryHandler(resolvedRoot, selector)
503
526
  return checkWaitForOptions(node, waitForVisible, waitForHidden);
504
527
  JAVASCRIPT
505
528
  )
@@ -508,10 +531,10 @@ class Puppeteer::IsolaatedWorld
508
531
  dom_world: self,
509
532
  predicate_body: selector_predicate,
510
533
  title: title,
511
- polling: polling,
534
+ polling: option_polling,
512
535
  timeout: option_timeout,
513
- args: [selector, option_wait_for_visible, option_wait_for_hidden],
514
536
  root: option_root,
537
+ args: [option_root, selector, option_wait_for_visible, option_wait_for_hidden],
515
538
  binding_function: binding_function,
516
539
  )
517
540
  wait_task.await_promise
@@ -524,7 +547,7 @@ class Puppeteer::IsolaatedWorld
524
547
  # @return [Puppeteer::JSHandle]
525
548
  def wait_for_function(page_function, args: [], polling: nil, timeout: nil)
526
549
  option_polling = polling || 'raf'
527
- option_timeout = timeout || @timeout_settings.timeout
550
+ option_timeout = timeout.nil? ? @timeout_settings.timeout : timeout
528
551
 
529
552
  Puppeteer::WaitTask.new(
530
553
  dom_world: self,
@@ -54,12 +54,14 @@ class Puppeteer::JSCoverage
54
54
  @script_sources.clear
55
55
  @event_listeners = []
56
56
  @event_listeners << @client.add_event_listener('Debugger.scriptParsed') do |event|
57
- future { on_script_parsed(event) }
57
+ Async do
58
+ Puppeteer::AsyncUtils.future_with_logging { on_script_parsed(event) }.call
59
+ end
58
60
  end
59
61
  @event_listeners << @client.add_event_listener('Runtime.executionContextsCleared') do
60
62
  on_execution_contexts_cleared
61
63
  end
62
- await_all(
64
+ Puppeteer::AsyncUtils.await_promise_all(
63
65
  @client.async_send_message('Profiler.enable'),
64
66
  @client.async_send_message('Profiler.startPreciseCoverage',
65
67
  callCount: @include_raw_script_coverage,
@@ -99,7 +101,7 @@ class Puppeteer::JSCoverage
99
101
  raise 'JSCoverage is not enabled' unless @enabled
100
102
  @enabled = false
101
103
 
102
- results = await_all(
104
+ results = Puppeteer::AsyncUtils.await_promise_all(
103
105
  @client.async_send_message('Profiler.takePreciseCoverage'),
104
106
  @client.async_send_message('Profiler.stopPreciseCoverage'),
105
107
  @client.async_send_message('Profiler.disable'),
@@ -1,3 +1,7 @@
1
+ # rbs_inline: enabled
2
+
3
+ require 'time'
4
+
1
5
  class Puppeteer::JSHandle
2
6
  using Puppeteer::DefineAsyncMethod
3
7
  include Puppeteer::IfPresent
@@ -29,6 +33,7 @@ class Puppeteer::JSHandle
29
33
  @client = client
30
34
  @remote_object = remote_object
31
35
  @disposed = false
36
+ @moved = false
32
37
  end
33
38
 
34
39
  attr_reader :context, :remote_object
@@ -92,7 +97,7 @@ class Puppeteer::JSHandle
92
97
  def properties
93
98
  response = @remote_object.properties(@client)
94
99
  response['result'].each_with_object({}) do |prop, h|
95
- next unless prop['enumerable']
100
+ next unless prop['enumerable'] && prop['value']
96
101
  h[prop['name']] = Puppeteer::JSHandle.create(
97
102
  context: @context,
98
103
  remote_object: Puppeteer::RemoteObject.new(prop['value']),
@@ -101,21 +106,29 @@ class Puppeteer::JSHandle
101
106
  end
102
107
 
103
108
  def json_value
104
- # original logic was:
105
- # if (this._remoteObject.objectId) {
106
- # const response = await this._client.send('Runtime.callFunctionOn', {
107
- # functionDeclaration: 'function() { return this; }',
108
- # objectId: this._remoteObject.objectId,
109
- # returnByValue: true,
110
- # awaitPromise: true,
111
- # });
112
- # return helper.valueFromRemoteObject(response.result);
109
+ # Node.js Puppeteer:
110
+ # if (!this.#remoteObject.objectId) {
111
+ # return valueFromRemoteObject(this.#remoteObject);
113
112
  # }
114
- # return helper.valueFromRemoteObject(this._remoteObject);
115
- #
116
- # However it would be better that RemoteObject is responsible for
117
- # the logic `if (this._remoteObject.objectId) { ... }`.
118
- @remote_object.evaluate_self(@client)&.value || @remote_object.value
113
+ # const value = await this.evaluate(object => object);
114
+ # if (value === undefined) {
115
+ # throw new Error('Could not serialize referenced object');
116
+ # }
117
+ # return value;
118
+ if !@remote_object.object_id?
119
+ return @remote_object.value
120
+ end
121
+
122
+ if @remote_object.sub_type == 'date'
123
+ iso_value = evaluate('(object) => object.toISOString()')
124
+ return Time.iso8601(iso_value) if iso_value
125
+ end
126
+
127
+ value = evaluate('(object) => object')
128
+ if value.nil?
129
+ raise Puppeteer::Error.new('Could not serialize referenced object')
130
+ end
131
+ value
119
132
  end
120
133
 
121
134
  def as_element
@@ -133,6 +146,24 @@ class Puppeteer::JSHandle
133
146
  @disposed
134
147
  end
135
148
 
149
+ # @rbs return: void
150
+ def dispose_symbol
151
+ if @moved
152
+ @moved = false
153
+ return
154
+ end
155
+
156
+ dispose
157
+ end
158
+
159
+ define_async_method :async_dispose_symbol
160
+
161
+ # @rbs return: self
162
+ def move
163
+ @moved = true
164
+ self
165
+ end
166
+
136
167
  def to_s
137
168
  # original logic was:
138
169
  # if (this._remoteObject.objectId) {
@@ -143,6 +174,17 @@ class Puppeteer::JSHandle
143
174
  #
144
175
  # However it would be better that RemoteObject is responsible for
145
176
  # the logic `if (this._remoteObject.objectId) { ... }`.
146
- if_present(@remote_object.type_str) { |type_str| "JSHandle@#{type_str}" } || "JSHandle:#{@remote_object.value || 'undefined'}"
177
+ if_present(@remote_object.type_str) { |type_str| "JSHandle@#{type_str}" } || "JSHandle:#{stringify_remote_value}"
178
+ end
179
+
180
+ # @rbs return: String
181
+ private def stringify_remote_value
182
+ value = @remote_object.value
183
+ return value unless value.nil?
184
+
185
+ return 'undefined' if @remote_object.type == 'undefined'
186
+ return 'null' if @remote_object.sub_type == 'null'
187
+
188
+ 'undefined'
147
189
  end
148
190
  end
@@ -1,10 +1,13 @@
1
+ # rbs_inline: enabled
2
+
1
3
  require_relative './keyboard/key_description'
2
4
  require_relative './keyboard/us_keyboard_layout'
3
5
 
4
6
  class Puppeteer::Keyboard
5
7
  using Puppeteer::DefineAsyncMethod
6
8
 
7
- # @param {!Puppeteer.CDPSession} client
9
+ # @rbs client: Puppeteer::CDPSession -- CDP session
10
+ # @rbs return: void -- No return value
8
11
  def initialize(client)
9
12
  @client = client
10
13
  @modifiers = 0
@@ -13,8 +16,10 @@ class Puppeteer::Keyboard
13
16
 
14
17
  attr_reader :modifiers
15
18
 
16
- # @param key [String]
17
- # @param text [String]
19
+ # @rbs key: String -- Key name
20
+ # @rbs text: String? -- Text to input
21
+ # @rbs commands: Array[String]? -- Editing commands
22
+ # @rbs return: void -- No return value
18
23
  def down(key, text: nil, commands: nil)
19
24
  description = key_description_for_string(key)
20
25
 
@@ -41,8 +46,6 @@ class Puppeteer::Keyboard
41
46
 
42
47
  define_async_method :async_down
43
48
 
44
- # @param {string} key
45
- # @return {number}
46
49
  private def modifier_bit(key)
47
50
  case key
48
51
  when 'Alt'
@@ -58,8 +61,6 @@ class Puppeteer::Keyboard
58
61
  end
59
62
  end
60
63
 
61
- # @param {string} keyString
62
- # @return {KeyDescription}
63
64
  private def key_description_for_string(key_string)
64
65
  shift = (@modifiers & 8) != 0
65
66
  description = {}
@@ -106,7 +107,8 @@ class Puppeteer::Keyboard
106
107
  KeyDescription.new(**description)
107
108
  end
108
109
 
109
- # @param key [String]
110
+ # @rbs key: String -- Key name
111
+ # @rbs return: void -- No return value
110
112
  def up(key)
111
113
  description = key_description_for_string(key)
112
114
 
@@ -125,22 +127,24 @@ class Puppeteer::Keyboard
125
127
 
126
128
  define_async_method :async_up
127
129
 
128
- # @param char [string]
130
+ # @rbs char: String -- Character to insert
131
+ # @rbs return: void -- No return value
129
132
  def send_character(char)
130
133
  @client.send_message('Input.insertText', text: char)
131
134
  end
132
135
 
133
136
  define_async_method :async_send_character
134
137
 
135
- # @param text [String]
136
- # @return [Future]
138
+ # @rbs text: String -- Text to type
139
+ # @rbs delay: Numeric? -- Delay between key presses (ms)
140
+ # @rbs return: void -- No return value
137
141
  def type_text(text, delay: nil)
138
142
  text.each_char do |char|
139
143
  if KEY_DEFINITIONS.include?(char.to_sym)
140
144
  press(char, delay: delay)
141
145
  else
142
146
  if delay
143
- sleep(delay.to_i / 1000.0)
147
+ Puppeteer::AsyncUtils.sleep_seconds(delay.to_i / 1000.0)
144
148
  end
145
149
  send_character(char)
146
150
  end
@@ -149,13 +153,22 @@ class Puppeteer::Keyboard
149
153
 
150
154
  define_async_method :async_type_text
151
155
 
152
- # @param key [String]
153
- # @param text [String]
154
- # @return [Future]
155
- def press(key, delay: nil, text: nil, commands: nil)
156
+ # @rbs key: String -- Key name
157
+ # @rbs delay: Numeric? -- Delay between key events (ms)
158
+ # @rbs text: String? -- Text to input
159
+ # @rbs commands: Array[String]? -- Editing commands
160
+ # @rbs block: Proc? -- Optional block for key combo usage
161
+ # @rbs return: void -- No return value
162
+ def press(key, delay: nil, text: nil, commands: nil, &block)
156
163
  down(key, text: text, commands: commands)
157
164
  if delay
158
- sleep(delay.to_i / 1000.0)
165
+ Puppeteer::AsyncUtils.sleep_seconds(delay.to_i / 1000.0)
166
+ end
167
+ if block
168
+ block.call
169
+ if delay
170
+ Puppeteer::AsyncUtils.sleep_seconds(delay.to_i / 1000.0)
171
+ end
159
172
  end
160
173
  up(key)
161
174
  end
@@ -31,6 +31,8 @@ module Puppeteer::Launcher
31
31
  # `default_viewport: nil` must be respected here.
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
+ @network_enabled = options.fetch(:network_enabled, true)
35
+ @protocol_timeout = options[:protocol_timeout]
34
36
 
35
37
  # only for Puppeteer.connect
36
38
  @target_filter = options[:target_filter]
@@ -44,7 +46,7 @@ module Puppeteer::Launcher
44
46
  end
45
47
  end
46
48
 
47
- attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target
49
+ attr_reader :default_viewport, :slow_mo, :target_filter, :is_page_target, :network_enabled, :protocol_timeout
48
50
 
49
51
  def ignore_https_errors?
50
52
  @ignore_https_errors
@@ -56,7 +56,6 @@ module Puppeteer::Launcher
56
56
  end
57
57
  use_pipe = chrome_arguments.include?('--remote-debugging-pipe')
58
58
  runner = Puppeteer::BrowserRunner.new(
59
- false,
60
59
  chrome_executable,
61
60
  chrome_arguments,
62
61
  user_data_dir,
@@ -78,6 +77,7 @@ module Puppeteer::Launcher
78
77
  timeout: @launch_options.timeout,
79
78
  slow_mo: @browser_options.slow_mo,
80
79
  preferred_revision: @preferred_revision,
80
+ protocol_timeout: @browser_options.protocol_timeout,
81
81
  )
82
82
 
83
83
  Puppeteer::Browser.create(
@@ -86,6 +86,7 @@ module Puppeteer::Launcher
86
86
  context_ids: [],
87
87
  ignore_https_errors: @browser_options.ignore_https_errors?,
88
88
  default_viewport: @browser_options.default_viewport,
89
+ network_enabled: @browser_options.network_enabled,
89
90
  process: runner.proc,
90
91
  close_callback: -> { runner.close },
91
92
  target_filter_callback: nil,
@@ -97,10 +98,12 @@ module Puppeteer::Launcher
97
98
  end
98
99
 
99
100
  begin
100
- browser.wait_for_target(
101
- predicate: ->(target) { target.type == 'page' },
102
- timeout: @launch_options.timeout,
103
- )
101
+ if @launch_options.wait_for_initial_page?
102
+ browser.wait_for_target(
103
+ predicate: ->(target) { target.type == 'page' },
104
+ timeout: @launch_options.timeout,
105
+ )
106
+ end
104
107
  rescue
105
108
  browser.close
106
109
  raise
@@ -31,6 +31,7 @@ module Puppeteer::Launcher
31
31
  # @property {boolean=} dumpio
32
32
  # @property {!Object<string, string | undefined>=} env
33
33
  # @property {boolean=} pipe
34
+ # @property {boolean=} wait_for_initial_page
34
35
  def initialize(options)
35
36
  @channel = options[:channel]
36
37
  @executable_path = options[:executable_path]
@@ -42,10 +43,10 @@ module Puppeteer::Launcher
42
43
  @dumpio = options[:dumpio] || false
43
44
  @env = options[:env] || ENV
44
45
  @pipe = options[:pipe] || false
45
- @extra_prefs_firefox = options[:extra_prefs_firefox] || {}
46
+ @wait_for_initial_page = options.fetch(:wait_for_initial_page, true)
46
47
  end
47
48
 
48
- attr_reader :channel, :executable_path, :ignore_default_args, :timeout, :env, :extra_prefs_firefox
49
+ attr_reader :channel, :executable_path, :ignore_default_args, :timeout, :env
49
50
 
50
51
  def handle_SIGINT?
51
52
  @handle_SIGINT
@@ -66,5 +67,9 @@ module Puppeteer::Launcher
66
67
  def pipe?
67
68
  @pipe
68
69
  end
70
+
71
+ def wait_for_initial_page?
72
+ @wait_for_initial_page
73
+ end
69
74
  end
70
75
  end