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
data/lib/puppeteer/puppeteer.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
1
3
|
class Puppeteer::Puppeteer
|
|
2
|
-
# @
|
|
3
|
-
# @
|
|
4
|
-
# @
|
|
4
|
+
# @rbs project_root: String -- Project root directory
|
|
5
|
+
# @rbs preferred_revision: String -- Preferred Chromium revision
|
|
6
|
+
# @rbs is_puppeteer_core: bool -- Whether puppeteer-core mode is enabled
|
|
7
|
+
# @rbs return: void -- No return value
|
|
5
8
|
def initialize(project_root:, preferred_revision:, is_puppeteer_core:)
|
|
6
9
|
@project_root = project_root
|
|
7
10
|
@preferred_revision = preferred_revision
|
|
@@ -10,25 +13,30 @@ class Puppeteer::Puppeteer
|
|
|
10
13
|
|
|
11
14
|
class NoViewport ; end
|
|
12
15
|
|
|
13
|
-
# @
|
|
14
|
-
# @
|
|
15
|
-
# @
|
|
16
|
-
# @
|
|
17
|
-
# @
|
|
18
|
-
# @
|
|
19
|
-
# @
|
|
20
|
-
# @
|
|
21
|
-
# @
|
|
22
|
-
# @
|
|
23
|
-
# @
|
|
24
|
-
# @
|
|
25
|
-
# @
|
|
26
|
-
# @
|
|
27
|
-
# @
|
|
28
|
-
# @
|
|
29
|
-
# @
|
|
30
|
-
# @
|
|
31
|
-
# @
|
|
16
|
+
# @rbs product: String? -- Browser product (chrome only)
|
|
17
|
+
# @rbs channel: (String | Symbol)? -- Browser channel
|
|
18
|
+
# @rbs executable_path: String? -- Path to browser executable
|
|
19
|
+
# @rbs ignore_default_args: Array[String]? -- Arguments to exclude from defaults
|
|
20
|
+
# @rbs handle_SIGINT: bool? -- Handle SIGINT in browser process
|
|
21
|
+
# @rbs handle_SIGTERM: bool? -- Handle SIGTERM in browser process
|
|
22
|
+
# @rbs handle_SIGHUP: bool? -- Handle SIGHUP in browser process
|
|
23
|
+
# @rbs timeout: Integer? -- Launch timeout in milliseconds
|
|
24
|
+
# @rbs dumpio: bool? -- Pipe browser stdout/stderr to current process
|
|
25
|
+
# @rbs env: Hash[String, String]? -- Environment variables for browser
|
|
26
|
+
# @rbs pipe: bool? -- Use pipe instead of WebSocket
|
|
27
|
+
# @rbs args: Array[String]? -- Additional browser arguments
|
|
28
|
+
# @rbs user_data_dir: String? -- Path to user data directory
|
|
29
|
+
# @rbs devtools: bool? -- Auto-open DevTools
|
|
30
|
+
# @rbs debugging_port: Integer? -- Remote debugging port
|
|
31
|
+
# @rbs headless: bool? -- Run browser in headless mode
|
|
32
|
+
# @rbs ignore_https_errors: bool? -- Ignore HTTPS errors
|
|
33
|
+
# @rbs network_enabled: bool? -- Enable network domain
|
|
34
|
+
# @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
|
|
35
|
+
# @rbs slow_mo: Integer? -- Delay between operations (ms)
|
|
36
|
+
# @rbs protocol_timeout: Integer? -- CDP protocol timeout in milliseconds
|
|
37
|
+
# @rbs wait_for_initial_page: bool? -- Wait for initial page to load
|
|
38
|
+
# @rbs block: Proc? -- Optional block receiving the browser
|
|
39
|
+
# @rbs return: Puppeteer::Browser -- Browser instance
|
|
32
40
|
def launch(
|
|
33
41
|
product: nil,
|
|
34
42
|
channel: nil,
|
|
@@ -47,9 +55,18 @@ class Puppeteer::Puppeteer
|
|
|
47
55
|
debugging_port: nil,
|
|
48
56
|
headless: nil,
|
|
49
57
|
ignore_https_errors: nil,
|
|
58
|
+
network_enabled: true,
|
|
50
59
|
default_viewport: NoViewport.new,
|
|
51
|
-
slow_mo: nil
|
|
60
|
+
slow_mo: nil,
|
|
61
|
+
protocol_timeout: nil,
|
|
62
|
+
wait_for_initial_page: nil,
|
|
63
|
+
&block
|
|
52
64
|
)
|
|
65
|
+
product = product.to_s if product
|
|
66
|
+
if product && product != 'chrome'
|
|
67
|
+
raise ArgumentError.new("Unsupported product: #{product}. Only 'chrome' is supported.")
|
|
68
|
+
end
|
|
69
|
+
|
|
53
70
|
options = {
|
|
54
71
|
channel: channel&.to_s,
|
|
55
72
|
executable_path: executable_path,
|
|
@@ -67,62 +84,115 @@ class Puppeteer::Puppeteer
|
|
|
67
84
|
debugging_port: debugging_port,
|
|
68
85
|
headless: headless,
|
|
69
86
|
ignore_https_errors: ignore_https_errors,
|
|
87
|
+
network_enabled: network_enabled,
|
|
70
88
|
default_viewport: default_viewport,
|
|
71
89
|
slow_mo: slow_mo,
|
|
90
|
+
protocol_timeout: protocol_timeout,
|
|
91
|
+
wait_for_initial_page: wait_for_initial_page,
|
|
72
92
|
}
|
|
73
93
|
if default_viewport.is_a?(NoViewport)
|
|
74
94
|
options.delete(:default_viewport)
|
|
75
95
|
end
|
|
96
|
+
options.delete(:wait_for_initial_page) if wait_for_initial_page.nil?
|
|
76
97
|
|
|
77
98
|
@product_name = product
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
if async_context?
|
|
100
|
+
browser = launcher.launch(options)
|
|
101
|
+
if block
|
|
102
|
+
begin
|
|
103
|
+
block.call(browser)
|
|
104
|
+
ensure
|
|
105
|
+
browser.close
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
browser
|
|
84
109
|
end
|
|
85
110
|
else
|
|
86
|
-
|
|
111
|
+
runner = Puppeteer::ReactorRunner.new
|
|
112
|
+
begin
|
|
113
|
+
browser = runner.sync { launcher.launch(options) }
|
|
114
|
+
rescue StandardError
|
|
115
|
+
runner.close
|
|
116
|
+
raise
|
|
117
|
+
end
|
|
118
|
+
proxy = Puppeteer::ReactorRunner::Proxy.new(runner, browser, owns_runner: true)
|
|
119
|
+
if block
|
|
120
|
+
begin
|
|
121
|
+
block.call(proxy)
|
|
122
|
+
ensure
|
|
123
|
+
proxy.close
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
proxy
|
|
127
|
+
end
|
|
87
128
|
end
|
|
88
129
|
end
|
|
89
130
|
|
|
90
|
-
# @
|
|
91
|
-
# @
|
|
92
|
-
# @
|
|
93
|
-
# @
|
|
94
|
-
# @
|
|
95
|
-
# @
|
|
96
|
-
# @
|
|
131
|
+
# @rbs browser_ws_endpoint: String? -- Browser WebSocket endpoint
|
|
132
|
+
# @rbs browser_url: String? -- Browser HTTP URL for WebSocket discovery
|
|
133
|
+
# @rbs transport: Puppeteer::WebSocketTransport? -- Pre-connected transport
|
|
134
|
+
# @rbs ignore_https_errors: bool? -- Ignore HTTPS errors
|
|
135
|
+
# @rbs network_enabled: bool? -- Enable network domain
|
|
136
|
+
# @rbs default_viewport: Puppeteer::Viewport? -- Default viewport
|
|
137
|
+
# @rbs slow_mo: Integer? -- Delay between operations (ms)
|
|
138
|
+
# @rbs protocol_timeout: Integer? -- CDP protocol timeout in milliseconds
|
|
139
|
+
# @rbs block: Proc? -- Optional block receiving the browser
|
|
140
|
+
# @rbs return: Puppeteer::Browser -- Browser instance
|
|
97
141
|
def connect(
|
|
98
142
|
browser_ws_endpoint: nil,
|
|
99
143
|
browser_url: nil,
|
|
100
144
|
transport: nil,
|
|
101
145
|
ignore_https_errors: nil,
|
|
146
|
+
network_enabled: true,
|
|
102
147
|
default_viewport: nil,
|
|
103
|
-
slow_mo: nil
|
|
148
|
+
slow_mo: nil,
|
|
149
|
+
protocol_timeout: nil,
|
|
150
|
+
&block
|
|
104
151
|
)
|
|
105
152
|
options = {
|
|
106
153
|
browser_ws_endpoint: browser_ws_endpoint,
|
|
107
154
|
browser_url: browser_url,
|
|
108
155
|
transport: transport,
|
|
109
156
|
ignore_https_errors: ignore_https_errors,
|
|
157
|
+
network_enabled: network_enabled,
|
|
110
158
|
default_viewport: default_viewport,
|
|
111
159
|
slow_mo: slow_mo,
|
|
160
|
+
protocol_timeout: protocol_timeout,
|
|
112
161
|
}.compact
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
162
|
+
if async_context?
|
|
163
|
+
browser = Puppeteer::BrowserConnector.new(options).connect_to_browser
|
|
164
|
+
if block
|
|
165
|
+
begin
|
|
166
|
+
block.call(browser)
|
|
167
|
+
ensure
|
|
168
|
+
browser.disconnect
|
|
169
|
+
end
|
|
170
|
+
else
|
|
171
|
+
browser
|
|
119
172
|
end
|
|
120
173
|
else
|
|
121
|
-
|
|
174
|
+
runner = Puppeteer::ReactorRunner.new
|
|
175
|
+
begin
|
|
176
|
+
browser = runner.sync { Puppeteer::BrowserConnector.new(options).connect_to_browser }
|
|
177
|
+
rescue StandardError
|
|
178
|
+
runner.close
|
|
179
|
+
raise
|
|
180
|
+
end
|
|
181
|
+
proxy = Puppeteer::ReactorRunner::Proxy.new(runner, browser, owns_runner: true)
|
|
182
|
+
if block
|
|
183
|
+
begin
|
|
184
|
+
block.call(proxy)
|
|
185
|
+
ensure
|
|
186
|
+
proxy.disconnect
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
proxy
|
|
190
|
+
end
|
|
122
191
|
end
|
|
123
192
|
end
|
|
124
193
|
|
|
125
|
-
# @
|
|
194
|
+
# @rbs channel: String? -- Browser channel
|
|
195
|
+
# @rbs return: String -- Executable path
|
|
126
196
|
def executable_path(channel: nil)
|
|
127
197
|
launcher.executable_path(channel: channel)
|
|
128
198
|
end
|
|
@@ -136,34 +206,56 @@ class Puppeteer::Puppeteer
|
|
|
136
206
|
)
|
|
137
207
|
end
|
|
138
208
|
|
|
139
|
-
# @return
|
|
209
|
+
# @rbs return: String -- Product name
|
|
140
210
|
def product
|
|
141
211
|
launcher.product
|
|
142
212
|
end
|
|
143
213
|
|
|
144
|
-
def
|
|
214
|
+
private def async_context?
|
|
215
|
+
task = Async::Task.current
|
|
216
|
+
!task.nil?
|
|
217
|
+
rescue RuntimeError, NoMethodError
|
|
218
|
+
false
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @rbs name: String -- Custom query handler name
|
|
222
|
+
# @rbs query_one: String? -- Query-one handler
|
|
223
|
+
# @rbs query_all: String? -- Query-all handler
|
|
224
|
+
# @rbs return: void -- No return value
|
|
225
|
+
def register_custom_query_handler(name:, query_one: nil, query_all: nil)
|
|
145
226
|
unless name =~ /\A[a-zA-Z]+\z/
|
|
146
227
|
raise ArgumentError.new("Custom query handler names may only contain [a-zA-Z]")
|
|
147
228
|
end
|
|
148
229
|
|
|
230
|
+
if query_one.nil? && query_all.nil?
|
|
231
|
+
raise ArgumentError.new('At least one query method must be implemented.')
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
handler = Puppeteer::CustomQueryHandler.new(query_one: query_one, query_all: query_all)
|
|
149
235
|
handler_name = name.to_sym
|
|
150
236
|
if query_handler_manager.query_handlers.key?(handler_name)
|
|
151
237
|
raise ArgumentError.new("A query handler named #{name} already exists")
|
|
152
238
|
end
|
|
153
239
|
|
|
154
|
-
|
|
155
|
-
Puppeteer::QueryHandlerManager.instance.query_handlers[handler_name] = handler
|
|
240
|
+
query_handler_manager.query_handlers[handler_name] = handler
|
|
156
241
|
end
|
|
157
242
|
|
|
158
|
-
|
|
243
|
+
# @rbs name: String -- Custom query handler name
|
|
244
|
+
# @rbs query_one: String? -- Query-one handler
|
|
245
|
+
# @rbs query_all: String? -- Query-all handler
|
|
246
|
+
# @rbs return: untyped -- Block result
|
|
247
|
+
def with_custom_query_handler(name:, query_one: nil, query_all: nil, &block)
|
|
159
248
|
unless name =~ /\A[a-zA-Z]+\z/
|
|
160
249
|
raise ArgumentError.new("Custom query handler names may only contain [a-zA-Z]")
|
|
161
250
|
end
|
|
162
251
|
|
|
252
|
+
if query_one.nil? && query_all.nil?
|
|
253
|
+
raise ArgumentError.new('At least one query method must be implemented.')
|
|
254
|
+
end
|
|
255
|
+
|
|
163
256
|
handler_name = name.to_sym
|
|
164
257
|
|
|
165
258
|
handler = Puppeteer::CustomQueryHandler.new(query_one: query_one, query_all: query_all)
|
|
166
|
-
query_handler_manager = Puppeteer::QueryHandlerManager.instance
|
|
167
259
|
original = query_handler_manager.query_handlers.delete(handler_name)
|
|
168
260
|
query_handler_manager.query_handlers[handler_name] = handler
|
|
169
261
|
begin
|
|
@@ -177,26 +269,41 @@ class Puppeteer::Puppeteer
|
|
|
177
269
|
end
|
|
178
270
|
end
|
|
179
271
|
|
|
180
|
-
# @
|
|
272
|
+
# @rbs name: String -- Custom query handler name
|
|
273
|
+
# @rbs return: void -- No return value
|
|
274
|
+
def unregister_custom_query_handler(name:)
|
|
275
|
+
query_handler_manager.unregister_custom_query_handler(name)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# @rbs return: void -- No return value
|
|
279
|
+
def clear_custom_query_handlers
|
|
280
|
+
query_handler_manager.clear_custom_query_handlers
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# @rbs return: Array[String] -- Registered custom query handler names
|
|
284
|
+
def custom_query_handler_names
|
|
285
|
+
query_handler_manager.custom_query_handler_names
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
private def query_handler_manager
|
|
289
|
+
Puppeteer::QueryHandlerManager.instance
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# @rbs return: Puppeteer::Devices -- Devices registry
|
|
181
293
|
def devices
|
|
182
294
|
Puppeteer::Devices
|
|
183
295
|
end
|
|
184
296
|
|
|
185
|
-
#
|
|
186
|
-
# def errors
|
|
187
|
-
# # ???
|
|
188
|
-
# end
|
|
189
|
-
|
|
190
|
-
# @return [Puppeteer::NetworkConditions]
|
|
297
|
+
# @rbs return: Puppeteer::NetworkConditions -- Network conditions registry
|
|
191
298
|
def network_conditions
|
|
192
299
|
Puppeteer::NetworkConditions
|
|
193
300
|
end
|
|
194
301
|
|
|
195
|
-
# @
|
|
196
|
-
# @
|
|
197
|
-
# @
|
|
198
|
-
# @
|
|
199
|
-
# @return [
|
|
302
|
+
# @rbs args: Array[String]? -- Additional arguments
|
|
303
|
+
# @rbs user_data_dir: String? -- Path to user data directory
|
|
304
|
+
# @rbs devtools: bool? -- Enable DevTools
|
|
305
|
+
# @rbs headless: bool? -- Run browser in headless mode
|
|
306
|
+
# @rbs return: Array[String] -- Default launch arguments
|
|
200
307
|
def default_args(args: nil, user_data_dir: nil, devtools: nil, headless: nil)
|
|
201
308
|
options = {
|
|
202
309
|
args: args,
|
|
@@ -3,6 +3,8 @@ require 'singleton'
|
|
|
3
3
|
class Puppeteer::QueryHandlerManager
|
|
4
4
|
include Singleton
|
|
5
5
|
|
|
6
|
+
DEFAULT_QUERY_HANDLER_NAMES = %i[aria xpath text].freeze
|
|
7
|
+
|
|
6
8
|
def query_handlers
|
|
7
9
|
@query_handlers ||= {
|
|
8
10
|
aria: Puppeteer::AriaQueryHandler.new,
|
|
@@ -11,6 +13,30 @@ class Puppeteer::QueryHandlerManager
|
|
|
11
13
|
}
|
|
12
14
|
end
|
|
13
15
|
|
|
16
|
+
def custom_query_handler_names
|
|
17
|
+
query_handlers.keys.reject { |name| DEFAULT_QUERY_HANDLER_NAMES.include?(name) }.map(&:to_s)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def unregister_custom_query_handler(name)
|
|
21
|
+
handler_name = name.to_sym
|
|
22
|
+
if DEFAULT_QUERY_HANDLER_NAMES.include?(handler_name)
|
|
23
|
+
raise ArgumentError.new("Cannot unregister built-in query handler: #{name}")
|
|
24
|
+
end
|
|
25
|
+
unless query_handlers.key?(handler_name)
|
|
26
|
+
raise ArgumentError.new("Cannot unregister unknown handler: #{name}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
query_handlers.delete(handler_name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clear_custom_query_handlers
|
|
33
|
+
query_handlers.each_key do |name|
|
|
34
|
+
next if DEFAULT_QUERY_HANDLER_NAMES.include?(name)
|
|
35
|
+
|
|
36
|
+
query_handlers.delete(name)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
14
40
|
private def default_handler
|
|
15
41
|
@default_handler ||= Puppeteer::CustomQueryHandler.new(
|
|
16
42
|
query_one: '(element, selector) => element.querySelector(selector)',
|
|
@@ -18,6 +44,65 @@ class Puppeteer::QueryHandlerManager
|
|
|
18
44
|
)
|
|
19
45
|
end
|
|
20
46
|
|
|
47
|
+
private def p_query_handler
|
|
48
|
+
@p_query_handler ||= Puppeteer::CustomQueryHandler.new(
|
|
49
|
+
query_one: <<~JAVASCRIPT,
|
|
50
|
+
(element, selector) => {
|
|
51
|
+
const parts = selector.split('>>>').map((part) => part.trim()).filter(Boolean);
|
|
52
|
+
let roots = [element];
|
|
53
|
+
if (parts.length === 0) return null;
|
|
54
|
+
for (let i = 0; i < parts.length; i++) {
|
|
55
|
+
const part = parts[i];
|
|
56
|
+
const next = [];
|
|
57
|
+
for (const root of roots) {
|
|
58
|
+
const scope = root;
|
|
59
|
+
const elements = scope.querySelectorAll(part);
|
|
60
|
+
for (const node of elements) {
|
|
61
|
+
if (i === parts.length - 1) {
|
|
62
|
+
next.push(node);
|
|
63
|
+
} else if (node.shadowRoot) {
|
|
64
|
+
next.push(node.shadowRoot);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (i === parts.length - 1) {
|
|
69
|
+
return next[0] || null;
|
|
70
|
+
}
|
|
71
|
+
roots = next;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
JAVASCRIPT
|
|
76
|
+
query_all: <<~JAVASCRIPT,
|
|
77
|
+
(element, selector) => {
|
|
78
|
+
const parts = selector.split('>>>').map((part) => part.trim()).filter(Boolean);
|
|
79
|
+
let roots = [element];
|
|
80
|
+
if (parts.length === 0) return [];
|
|
81
|
+
for (let i = 0; i < parts.length; i++) {
|
|
82
|
+
const part = parts[i];
|
|
83
|
+
const next = [];
|
|
84
|
+
for (const root of roots) {
|
|
85
|
+
const scope = root;
|
|
86
|
+
const elements = scope.querySelectorAll(part);
|
|
87
|
+
for (const node of elements) {
|
|
88
|
+
if (i === parts.length - 1) {
|
|
89
|
+
next.push(node);
|
|
90
|
+
} else if (node.shadowRoot) {
|
|
91
|
+
next.push(node.shadowRoot);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (i === parts.length - 1) {
|
|
96
|
+
return next;
|
|
97
|
+
}
|
|
98
|
+
roots = next;
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
JAVASCRIPT
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
21
106
|
private def xpath_handler
|
|
22
107
|
@xpath_handler ||= Puppeteer::CustomQueryHandler.new(
|
|
23
108
|
query_one: <<~JAVASCRIPT,
|
|
@@ -203,9 +288,10 @@ class Puppeteer::QueryHandlerManager
|
|
|
203
288
|
end
|
|
204
289
|
|
|
205
290
|
class Result
|
|
206
|
-
def initialize(query_handler:, selector:)
|
|
291
|
+
def initialize(query_handler:, selector:, polling:)
|
|
207
292
|
@query_handler = query_handler
|
|
208
293
|
@selector = selector
|
|
294
|
+
@polling = polling
|
|
209
295
|
end
|
|
210
296
|
|
|
211
297
|
def query_one(element_handle)
|
|
@@ -213,7 +299,14 @@ class Puppeteer::QueryHandlerManager
|
|
|
213
299
|
end
|
|
214
300
|
|
|
215
301
|
def wait_for(element_or_frame, visible:, hidden:, timeout:)
|
|
216
|
-
@query_handler.wait_for(
|
|
302
|
+
@query_handler.wait_for(
|
|
303
|
+
element_or_frame,
|
|
304
|
+
@selector,
|
|
305
|
+
visible: visible,
|
|
306
|
+
hidden: hidden,
|
|
307
|
+
timeout: timeout,
|
|
308
|
+
polling: @polling,
|
|
309
|
+
)
|
|
217
310
|
end
|
|
218
311
|
|
|
219
312
|
def query_all(element_handle)
|
|
@@ -226,26 +319,29 @@ class Puppeteer::QueryHandlerManager
|
|
|
226
319
|
end
|
|
227
320
|
|
|
228
321
|
def detect_query_handler(selector)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
selector: selector,
|
|
233
|
-
)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
chunk = selector.split("/")
|
|
237
|
-
name = chunk.shift
|
|
238
|
-
updated_selector = chunk.join("/")
|
|
239
|
-
|
|
240
|
-
query_handler = query_handlers[name.to_sym]
|
|
322
|
+
query_handler = nil
|
|
323
|
+
updated_selector = selector
|
|
324
|
+
polling = selector.include?(':') ? 'raf' : 'mutation'
|
|
241
325
|
|
|
242
|
-
|
|
243
|
-
|
|
326
|
+
if (match = selector.match(/^([a-zA-Z][\w-]*)([=\/])(.*)$/))
|
|
327
|
+
name = match[1]
|
|
328
|
+
updated_selector = match[3]
|
|
329
|
+
query_handler = query_handlers[name.to_sym]
|
|
330
|
+
unless query_handler
|
|
331
|
+
raise ArgumentError.new("Query set to use \"#{name}\", but no query handler of that name was found")
|
|
332
|
+
end
|
|
333
|
+
polling = name == 'aria' ? 'raf' : 'mutation'
|
|
334
|
+
elsif selector.include?('>>>')
|
|
335
|
+
query_handler = p_query_handler
|
|
336
|
+
polling = 'raf'
|
|
337
|
+
else
|
|
338
|
+
query_handler = default_handler
|
|
244
339
|
end
|
|
245
340
|
|
|
246
341
|
Result.new(
|
|
247
342
|
query_handler: query_handler,
|
|
248
343
|
selector: updated_selector,
|
|
344
|
+
polling: polling,
|
|
249
345
|
)
|
|
250
346
|
end
|
|
251
347
|
end
|