puppeteer-ruby 0.45.6 → 0.50.0.alpha6

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/AGENTS.md +170 -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 +605 -0
  9. data/CLAUDE/rbs_type_checking.md +101 -0
  10. data/CLAUDE/spec_migration_plans.md +1039 -0
  11. data/CLAUDE/testing.md +278 -0
  12. data/CLAUDE.md +242 -0
  13. data/README.md +9 -0
  14. data/Rakefile +7 -0
  15. data/Steepfile +28 -0
  16. data/docs/api_coverage.md +106 -57
  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 +236 -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 +177 -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 +77 -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/locators.rb +733 -0
  54. data/lib/puppeteer/mouse.rb +273 -64
  55. data/lib/puppeteer/network_event_manager.rb +7 -0
  56. data/lib/puppeteer/network_manager.rb +393 -112
  57. data/lib/puppeteer/p_query_handler.rb +367 -0
  58. data/lib/puppeteer/p_selector_parser.rb +241 -0
  59. data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
  60. data/lib/puppeteer/page.rb +583 -226
  61. data/lib/puppeteer/puppeteer.rb +171 -64
  62. data/lib/puppeteer/query_handler_manager.rb +66 -16
  63. data/lib/puppeteer/reactor_runner.rb +247 -0
  64. data/lib/puppeteer/remote_object.rb +127 -47
  65. data/lib/puppeteer/target.rb +74 -27
  66. data/lib/puppeteer/task_manager.rb +3 -1
  67. data/lib/puppeteer/timeout_helper.rb +6 -10
  68. data/lib/puppeteer/touch_handle.rb +39 -0
  69. data/lib/puppeteer/touch_screen.rb +72 -22
  70. data/lib/puppeteer/tracing.rb +3 -3
  71. data/lib/puppeteer/version.rb +1 -1
  72. data/lib/puppeteer/wait_task.rb +264 -101
  73. data/lib/puppeteer/web_socket.rb +2 -2
  74. data/lib/puppeteer/web_socket_transport.rb +91 -27
  75. data/lib/puppeteer/web_worker.rb +175 -0
  76. data/lib/puppeteer.rb +23 -4
  77. data/puppeteer-ruby.gemspec +15 -11
  78. data/sig/_external.rbs +8 -0
  79. data/sig/_supplementary.rbs +314 -0
  80. data/sig/puppeteer/browser.rbs +166 -0
  81. data/sig/puppeteer/cdp_session.rbs +64 -0
  82. data/sig/puppeteer/dialog.rbs +41 -0
  83. data/sig/puppeteer/element_handle.rbs +308 -0
  84. data/sig/puppeteer/execution_context.rbs +87 -0
  85. data/sig/puppeteer/frame.rbs +233 -0
  86. data/sig/puppeteer/http_request.rbs +214 -0
  87. data/sig/puppeteer/http_response.rbs +89 -0
  88. data/sig/puppeteer/js_handle.rbs +64 -0
  89. data/sig/puppeteer/keyboard.rbs +40 -0
  90. data/sig/puppeteer/locators.rbs +222 -0
  91. data/sig/puppeteer/mouse.rbs +113 -0
  92. data/sig/puppeteer/p_query_handler.rbs +73 -0
  93. data/sig/puppeteer/p_selector_parser.rbs +31 -0
  94. data/sig/puppeteer/page.rbs +522 -0
  95. data/sig/puppeteer/puppeteer.rbs +98 -0
  96. data/sig/puppeteer/remote_object.rbs +78 -0
  97. data/sig/puppeteer/touch_handle.rbs +21 -0
  98. data/sig/puppeteer/touch_screen.rbs +35 -0
  99. data/sig/puppeteer/web_worker.rbs +83 -0
  100. metadata +122 -45
  101. data/CHANGELOG.md +0 -397
  102. data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
  103. data/lib/puppeteer/firefox_target_manager.rb +0 -157
  104. 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,50 @@ 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: Puppeteer::Locator -- Locator for this element
