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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -3
- data/AGENTS.md +169 -0
- data/CLAUDE/README.md +41 -0
- data/CLAUDE/architecture.md +253 -0
- data/CLAUDE/cdp_protocol.md +230 -0
- data/CLAUDE/concurrency.md +216 -0
- data/CLAUDE/porting_puppeteer.md +575 -0
- data/CLAUDE/rbs_type_checking.md +101 -0
- data/CLAUDE/spec_migration_plans.md +1041 -0
- data/CLAUDE/testing.md +278 -0
- data/CLAUDE.md +242 -0
- data/README.md +8 -0
- data/Rakefile +7 -0
- data/Steepfile +28 -0
- data/docs/api_coverage.md +105 -56
- data/lib/puppeteer/aria_query_handler.rb +3 -2
- data/lib/puppeteer/async_utils.rb +214 -0
- data/lib/puppeteer/browser.rb +98 -56
- data/lib/puppeteer/browser_connector.rb +18 -3
- data/lib/puppeteer/browser_context.rb +196 -3
- data/lib/puppeteer/browser_runner.rb +18 -10
- data/lib/puppeteer/cdp_session.rb +67 -23
- data/lib/puppeteer/chrome_target_manager.rb +65 -40
- data/lib/puppeteer/connection.rb +55 -36
- data/lib/puppeteer/console_message.rb +9 -1
- data/lib/puppeteer/console_patch.rb +47 -0
- data/lib/puppeteer/css_coverage.rb +5 -3
- data/lib/puppeteer/custom_query_handler.rb +80 -33
- data/lib/puppeteer/define_async_method.rb +31 -37
- data/lib/puppeteer/dialog.rb +47 -14
- data/lib/puppeteer/element_handle.rb +231 -62
- data/lib/puppeteer/emulation_manager.rb +1 -1
- data/lib/puppeteer/env.rb +1 -1
- data/lib/puppeteer/errors.rb +25 -2
- data/lib/puppeteer/event_callbackable.rb +15 -0
- data/lib/puppeteer/events.rb +4 -0
- data/lib/puppeteer/execution_context.rb +148 -3
- data/lib/puppeteer/file_chooser.rb +6 -0
- data/lib/puppeteer/frame.rb +162 -91
- data/lib/puppeteer/frame_manager.rb +69 -48
- data/lib/puppeteer/http_request.rb +114 -38
- data/lib/puppeteer/http_response.rb +24 -7
- data/lib/puppeteer/isolated_world.rb +64 -41
- data/lib/puppeteer/js_coverage.rb +5 -3
- data/lib/puppeteer/js_handle.rb +58 -16
- data/lib/puppeteer/keyboard.rb +30 -17
- data/lib/puppeteer/launcher/browser_options.rb +3 -1
- data/lib/puppeteer/launcher/chrome.rb +8 -5
- data/lib/puppeteer/launcher/launch_options.rb +7 -2
- data/lib/puppeteer/launcher.rb +4 -8
- data/lib/puppeteer/lifecycle_watcher.rb +38 -22
- data/lib/puppeteer/mouse.rb +273 -64
- data/lib/puppeteer/network_event_manager.rb +7 -0
- data/lib/puppeteer/network_manager.rb +393 -112
- data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
- data/lib/puppeteer/page.rb +568 -226
- data/lib/puppeteer/puppeteer.rb +171 -64
- data/lib/puppeteer/query_handler_manager.rb +112 -16
- data/lib/puppeteer/reactor_runner.rb +247 -0
- data/lib/puppeteer/remote_object.rb +127 -47
- data/lib/puppeteer/target.rb +74 -27
- data/lib/puppeteer/task_manager.rb +3 -1
- data/lib/puppeteer/timeout_helper.rb +6 -10
- data/lib/puppeteer/touch_handle.rb +39 -0
- data/lib/puppeteer/touch_screen.rb +72 -22
- data/lib/puppeteer/tracing.rb +3 -3
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +264 -101
- data/lib/puppeteer/web_socket.rb +2 -2
- data/lib/puppeteer/web_socket_transport.rb +91 -27
- data/lib/puppeteer/web_worker.rb +175 -0
- data/lib/puppeteer.rb +20 -4
- data/puppeteer-ruby.gemspec +15 -11
- data/sig/_external.rbs +8 -0
- data/sig/_supplementary.rbs +314 -0
- data/sig/puppeteer/browser.rbs +166 -0
- data/sig/puppeteer/cdp_session.rbs +64 -0
- data/sig/puppeteer/dialog.rbs +41 -0
- data/sig/puppeteer/element_handle.rbs +305 -0
- data/sig/puppeteer/execution_context.rbs +87 -0
- data/sig/puppeteer/frame.rbs +226 -0
- data/sig/puppeteer/http_request.rbs +214 -0
- data/sig/puppeteer/http_response.rbs +89 -0
- data/sig/puppeteer/js_handle.rbs +64 -0
- data/sig/puppeteer/keyboard.rbs +40 -0
- data/sig/puppeteer/mouse.rbs +113 -0
- data/sig/puppeteer/page.rbs +515 -0
- data/sig/puppeteer/puppeteer.rbs +98 -0
- data/sig/puppeteer/remote_object.rbs +78 -0
- data/sig/puppeteer/touch_handle.rbs +21 -0
- data/sig/puppeteer/touch_screen.rbs +35 -0
- data/sig/puppeteer/web_worker.rbs +83 -0
- metadata +116 -45
- data/CHANGELOG.md +0 -397
- data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
- data/lib/puppeteer/firefox_target_manager.rb +0 -157
- 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
|
-
# @
|
|
12
|
-
# @
|
|
13
|
-
# @
|
|
14
|
-
# @
|
|
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(
|
|
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 <
|
|
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
|
|
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 <
|
|
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 <
|
|
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
|
-
# @
|
|
214
|
-
# @
|
|
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
|
-
# @
|
|
220
|
-
# @return
|
|
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
|
-
#
|
|
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
|
-
# @
|
|
294
|
-
# @return [
|
|
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
|
-
# @
|
|
321
|
-
# @
|
|
322
|
-
# @
|
|
323
|
-
# @
|
|
324
|
-
|
|
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
|
-
|
|
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
|
-
# @
|
|
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
|
-
# @
|
|
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
|
-
# @
|
|
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
|
-
|
|
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
|
-
# @
|
|
459
|
-
# @
|
|
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
|
-
# @
|
|
468
|
-
# @
|
|
469
|
-
# @
|
|
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
|
|
573
|
+
@page.keyboard.press(key, delay: delay)
|
|
473
574
|
end
|
|
474
575
|
|
|
475
576
|
define_async_method :async_press
|
|
476
577
|
|
|
477
|
-
# @return
|
|
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
|
|
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
|
-
# @
|
|
731
|
+
# @rbs selector: String -- CSS selector
|
|
732
|
+
# @rbs return: Puppeteer::ElementHandle? -- Matching element or nil
|
|
576
733
|
def query_selector(selector)
|
|
577
|
-
|
|
734
|
+
query_selector_in_isolated_world(selector)
|
|
578
735
|
end
|
|
579
736
|
alias_method :S, :query_selector
|
|
580
737
|
|
|
581
738
|
# `$$()` in JavaScript.
|
|
582
|
-
# @
|
|
583
|
-
|
|
584
|
-
|
|
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 <
|
|
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
|
-
# @
|
|
596
|
-
# @
|
|
597
|
-
# @
|
|
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
|
-
# @
|
|
614
|
-
# @
|
|
615
|
-
# @
|
|
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
|
-
# @
|
|
629
|
-
# @return [
|
|
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
|
-
# @
|
|
645
|
-
# @return
|
|
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)
|
data/lib/puppeteer/env.rb
CHANGED
data/lib/puppeteer/errors.rb
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
|
-
# ref: https://github.com/puppeteer/puppeteer/blob/
|
|
2
|
-
|
|
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|
|
data/lib/puppeteer/events.rb
CHANGED
|
@@ -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.
|