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,3 +1,5 @@
1
+ # rbs_inline: enabled
2
+
1
3
  require_relative './element_handle/bounding_box'
2
4
  require_relative './element_handle/box_model'
3
5
  require_relative './element_handle/offset'
@@ -8,10 +10,10 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
8
10
  include Puppeteer::IfPresent
9
11
  using Puppeteer::DefineAsyncMethod
10
12
 
11
- # @param context [Puppeteer::ExecutionContext]
12
- # @param client [Puppeteer::CDPSession]
13
- # @param remote_object [Puppeteer::RemoteObject]
14
- # @param frame [Puppeteer::Frame]
13
+ # @rbs context: Puppeteer::ExecutionContext -- Execution context
14
+ # @rbs client: Puppeteer::CDPSession -- CDP session
15
+ # @rbs remote_object: Puppeteer::RemoteObject -- Remote object handle
16
+ # @rbs frame: Puppeteer::Frame -- Owning frame
15
17
  def initialize(context:, client:, remote_object:, frame:)
16
18
  super(context: context, client: client, remote_object: remote_object)
17
19
  @frame = frame
@@ -22,6 +24,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
22
24
 
23
25
  attr_reader :page, :frame, :frame_manager
24
26
 
27
+ # @rbs return: String -- Inspection string
25
28
  def inspect
26
29
  values = %i[context remote_object page disposed].map do |sym|
27
30
  value = instance_variable_get(:"@#{sym}")
@@ -38,11 +41,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
38
41
  #
39
42
  # This method does not work across navigations or if the element is detached from DOM.
40
43
  #
41
- # @param selector - A
42
44
  # {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors | selector}
43
45
  # of an element to wait for
44
- # @param options - Optional waiting parameters
45
- # @returns Promise which resolves when element specified by selector string
46
46
  # is added to DOM. Resolves to `null` if waiting for hidden: `true` and
47
47
  # selector is not found in DOM.
48
48
  # @remarks
@@ -59,8 +59,18 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
59
59
  # - `timeout`: maximum time to wait in milliseconds. Defaults to `30000`
60
60
  # (30 seconds). Pass `0` to disable timeout. The default value can be changed
61
61
  # by using the {@link Page.setDefaultTimeout} method.
62
+ # @rbs selector: String -- CSS selector
63
+ # @rbs visible: bool? -- Wait for element to be visible
64
+ # @rbs hidden: bool? -- Wait for element to be hidden
65
+ # @rbs timeout: Numeric? -- Maximum wait time in milliseconds
66
+ # @rbs return: Puppeteer::ElementHandle? -- Matched element handle
62
67
  def wait_for_selector(selector, visible: nil, hidden: nil, timeout: nil)
63
- query_handler_manager.detect_query_handler(selector).wait_for(self, visible: visible, hidden: hidden, timeout: timeout)
68
+ query_handler_manager.detect_query_handler(selector).wait_for(
69
+ self,
70
+ visible: visible,
71
+ hidden: hidden,
72
+ timeout: timeout,
73
+ )
64
74
  end
65
75
 
66
76
  define_async_method :async_wait_for_selector
@@ -92,11 +102,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
92
102
  # await browser.close();
93
103
  # })();
94
104
  # ```
95
- # @param xpath - A
96
105
  # {@link https://developer.mozilla.org/en-US/docs/Web/XPath | xpath} of an
97
106
  # element to wait for
98
- # @param options - Optional waiting parameters
99
- # @returns Promise which resolves when element specified by xpath string is
100
107
  # added to DOM. Resolves to `null` if waiting for `hidden: true` and xpath is
101
108
  # not found in DOM.
102
109
  # @remarks
@@ -113,6 +120,11 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
113
120
  # - `timeout`: A number which is maximum time to wait for in milliseconds.
114
121
  # Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default
115
122
  # value can be changed by using the {@link Page.setDefaultTimeout} method.
123
+ # @rbs xpath: String -- XPath expression
124
+ # @rbs visible: bool? -- Wait for element to be visible
125
+ # @rbs hidden: bool? -- Wait for element to be hidden
126
+ # @rbs timeout: Numeric? -- Maximum wait time in milliseconds
127
+ # @rbs return: Puppeteer::ElementHandle? -- Matched element handle
116
128
  def wait_for_xpath(xpath, visible: nil, hidden: nil, timeout: nil)