156
+ def as_locator
157
+ Puppeteer::NodeLocator.create_from_handle(@frame, self)
158
+ end
159
+
160
+ # @rbs return: bool -- Whether element is visible
161
+ def visible?
162
+ check_visibility(true)
163
+ end
164
+
165
+ # @rbs return: bool -- Whether element is hidden
166
+ def hidden?
167
+ check_visibility(false)
168
+ end
169
+
170
+ # @rbs visible: bool -- Expected visibility state
171
+ # @rbs return: bool -- Whether visibility matches
172
+ private def check_visibility(visible)
173
+ js = <<~JAVASCRIPT
174
+ (node, visible) => {
175
+ if (!node) return visible === false;
176
+ const element =
177
+ node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
178
+ if (!element) return visible === false;
179
+ const style = window.getComputedStyle(element);
180
+ const rect = element.getBoundingClientRect();
181
+ const isVisible =
182
+ style &&
183
+ style.visibility !== 'hidden' &&
184
+ style.visibility !== 'collapse' &&
185
+ rect.width !== 0 &&
186
+ rect.height !== 0;
187
+ return visible === isVisible;
188
+ }
189
+ JAVASCRIPT
190
+ evaluate(js, visible)
191
+ end
192
+
193
+ # @rbs return: Puppeteer::Frame? -- Frame that owns this element
140
194
  def content_frame
141
195
  node_info = @remote_object.node_info(@client)
142
196
  frame_id = node_info['node']['frameId']
@@ -147,8 +201,9 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
147
201
  end
148
202
  end
149
203
 
150
- class ScrollIntoViewError < StandardError; end
204
+ class ScrollIntoViewError < Puppeteer::Error; end
151
205
 
206
+ # @rbs return: void -- No return value
152
207
  def scroll_into_view_if_needed
153
208
  js = <<~JAVASCRIPT
