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