117
129
  param_xpath =
118
130
  if xpath.start_with?('//')
@@ -126,6 +138,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
126
138
 
127
139
  define_async_method :async_wait_for_xpath
128
140
 
141
+ # @rbs tag_name: String -- Tag name to assert
142
+ # @rbs return: Puppeteer::ElementHandle -- Element handle
129
143
  def to_element(tag_name)
130
144
  unless evaluate('(node, tagName) => node.nodeName === tagName.toUpperCase()', tag_name)
131
145
  raise ArgumentError.new("Element is not a(n) `#{tag_name}` element")
@@ -133,10 +147,45 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
133
147
  self
134
148
  end
135
149
 
150
+ # @rbs return: Puppeteer::ElementHandle -- Element handle
136
151
  def as_element
137
152
  self
138
153
  end
139
154
 
155
+ # @rbs return: bool -- Whether element is visible
156
+ def visible?
157
+ check_visibility(true)
158
+ end
159
+
160
+ # @rbs return: bool -- Whether element is hidden
161
+ def hidden?
162
+ check_visibility(false)
163
+ end
164
+
165
+ # @rbs visible: bool -- Expected visibility state
166
+ # @rbs return: bool -- Whether visibility matches
167
+ private def check_visibility(visible)
168
+ js = <<~JAVASCRIPT
169
+ (node, visible) => {
170
+ if (!node) return visible === false;
171
+ const element =
172
+ node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
173
+ if (!element) return visible === false;
174
+ const style = window.getComputedStyle(element);
175
+ const rect = element.getBoundingClientRect();
176
+ const isVisible =
177
+ style &&
178
+ style.visibility !== 'hidden' &&
179
+ style.visibility !== 'collapse' &&
180
+ rect.width !== 0 &&
181
+ rect.height !== 0;
182
+ return visible === isVisible;
183
+ }
184
+ JAVASCRIPT
185
+ evaluate(js, visible)
186
+ end
187
+
188
+ # @rbs return: Puppeteer::Frame? -- Frame that owns this element
140
189
  def content_frame
141
190
  node_info = @remote_object.node_info(@client)
142
191
  frame_id = node_info['node']['frameId']
@@ -147,8 +196,9 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
147
196
  end
148
197
  end
149
198
 
150
- class ScrollIntoViewError < StandardError; end
199
+ class ScrollIntoViewError < Puppeteer::Error; end
151
200
 
201
+ # @rbs return: void -- No return value
152
202
  def scroll_into_view_if_needed
153
203
  js = <<~JAVASCRIPT