154
209
  async(element) => {
@@ -165,7 +220,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
165
220
  end
166
221
  begin
167
222
  @remote_object.scroll_into_view_if_needed(@client)
168
- rescue => err
223
+ rescue
169
224
  # Fallback to Element.scrollIntoView if DOM.scrollIntoViewIfNeeded is not supported
170
225
  js = <<~JAVASCRIPT
171
226
  async (element, pageJavascriptEnabled) => {
@@ -198,26 +253,27 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
198
253
  sleep 0.16
199
254
  end
200
255
 
201
- class ElementNotVisibleError < StandardError
256
+ class ElementNotVisibleError < Puppeteer::Error
202
257
  def initialize
203
258
  super("Node is either not visible or not an HTMLElement")
204
259
  end
205
260
  end
206
261
 
207
- class ElementNotClickableError < StandardError
262
+ class ElementNotClickableError < Puppeteer::Error
208
263
  def initialize
209
264
  super("Node is either not clickable or not an HTMLElement")
210
265
  end
211
266
  end
212
267
 
213
- # @param quad [Array<Array<Point>>]]
214
- # @param offset [Point]
268
+ # @rbs quad: Array[Point] -- Quad points
269
+ # @rbs offset: Point -- Offset to apply
270
+ # @rbs return: Array[Point] -- Offset quad points
215
271
  private def apply_offsets_to_quad(quad, offset)
216
272
  quad.map { |part| part + offset }
217
273
  end
218
274
 
219
- # @param frame [Puppeteer::Frame]
220
- # @return [Point]
275
+ # @rbs frame: Puppeteer::Frame -- Frame to calculate offsets for
276
+ # @rbs return: Point -- Calculated offset
221
277
  private def oopif_offsets(frame)
222
278
  offset = Point.new(x: 0, y: 0)
223
279
  while frame.parent_frame
@@ -235,6 +291,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
235
291
  offset
236
292
  end
237
293
 
294
+ # @rbs offset: Puppeteer::ElementHandle::Offset | Hash[Symbol, Numeric] | nil -- Click offset
295
+ # @rbs return: Puppeteer::ElementHandle::Point -- Clickable point
238
296
  def clickable_point(offset = nil)
239
297
  offset_param = Offset.from(offset)
240
298
 
@@ -258,7 +316,7 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
258
316
  end
259
317
 
260
318
  # Filter out quads that have too small area to click into.
261
- # Fallback to `layoutViewport` in case of using Firefox.
319
+ # Prefer cssLayoutViewport when available.
262
320
  layout_viewport = layout_metrics["cssLayoutViewport"] || layout_metrics["layoutViewport"]
263
321
  client_width = layout_viewport["clientWidth"]
264
322
  client_height = layout_viewport["clientHeight"]
@@ -290,18 +348,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
290
348
  quads.first.reduce(:+) / 4
291
349
  end
292
350
 
293
- # @param quad [Array<number>]
294
- # @return [Array<Point>]
351
+ # @rbs quad: Array[Numeric] -- Protocol quad coordinates
352
+ # @rbs return: Array[Point] -- Point array
295
353
  private def from_protocol_quad(quad)
296
354
  quad.each_slice(2).map do |x, y|
297
355
  Point.new(x: x, y: y)
298
356
  end
299
357
  end
300
358
 
301
- # @param quad [Array<Point>]
302
- # @param width [number]
303
- # @param height [number]
304
- # @return [Array<Point>]
305
359
  private def intersect_quad_with_viewport(quad, width, height)
306
360
  quad.map do |point|
307
361
  Point.new(
@@ -311,30 +365,67 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
311
365
  end
312
366
  end
313
367
 
368
+ # @rbs return: void -- No return value
314
369
  def hover
315
370
  scroll_into_view_if_needed
316
371
  point = clickable_point
317
372
  @page.mouse.move(point.x, point.y)
318
373
  end
319
374
 
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)
375
+ # @rbs delay: Numeric? -- Delay between down and up (ms)
376
+ # @rbs button: String? -- Mouse button
377
+ # @rbs click_count: Integer? -- Deprecated: use count (click_count only sets clickCount)
378
+ # @rbs count: Integer? -- Number of clicks to perform
379
+ # @rbs offset: Puppeteer::ElementHandle::Offset | Hash[Symbol, Numeric] | nil -- Click offset
380
+ # @rbs return: void -- No return value
381
+ def click(delay: nil, button: nil, click_count: nil, count: nil, offset: nil)
325
382
  scroll_into_view_if_needed
326
383
  point = clickable_point(offset)
327
- @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count)
384
+ @page.mouse.click(point.x, point.y, delay: delay, button: button, click_count: click_count, count: count)
328
385
  end
329
386
 
330
387
  define_async_method :async_click
331
388
 
332
- class DragInterceptionNotEnabledError < StandardError
389
+ # @rbs return: Puppeteer::TouchHandle -- Touch handle
390
+ def touch_start
391
+ scroll_into_view_if_needed
392
+ point = clickable_point
393
+ @page.touchscreen.touch_start(point.x, point.y)
394
+ end
395
+
396
+ define_async_method :async_touch_start
397
+
398
+ # @rbs touch: Puppeteer::TouchHandle? -- Optional touch handle
399
+ # @rbs return: void -- No return value
400
+ def touch_move(touch = nil)
401
+ scroll_into_view_if_needed
402
+ point = clickable_point
403
+ if touch
404
+ touch.move(point.x, point.y)
405
+ else
406
+ @page.touchscreen.touch_move(point.x, point.y)
407
+ end
408
+ end
409
+
410
+ define_async_method :async_touch_move
411
+
412
+ # @rbs return: void -- No return value
413
+ def touch_end
414
+ scroll_into_view_if_needed
415
+ @page.touchscreen.touch_end
416
+ end
417
+
418
+ define_async_method :async_touch_end
419
+
420
+ class DragInterceptionNotEnabledError < Puppeteer::Error
333
421
  def initialize
334
422
  super('Drag Interception is not enabled!')
335
423
  end
336
424
  end
337
425
 
426
+ # @rbs x: Numeric -- Drag end X coordinate
427
+ # @rbs y: Numeric -- Drag end Y coordinate
428
+ # @rbs return: void -- No return value
338
429
  def drag(x:, y:)
339
430
  unless @page.drag_interception_enabled?
340
431
  raise DragInterceptionNotEnabledError.new
@@ -344,25 +435,33 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
344
435
  @page.mouse.drag(start, Point.new(x: x, y: y))
345
436
  end
346
437
 
438
+ # @rbs data: Hash[String, untyped] -- Drag data payload
439
+ # @rbs return: void -- No return value
347
440
  def drag_enter(data)
348
441
  scroll_into_view_if_needed
349
442
  target = clickable_point
350
443
  @page.mouse.drag_enter(target, data)
351
444
  end
352
445
 
446
+ # @rbs data: Hash[String, untyped] -- Drag data payload
447
+ # @rbs return: void -- No return value
353
448
  def drag_over(data)
354
449
  scroll_into_view_if_needed
355
450
  target = clickable_point
356
451
  @page.mouse.drag_over(target, data)
357
452
  end
358
453
 
454
+ # @rbs data: Hash[String, untyped] -- Drag data payload
455
+ # @rbs return: void -- No return value
359
456
  def drop(data)
360
457
  scroll_into_view_if_needed
361
458
  target = clickable_point
362
459
  @page.mouse.drop(target, data)
363
460
  end
364
461
 
365
- # @param target [ElementHandle]
462
+ # @rbs target: Puppeteer::ElementHandle -- Drop target element
463
+ # @rbs delay: Numeric? -- Delay before dropping (ms)
464
+ # @rbs return: void -- No return value
366
465
  def drag_and_drop(target, delay: nil)
367
466
  scroll_into_view_if_needed
368
467
  start_point = clickable_point
@@ -370,7 +469,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
370
469
  @page.mouse.drag_and_drop(start_point, target_point, delay: delay)
371
470
  end
372
471
 
373
- # @return [Array<String>]
472
+ # @rbs values: Array[String] -- Option values to select
473
+ # @rbs return: Array[String] -- Selected values
374
474
  def select(*values)
375
475
  if nonstring = values.find { |value| !value.is_a?(String) }
376
476
  raise ArgumentError.new("Values must be strings. Found value \"#{nonstring}\" of type \"#{nonstring.class}\"")
@@ -411,7 +511,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
411
511
  evaluate(fn, values)
412
512
  end
413
513
 
414
- # @param file_paths [Array<String>]
514
+ # @rbs file_paths: Array[String] -- Files to upload
515
+ # @rbs return: void -- No return value
415
516
  def upload_file(*file_paths)
416
517
  is_multiple = evaluate("el => el.multiple")
417
518
  if !is_multiple && file_paths.length >= 2
@@ -433,12 +534,14 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
433
534
  element.dispatchEvent(new Event('change', { bubbles: true }));
434
535
  }
435
536
  JAVASCRIPT
436
- await evaluate(fn)
537
+ evaluate(fn)
437
538
  else
438
539
  @remote_object.set_file_input_files(@client, file_paths.map { |path| File.expand_path(path) }, backend_node_id)
439
540
  end
440
541
  end
441
542
 
543
+ # @rbs block: Proc? -- Optional block for Object#tap usage
544
+ # @rbs return: Puppeteer::ElementHandle | nil -- Element handle or nil
442
545
  def tap(&block)
443
546
  return super(&block) if block
444
547
 
@@ -449,14 +552,16 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
449
552
 
450
553
  define_async_method :async_tap
451
554
 
555
+ # @rbs return: void -- No return value
452
556
  def focus
453
557
  evaluate('element => element.focus()')
454
558
  end
455
559
 
456
560
  define_async_method :async_focus
457
561
 
458
- # @param text [String]
459
- # @param delay [number|nil]
562
+ # @rbs text: String -- Text to type
563
+ # @rbs delay: Numeric? -- Delay between key presses (ms)
564
+ # @rbs return: void -- No return value
460
565
  def type_text(text, delay: nil)
461
566
  focus
462
567
  @page.keyboard.type_text(text, delay: delay)
@@ -464,17 +569,18 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
464
569
 
465
570
  define_async_method :async_type_text
466
571
 
467
- # @param key [String]
468
- # @param text [String]
469
- # @param delay [number|nil]
572
+ # @rbs key: String -- Key name
573
+ # @rbs delay: Numeric? -- Delay between key events (ms)
574
+ # @rbs text: String? -- Text to input
575
+ # @rbs return: void -- No return value
470
576
  def press(key, delay: nil, text: nil)