154
204
  async(element) => {
@@ -165,7 +215,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
165
215
  end
166
216
  begin
167
217
  @remote_object.scroll_into_view_if_needed(@client)
168
- rescue => err
218
+ rescue
169
219
  # Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
170
220
  js = <<~JAVASCRIPT
171
221
  async (element, pageJavascriptEnabled) => {
@@ -198,26 +248,27 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
198
248
  sleep 0.16
199
249
  end
200
250
 
201
- class ElementNotVisibleError < StandardError
251
+ class ElementNotVisibleError < Puppeteer::Error
202
252
  def initialize
203
253
  super("Node is either not visible or not an HTMLElement")
204
254
  end
205
255
  end
206
256
 
207
- class ElementNotClickableError < StandardError
257
+ class ElementNotClickableError < Puppeteer::Error
208
258
  def initialize
209
259
  super("Node is either not clickable or not an HTMLElement")
210
260
  end
211
261
  end
212
262
 
213
- # @param quad [Array<Array<Point>>]]
214
- # @param offset [Point]
263
+ # @rbs quad: Array[Point] -- Quad points
264
+ # @rbs offset: Point -- Offset to apply
265
+ # @rbs return: Array[Point] -- Offset quad points
215
266
  private def apply_offsets_to_quad(quad, offset)
216
267
  quad.map { |part| part + offset }
217
268
  end
218
269
 
219
- # @param frame [Puppeteer::Frame]
220
- # @return [Point]
270
+ # @rbs frame: Puppeteer::Frame -- Frame to calculate offsets for
271
+ # @rbs return: Point -- Calculated offset
221
272
  private def oopif_offsets(frame)
222
273
  offset = Point.new(x: 0, y: 0)
223
274
  while frame.parent_frame
@@ -235,6 +286,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
235
286
  offset
236
287
  end
237
288
 
289
+ # @rbs offset: Puppeteer::ElementHandle::Offset | Hash[Symbol, Numeric] | nil -- Click offset
290
+ # @rbs return: Puppeteer::ElementHandle::Point -- Clickable point
238
291
  def clickable_point(offset = nil)
239
292
  offset_param = Offset.from(offset)
240
293
 
@@ -258,7 +311,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
258
311
  end
259
312
 
260
313
  # Filter out quads that have too small area to click into.
261
- # Fallback to `layoutViewport` in case of using Firefox.
314
+ # Prefer cssLayoutViewport when available.
262
315
  layout_viewport = layout_metrics["cssLayoutViewport"] || layout_metrics["layoutViewport"]
263
316
  client_width = layout_viewport["clientWidth"]
264
317
  client_height = layout_viewport["clientHeight"]
@@ -290,18 +343,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
290
343
  quads.first.reduce(:+) / 4
291
344
  end
292
345
 
293
- # @param quad [Array<number>]
294
- # @return [Array<Point>]
346
+ # @rbs quad: Array[Numeric] -- Protocol quad coordinates
347
+ # @rbs return: Array[Point] -- Point array
295
348
  private def from_protocol_quad(quad)
296
349
  quad.each_slice(2).map do |x, y|
297
350
  Point.new(x: x, y: y)
298
351
  end
299
352
  end
300
353
 
301
- # @param quad [Array<Point>]
302
- # @param width [number]
303
- # @param height [number]
304
- # @return [Array<Point>]
305
354
  private def intersect_quad_with_viewport(quad, width, height)
306
355
  quad.map do |point|
307
356
  Point.new(
@@ -311,30 +360,67 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
311
360
  end
312
361
  end
313
362
 
363
+ # @rbs return: void -- No return value
314
364
  def hover
315
365
  scroll_into_view_if_needed
316
366
  point = clickable_point
317
367
  @page.mouse.move(point.x, point.y)
318
368
  end
319
369
 
320
- # @param delay [Number]
321
- # @param button [String] "left"|"right"|"middle"
322
- # @param click_count [Number]
323
- # @param offset [Hash]
324
- def click(delay: nil, button: nil, click_count: nil, offset: nil)
370
+ # @rbs delay: Numeric? -- Delay between down and up (ms)
371
+ # @rbs button: String? -- Mouse button
372
+ # @rbs click_count: Integer? -- Deprecated: use count (click_count only sets clickCount)
373
+ # @rbs count: Integer? -- Number of clicks to perform
374
+ # @rbs offset: Puppeteer::ElementHandle::Offset | Hash[Symbol, Numeric] | nil -- Click offset
375
+ # @rbs return: void -- No return value
376
+ def click(delay: nil, button: nil, click_count: nil, count: nil, offset: nil)
325
377
  scroll_into_view_if_needed
326
378
  point = clickable_point(offset)
327
- @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
379
+ @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count, count: count)
328
380
  end
329
381
 
330
382
  define_async_method :async_click
331
383
 
332
- class DragInterceptionNotEnabledError < StandardError
384
+ # @rbs return: Puppeteer::TouchHandle -- Touch handle
385
+ def touch_start
386
+ scroll_into_view_if_needed
387
+ point = clickable_point
388
+ @page.touchscreen.touch_start(point.x, point.y)
389
+ end
390
+
391
+ define_async_method :async_touch_start
392
+
393
+ # @rbs touch: Puppeteer::TouchHandle? -- Optional touch handle
394
+ # @rbs return: void -- No return value
395
+ def touch_move(touch = nil)
396
+ scroll_into_view_if_needed
397
+ point = clickable_point
398
+ if touch
399
+ touch.move(point.x, point.y)
400
+ else
401
+ @page.touchscreen.touch_move(point.x, point.y)
402
+ end
403
+ end
404
+
405
+ define_async_method :async_touch_move
406
+
407
+ # @rbs return: void -- No return value
408
+ def touch_end
409
+ scroll_into_view_if_needed
410
+ @page.touchscreen.touch_end
411
+ end
412
+
413
+ define_async_method :async_touch_end
414
+
415
+ class DragInterceptionNotEnabledError < Puppeteer::Error
333
416
  def initialize
334
417
  super('Drag Interception is not enabled!')
335
418
  end
336
419
  end
337
420
 
421
+ # @rbs x: Numeric -- Drag end X coordinate
422
+ # @rbs y: Numeric -- Drag end Y coordinate
423
+ # @rbs return: void -- No return value
338
424
  def drag(x:, y:)
339
425
  unless @page.drag_interception_enabled?
340
426
  raise DragInterceptionNotEnabledError.new
@@ -344,25 +430,33 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
344
430
  @page.mouse.drag(start, Point.new(x: x, y: y))
345
431
  end
346
432
 
433
+ # @rbs data: Hash[String, untyped] -- Drag data payload
434
+ # @rbs return: void -- No return value
347
435
  def drag_enter(data)
348
436
  scroll_into_view_if_needed
349
437
  target = clickable_point
350
438
  @page.mouse.drag_enter(target, data)
351
439
  end
352
440
 
441
+ # @rbs data: Hash[String, untyped] -- Drag data payload
442
+ # @rbs return: void -- No return value
353
443
  def drag_over(data)
354
444
  scroll_into_view_if_needed
355
445
  target = clickable_point
356
446
  @page.mouse.drag_over(target, data)
357
447
  end
358
448
 
449
+ # @rbs data: Hash[String, untyped] -- Drag data payload
450
+ # @rbs return: void -- No return value
359
451
  def drop(data)
360
452
  scroll_into_view_if_needed
361
453
  target = clickable_point
362
454
  @page.mouse.drop(target, data)
363
455
  end
364
456
 
365
- # @param target [ElementHandle]
457
+ # @rbs target: Puppeteer::ElementHandle -- Drop target element
458
+ # @rbs delay: Numeric? -- Delay before dropping (ms)
459
+ # @rbs return: void -- No return value
366
460
  def drag_and_drop(target, delay: nil)
367
461
  scroll_into_view_if_needed
368
462
  start_point = clickable_point
@@ -370,7 +464,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
370
464
  @page.mouse.drag_and_drop(start_point, target_point, delay: delay)
371
465
  end
372
466
 
373
- # @return [Array<String>]
467
+ # @rbs values: Array[String] -- Option values to select
468
+ # @rbs return: Array[String] -- Selected values
374
469
  def select(*values)
375
470
  if nonstring = values.find { |value| !value.is_a?(String) }
376
471
  raise ArgumentError.new("Values must be strings. Found value \"#{nonstring}\" of type \"#{nonstring.class}\"")
@@ -411,7 +506,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
411
506
  evaluate(fn, values)
412
507
  end
413
508
 
414
- # @param file_paths [Array<String>]
509
+ # @rbs file_paths: Array[String] -- Files to upload
510
+ # @rbs return: void -- No return value
415
511
  def upload_file(*file_paths)
416
512
  is_multiple = evaluate("el => el.multiple")
417
513
  if !is_multiple && file_paths.length >= 2
@@ -433,12 +529,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
433
529
  element.dispatchEvent(new Event('change', { bubbles: true }));
434
530
  }
435
531
  JAVASCRIPT
436
- await evaluate(fn)
532
+ evaluate(fn)
437
533
  else
438
534
  @remote_object.set_file_input_files(@client, file_paths.map { |path| File.expand_path(path) }, backend_node_id)
439
535
  end
440
536
  end
441
537
 
538
+ # @rbs block: Proc? -- Optional block for Object#tap usage
539
+ # @rbs return: Puppeteer::ElementHandle | nil -- Element handle or nil
442
540
  def tap(&block)
443
541
  return super(&block) if block
444
542
 
@@ -449,14 +547,16 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
449
547
 
450
548
  define_async_method :async_tap
451
549
 
550
+ # @rbs return: void -- No return value
452
551
  def focus
453
552
  evaluate('element => element.focus()')
454
553
  end
455
554
 
456
555
  define_async_method :async_focus
457
556
 
458
- # @param text [String]
459
- # @param delay [number|nil]
557
+ # @rbs text: String -- Text to type
558
+ # @rbs delay: Numeric? -- Delay between key presses (ms)
559
+ # @rbs return: void -- No return value
460
560
  def type_text(text, delay: nil)
461
561
  focus
462
562
  @page.keyboard.type_text(text, delay: delay)
@@ -464,17 +564,18 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
464
564
 
465
565
  define_async_method :async_type_text
466
566
 
467
- # @param key [String]
468
- # @param text [String]
469
- # @param delay [number|nil]
567
+ # @rbs key: String -- Key name
568
+ # @rbs delay: Numeric? -- Delay between key events (ms)
569
+ # @rbs text: String? -- Text to input
570
+ # @rbs return: void -- No return value
470
571
  def press(key, delay: nil, text: nil)
471
572
  focus
472
- @page.keyboard.press(key, delay: delay, text: text)
573
+ @page.keyboard.press(key, delay: delay)
473
574
  end
474
575
 
475
576
  define_async_method :async_press
476
577
 
477
- # @return [BoundingBox|nil]
578
+ # @rbs return: Puppeteer::ElementHandle::BoundingBox? -- Bounding box or nil
478
579
  def bounding_box
479
580
  if_present(box_model) do |result_model|
480
581
  offset = oopif_offsets(@frame)
@@ -491,13 +592,23 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
491
592
  end
492
593
  end
493
594
 
494
- # @return [BoxModel|nil]
595
+ # @rbs return: Puppeteer::ElementHandle::BoxModel? -- Box model or nil
495
596
  def box_model
496
597
  if_present(@remote_object.box_model(@client)) do |result|
497
598
  BoxModel.new(result['model'], offset: oopif_offsets(@frame))
498
599
  end
499
600
  end
500
601
 
602
+ # @rbs type: String? -- Image format
603
+ # @rbs path: String? -- File path to save
604
+ # @rbs full_page: bool? -- Capture full page
605
+ # @rbs clip: Hash[Symbol, Numeric]? -- Clip rectangle
606
+ # @rbs quality: Integer? -- JPEG quality
607
+ # @rbs omit_background: bool? -- Omit background for PNG
608
+ # @rbs encoding: String? -- Encoding (base64 or binary)
609
+ # @rbs capture_beyond_viewport: bool? -- Capture beyond viewport
610
+ # @rbs from_surface: bool? -- Capture from surface
611
+ # @rbs return: String -- Screenshot data
501
612
  def screenshot(type: nil,
502
613
  path: nil,
503
614
  full_page: nil,
@@ -571,30 +682,85 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
571
682
  Puppeteer::QueryHandlerManager.instance
572
683
  end
573
684
 
685
+ private def query_selector_in_isolated_world(selector)
686
+ puppeteer_world = frame.puppeteer_world
687
+ if execution_context == puppeteer_world.execution_context
688
+ return query_handler_manager.detect_query_handler(selector).query_one(self)
689
+ end
690
+
691
+ adopted = puppeteer_world.adopt_handle(self)
692
+ begin
693
+ result = query_handler_manager.detect_query_handler(selector).query_one(adopted)
694
+ return nil unless result
695
+
696
+ if result.execution_context == frame.main_world.execution_context
697
+ return result
698
+ end
699
+
700
+ frame.main_world.transfer_handle(result)
701
+ ensure
702
+ adopted.dispose
703
+ end
704
+ end
705
+
706
+ private def query_selector_all_in_isolated_world(selector)
707
+ puppeteer_world = frame.puppeteer_world
708
+ if execution_context == puppeteer_world.execution_context
709
+ results = query_handler_manager.detect_query_handler(selector).query_all(self)
710
+ return results || []
711
+ end
712
+
713
+ adopted = puppeteer_world.adopt_handle(self)
714
+ begin
715
+ results = query_handler_manager.detect_query_handler(selector).query_all(adopted)
716
+ return [] unless results
717
+
718
+ results.map do |handle|
719
+ if handle.execution_context == frame.main_world.execution_context
720
+ handle
721
+ else
722
+ frame.main_world.transfer_handle(handle)
723
+ end
724
+ end
725
+ ensure
726
+ adopted.dispose
727
+ end
728
+ end
729
+
574
730
  # `$()` in JavaScript.
575
- # @param selector [String]
731
+ # @rbs selector: String -- CSS selector
732
+ # @rbs return: Puppeteer::ElementHandle? -- Matching element or nil
576
733
  def query_selector(selector)
577
- query_handler_manager.detect_query_handler(selector).query_one(self)
734
+ query_selector_in_isolated_world(selector)
578
735
  end
579
736
  alias_method :S, :query_selector
580
737
 
581
738
  # `$$()` in JavaScript.
582
- # @param selector [String]
583
- def query_selector_all(selector)
584
- query_handler_manager.detect_query_handler(selector).query_all(self)
739
+ # @rbs selector: String -- CSS selector
740
+ # @rbs isolate: bool? -- Use isolated world for queries
741
+ # @rbs return: Array[Puppeteer::ElementHandle] -- Matching elements
742
+ def query_selector_all(selector, isolate: nil)
743
+ if isolate == false
744
+ results = query_handler_manager.detect_query_handler(selector).query_all(self)
745
+ return results || []
746
+ end
747
+
748
+ query_selector_all_in_isolated_world(selector)
585
749
  end
586
750
  alias_method :SS, :query_selector_all
587
751
 
588
- class ElementNotFoundError < StandardError
752
+ class ElementNotFoundError < Puppeteer::Error
753
+ # @rbs selector: String -- CSS selector
589
754
  def initialize(selector)
590
755
  super("failed to find element matching selector \"#{selector}\"")
591
756
  end
592
757
  end
593
758
 
594
759
  # `$eval()` in JavaScript.
595
- # @param selector [String]
596
- # @param page_function [String]
597
- # @return [Object]
760
+ # @rbs selector: String -- CSS selector
761
+ # @rbs page_function: String -- Function or expression to evaluate
762
+ # @rbs args: Array[untyped] -- Arguments for evaluation
763
+ # @rbs return: untyped -- Evaluation result
598
764
  def eval_on_selector(selector, page_function, *args)
599
765
  element_handle = query_selector(selector)
600
766
  unless element_handle
@@ -610,9 +776,10 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
610
776
  define_async_method :async_eval_on_selector
611
777
 
612
778
  # `$$eval()` in JavaScript.
613
- # @param selector [String]
614
- # @param page_function [String]
615
- # @return [Object]
779
+ # @rbs selector: String -- CSS selector
780
+ # @rbs page_function: String -- Function or expression to evaluate
781
+ # @rbs args: Array[untyped] -- Arguments for evaluation
782
+ # @rbs return: untyped -- Evaluation result
616
783
  def eval_on_selector_all(selector, page_function, *args)
617
784
  handles = query_handler_manager.detect_query_handler(selector).query_all_array(self)
618
785
  result = handles.evaluate(page_function, *args)
@@ -625,8 +792,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
625
792
  define_async_method :async_eval_on_selector_all
626
793
 
627
794
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
628
- # @param expression [String]
629
- # @return [Array<ElementHandle>]
795
+ # @rbs expression: String -- XPath expression
796
+ # @rbs return: Array[Puppeteer::ElementHandle] -- Matching elements
630
797
  def Sx(expression)
631
798
  param_xpath =
632
799
  if expression.start_with?('//')
@@ -641,8 +808,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
641
808
  define_async_method :async_Sx
642
809
 
643
810
  # in JS, #isIntersectingViewport.
644
- # @param threshold [Float|nil]
645
- # @return [Boolean]
811
+ # @rbs threshold: Numeric? -- Visibility threshold
812
+ # @rbs return: bool -- Whether element intersects viewport
646
813
  def intersecting_viewport?(threshold: nil)
647
814
  option_threshold = threshold || 0
648
815
  js = <<~JAVASCRIPT
@@ -662,7 +829,6 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
662
829
  evaluate(js, option_threshold)
663
830
  end
664
831
 
665
- # @param quad [Array<Point>]
666
832
  private def compute_quad_area(quad)
667
833
  # Compute sum of all directed areas of adjacent triangles
668
834
  # https://en.wikipedia.org/wiki/Polygon#Simple_polygons
@@ -670,6 +836,9 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
670
836
  end
671
837
 
672
838
  # used in AriaQueryHandler
839
+ # @rbs accessible_name: String? -- Accessible name filter
840
+ # @rbs role: String? -- Accessible role filter
841
+ # @rbs return: Hash[String, untyped] -- Accessibility tree result
673
842
  def query_ax_tree(accessible_name: nil, role: nil)
674
843
  @remote_object.query_ax_tree(@client,
675
844
  accessible_name: accessible_name, role: role)
@@ -23,7 +23,7 @@ class Puppeteer::EmulationManager
23
23
  end
24
24
  has_touch = viewport.has_touch?
25
25
 
26
- await_all(
26
+ Puppeteer::AsyncUtils.await_promise_all(
27
27
  @client.async_send_message('Emulation.setDeviceMetricsOverride',
28
28
  mobile: mobile,
29
29
  width: width,
data/lib/puppeteer/env.rb CHANGED
@@ -16,7 +16,7 @@ class Puppeteer::Env
16
16
  end
17
17
 
18
18
  def windows?
19
- RUBY_PLATFORM =~ /mswin|mingw|cygwin/
19
+ !!(RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
20
20
  end
21
21
  end
22
22
 
@@ -1,2 +1,25 @@
1
- # ref: https://github.com/puppeteer/puppeteer/blob/master/src/Errors.ts
2
- class Puppeteer::TimeoutError < StandardError; end
1
+ # ref: https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/common/Errors.ts
2
+
3
+ # The base class for all Puppeteer-specific errors
4
+ class Puppeteer::Error < StandardError
5
+ attr_writer :cause
6
+
7
+ def cause
8
+ return nil if @cause.equal?(self)
9
+
10
+ stack = Thread.current[:puppeteer_cause_stack] ||= []
11
+ return nil if stack.include?(object_id)
12
+
13
+ stack << object_id
14
+ begin
15
+ @cause || super
16
+ ensure
17
+ stack.pop
18
+ Thread.current[:puppeteer_cause_stack] = nil if stack.empty?
19
+ end
20
+ end
21
+ end
22
+
23
+ class Puppeteer::TimeoutError < Puppeteer::Error; end
24
+
25
+ class Puppeteer::TouchError < Puppeteer::Error; end
@@ -20,6 +20,12 @@ module Puppeteer::EventCallbackable
20
20
  @listeners.delete(id)
21
21
  end
22
22
 
23
+ # @param handler [Proc] Listener proc
24
+ def delete_by_handler(handler)
25
+ id, = @listeners.find { |_, listener| listener == handler }
26
+ @listeners.delete(id) if id
27
+ end
28
+
23
29
  # @implement Enumerable#each
24
30
  def each(&block)
25
31
  @listeners.values.each(&block)
@@ -33,6 +39,15 @@ module Puppeteer::EventCallbackable
33
39
 
34
40
  alias_method :on, :add_event_listener
35
41
 
42
+ def off(event_name_or_id, listener = nil, &block)
43
+ listener ||= block
44
+ if listener
45
+ (@event_listeners ||= {})[event_name_or_id]&.delete_by_handler(listener)
46
+ else
47
+ remove_event_listener(event_name_or_id)
48
+ end
49
+ end
50
+
36
51
  def remove_event_listener(*id_args)
37
52
  (@event_listeners ||= {}).each do |event_name, listeners|
38
53
  id_args.each do |id|
@@ -34,6 +34,7 @@ module CDPSessionEmittedEvents ; end
34
34
 
35
35
  {
36
36
  Disconnected: EventsDefinitionUtils.symbol('CDPSession.Disconnected'),
37
+ Ready: EventsDefinitionUtils.symbol('CDPSession.Ready'),
37
38
  }.define_const_into(CDPSessionEmittedEvents)
38
39
 
39
40
  # All the events a Browser may emit.
@@ -159,6 +160,9 @@ module PageEmittedEvents ; end
159
160
  # The object is readonly. See Page#setRequestInterception for intercepting and mutating requests.
160
161
  Request: 'request',
161
162
 
163
+ # Emitted when a request is served from cache.
164
+ RequestServedFromCache: 'requestservedfromcache',
165
+
162
166
  # Emitted when a request fails, for example by timing out.
163
167
  #
164
168
  # Contains a HTTPRequest.