471
577
  focus
472
- @page.keyboard.press(key, delay: delay, text: text)
578
+ @page.keyboard.press(key, delay: delay)
473
579
  end
474
580
 
475
581
  define_async_method :async_press
476
582
 
477
- # @return [BoundingBox|nil]
583
+ # @rbs return: Puppeteer::ElementHandle::BoundingBox? -- Bounding box or nil
478
584
  def bounding_box
479
585
  if_present(box_model) do |result_model|
480
586
  offset = oopif_offsets(@frame)
@@ -491,13 +597,23 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
491
597
  end
492
598
  end
493
599
 
494
- # @return [BoxModel|nil]
600
+ # @rbs return: Puppeteer::ElementHandle::BoxModel? -- Box model or nil
495
601
  def box_model
496
602
  if_present(@remote_object.box_model(@client)) do |result|
497
603
  BoxModel.new(result['model'], offset: oopif_offsets(@frame))
498
604
  end
499
605
  end
500
606
 
607
+ # @rbs type: String? -- Image format
608
+ # @rbs path: String? -- File path to save
609
+ # @rbs full_page: bool? -- Capture full page
610
+ # @rbs clip: Hash[Symbol, Numeric]? -- Clip rectangle
611
+ # @rbs quality: Integer? -- JPEG quality
612
+ # @rbs omit_background: bool? -- Omit background for PNG
613
+ # @rbs encoding: String? -- Encoding (base64 or binary)
614
+ # @rbs capture_beyond_viewport: bool? -- Capture beyond viewport
615
+ # @rbs from_surface: bool? -- Capture from surface
616
+ # @rbs return: String -- Screenshot data
501
617
  def screenshot(type: nil,
502
618
  path: nil,
503
619
  full_page: nil,
@@ -571,30 +687,85 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
571
687
  Puppeteer::QueryHandlerManager.instance
572
688
  end
573
689
 
690
+ private def query_selector_in_isolated_world(selector)
691
+ puppeteer_world = frame.puppeteer_world
692
+ if execution_context == puppeteer_world.execution_context
693
+ return query_handler_manager.detect_query_handler(selector).query_one(self)
694
+ end
695
+
696
+ adopted = puppeteer_world.adopt_handle(self)
697
+ begin
698
+ result = query_handler_manager.detect_query_handler(selector).query_one(adopted)
699
+ return nil unless result
700
+
701
+ if result.execution_context == frame.main_world.execution_context
702
+ return result
703
+ end
704
+
705
+ frame.main_world.transfer_handle(result)
706
+ ensure
707
+ adopted.dispose
708
+ end
709
+ end
710
+
711
+ private def query_selector_all_in_isolated_world(selector)
712
+ puppeteer_world = frame.puppeteer_world
713
+ if execution_context == puppeteer_world.execution_context
714
+ results = query_handler_manager.detect_query_handler(selector).query_all(self)
715
+ return results || []
716
+ end
717
+
718
+ adopted = puppeteer_world.adopt_handle(self)
719
+ begin
720
+ results = query_handler_manager.detect_query_handler(selector).query_all(adopted)
721
+ return [] unless results
722
+
723
+ results.map do |handle|
724
+ if handle.execution_context == frame.main_world.execution_context
725
+ handle
726
+ else
727
+ frame.main_world.transfer_handle(handle)
728
+ end
729
+ end
730
+ ensure
731
+ adopted.dispose
732
+ end
733
+ end
734
+
574
735
  # `$()` in JavaScript.
575
- # @param selector [String]
736
+ # @rbs selector: String -- CSS selector
737
+ # @rbs return: Puppeteer::ElementHandle? -- Matching element or nil
576
738
  def query_selector(selector)
577
- query_handler_manager.detect_query_handler(selector).query_one(self)
739
+ query_selector_in_isolated_world(selector)
578
740
  end
579
741
  alias_method :S, :query_selector
580
742
 
581
743
  # `$$()` in JavaScript.
582
- # @param selector [String]
583
- def query_selector_all(selector)
584
- query_handler_manager.detect_query_handler(selector).query_all(self)
744
+ # @rbs selector: String -- CSS selector
745
+ # @rbs isolate: bool? -- Use isolated world for queries
746
+ # @rbs return: Array[Puppeteer::ElementHandle] -- Matching elements
747
+ def query_selector_all(selector, isolate: nil)
748
+ if isolate == false
749
+ results = query_handler_manager.detect_query_handler(selector).query_all(self)
750
+ return results || []
751
+ end
752
+
753
+ query_selector_all_in_isolated_world(selector)
585
754
  end
586
755
  alias_method :SS, :query_selector_all
587
756
 
588
- class ElementNotFoundError < StandardError
757
+ class ElementNotFoundError < Puppeteer::Error
758
+ # @rbs selector: String -- CSS selector
589
759
  def initialize(selector)
590
760
  super("failed to find element matching selector \"#{selector}\"")
591
761
  end
592
762
  end
593
763
 
594
764
  # `$eval()` in JavaScript.
595
- # @param selector [String]
596
- # @param page_function [String]
597
- # @return [Object]
765
+ # @rbs selector: String -- CSS selector
766
+ # @rbs page_function: String -- Function or expression to evaluate
767
+ # @rbs args: Array[untyped] -- Arguments for evaluation
768
+ # @rbs return: untyped -- Evaluation result
598
769
  def eval_on_selector(selector, page_function, *args)
599
770
  element_handle = query_selector(selector)
600
771
  unless element_handle
@@ -610,9 +781,10 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
610
781
  define_async_method :async_eval_on_selector
611
782
 
612
783
  # `$$eval()` in JavaScript.
613
- # @param selector [String]
614
- # @param page_function [String]
615
- # @return [Object]
784
+ # @rbs selector: String -- CSS selector
785
+ # @rbs page_function: String -- Function or expression to evaluate
786
+ # @rbs args: Array[untyped] -- Arguments for evaluation
787
+ # @rbs return: untyped -- Evaluation result
616
788
  def eval_on_selector_all(selector, page_function, *args)
617
789
  handles = query_handler_manager.detect_query_handler(selector).query_all_array(self)
618
790
  result = handles.evaluate(page_function, *args)
@@ -625,8 +797,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
625
797
  define_async_method :async_eval_on_selector_all
626
798
 
627
799
  # `$x()` in JavaScript. $ is not allowed to use as a method name in Ruby.
628
- # @param expression [String]
629
- # @return [Array<ElementHandle>]
800
+ # @rbs expression: String -- XPath expression
801
+ # @rbs return: Array[Puppeteer::ElementHandle] -- Matching elements
630
802
  def Sx(expression)
631
803
  param_xpath =
632
804
  if expression.start_with?('//')
@@ -641,8 +813,8 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
641
813
  define_async_method :async_Sx
642
814
 
643
815
  # in JS, #isIntersectingViewport.
644
- # @param threshold [Float|nil]
645
- # @return [Boolean]
816
+ # @rbs threshold: Numeric? -- Visibility threshold
817
+ # @rbs return: bool -- Whether element intersects viewport
646
818
  def intersecting_viewport?(threshold: nil)
647
819
  option_threshold = threshold || 0
648
820
  js = <<~JAVASCRIPT
@@ -662,7 +834,6 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
662
834
  evaluate(js, option_threshold)
663
835
  end
664
836
 
665
- # @param quad [Array<Point>]
666
837
  private def compute_quad_area(quad)
667
838
  # Compute sum of all directed areas of adjacent triangles
668
839
  # https://en.wikipedia.org/wiki/Polygon#Simple_polygons
@@ -670,6 +841,9 @@ class Puppeteer::ElementHandle < Puppeteer::JSHandle
670
841
  end
671
842
 
672
843
  # used in AriaQueryHandler
844
+ # @rbs accessible_name: String? -- Accessible name filter
845
+ # @rbs role: String? -- Accessible role filter
846
+ # @rbs return: Hash[String, untyped] -- Accessibility tree result
673
847
  def query_ax_tree(accessible_name: nil, role: nil)
674
848
  @remote_object.query_ax_tree(@client,
675
849
  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.