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
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'console/event/failure'
|
|
5
|
+
require 'console/terminal/formatter/failure'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
# Console is optional; skip patching if unavailable.
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Console
|
|
11
|
+
module Event
|
|
12
|
+
class Failure
|
|
13
|
+
unless method_defined?(:extract_without_cycle_guard)
|
|
14
|
+
alias extract_without_cycle_guard extract
|
|
15
|
+
|
|
16
|
+
private def extract(exception, hash)
|
|
17
|
+
seen = Thread.current[:console_failure_seen] ||= {}
|
|
18
|
+
return if seen[exception.object_id]
|
|
19
|
+
|
|
20
|
+
seen[exception.object_id] = true
|
|
21
|
+
begin
|
|
22
|
+
extract_without_cycle_guard(exception, hash)
|
|
23
|
+
ensure
|
|
24
|
+
seen.delete(exception.object_id)
|
|
25
|
+
Thread.current[:console_failure_seen] = nil if seen.empty?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Terminal
|
|
33
|
+
module Formatter
|
|
34
|
+
class Failure
|
|
35
|
+
unless method_defined?(:format_without_nil_guard)
|
|
36
|
+
alias format_without_nil_guard format
|
|
37
|
+
|
|
38
|
+
def format(event, stream, prefix: nil, verbose: false, **options)
|
|
39
|
+
event = event.dup
|
|
40
|
+
event[:message] = event[:message].to_s
|
|
41
|
+
format_without_nil_guard(event, stream, prefix: prefix, verbose: verbose, **options)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -35,12 +35,14 @@ class Puppeteer::CSSCoverage
|
|
|
35
35
|
@stylesheet_sources.clear
|
|
36
36
|
@event_listeners = []
|
|
37
37
|
@event_listeners << @client.add_event_listener('CSS.styleSheetAdded') do |event|
|
|
38
|
-
|
|
38
|
+
Async do
|
|
39
|
+
Puppeteer::AsyncUtils.future_with_logging { on_stylesheet(event) }.call
|
|
40
|
+
end
|
|
39
41
|
end
|
|
40
42
|
@event_listeners << @client.add_event_listener('Runtime.executionContextsCleared') do
|
|
41
43
|
on_execution_contexts_cleared
|
|
42
44
|
end
|
|
43
|
-
|
|
45
|
+
Puppeteer::AsyncUtils.await_promise_all(
|
|
44
46
|
@client.async_send_message('DOM.enable'),
|
|
45
47
|
@client.async_send_message('CSS.enable'),
|
|
46
48
|
@client.async_send_message('CSS.startRuleUsageTracking'),
|
|
@@ -76,7 +78,7 @@ class Puppeteer::CSSCoverage
|
|
|
76
78
|
@enabled = false
|
|
77
79
|
|
|
78
80
|
rule_tracking_response = @client.send_message('CSS.stopRuleUsageTracking')
|
|
79
|
-
|
|
81
|
+
Puppeteer::AsyncUtils.await_promise_all(
|
|
80
82
|
@client.async_send_message('CSS.disable'),
|
|
81
83
|
@client.async_send_message('DOM.disable'),
|
|
82
84
|
)
|
|
@@ -7,21 +7,23 @@ class Puppeteer::CustomQueryHandler
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def query_one(element, selector)
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
if @query_one
|
|
11
|
+
return query_one_with_query_one(element, selector)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if @query_all
|
|
15
|
+
elements = query_all_with_query_all(element, selector)
|
|
16
|
+
return nil if elements.empty?
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
first = elements.shift
|
|
19
|
+
elements.each(&:dispose)
|
|
20
|
+
return first
|
|
19
21
|
end
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def wait_for(element_or_frame, selector, visible: nil, hidden: nil, timeout: nil)
|
|
26
|
+
def wait_for(element_or_frame, selector, visible: nil, hidden: nil, timeout: nil, polling: nil)
|
|
25
27
|
case element_or_frame
|
|
26
28
|
when Puppeteer::Frame
|
|
27
29
|
frame = element_or_frame
|
|
@@ -37,42 +39,87 @@ class Puppeteer::CustomQueryHandler
|
|
|
37
39
|
raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
|
|
38
40
|
end
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
begin
|
|
43
|
+
result = frame.puppeteer_world.send(:wait_for_selector_in_page,
|
|
44
|
+
@query_one,
|
|
45
|
+
element,
|
|
46
|
+
selector,
|
|
47
|
+
visible: visible,
|
|
48
|
+
hidden: hidden,
|
|
49
|
+
timeout: timeout,
|
|
50
|
+
polling: polling,
|
|
51
|
+
)
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if result.is_a?(Puppeteer::ElementHandle)
|
|
54
|
+
result.frame.main_world.transfer_handle(result)
|
|
55
|
+
else
|
|
56
|
+
result&.dispose
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
rescue => err
|
|
60
|
+
wait_for_selector_error =
|
|
61
|
+
if err.is_a?(Puppeteer::TimeoutError)
|
|
62
|
+
Puppeteer::TimeoutError.new("Waiting for selector `#{selector}` failed")
|
|
63
|
+
else
|
|
64
|
+
Puppeteer::Error.new("Waiting for selector `#{selector}` failed")
|
|
65
|
+
end
|
|
66
|
+
wait_for_selector_error.cause = err
|
|
67
|
+
raise wait_for_selector_error
|
|
68
|
+
ensure
|
|
69
|
+
element&.dispose
|
|
56
70
|
end
|
|
57
71
|
end
|
|
58
72
|
|
|
59
73
|
def query_all(element, selector)
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
if @query_all
|
|
75
|
+
return query_all_with_query_all(element, selector)
|
|
62
76
|
end
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
if @query_one
|
|
79
|
+
element_handle = query_one_with_query_one(element, selector)
|
|
80
|
+
return element_handle ? [element_handle] : []
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
|
|
68
84
|
end
|
|
69
85
|
|
|
70
86
|
def query_all_array(element, selector)
|
|
71
|
-
|
|
72
|
-
|
|
87
|
+
if @query_all
|
|
88
|
+
handles = element.evaluate_handle(@query_all, selector)
|
|
89
|
+
begin
|
|
90
|
+
return handles.evaluate_handle('(res) => Array.from(res)')
|
|
91
|
+
ensure
|
|
92
|
+
handles.dispose
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if @query_one
|
|
97
|
+
elements = query_all(element, selector)
|
|
98
|
+
begin
|
|
99
|
+
return element.execution_context.evaluate_handle('(...elements) => elements', *elements)
|
|
100
|
+
ensure
|
|
101
|
+
elements.each(&:dispose)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise NotImplementedError.new("#{self.class}##{__method__} is not implemented.")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private def query_one_with_query_one(element, selector)
|
|
109
|
+
handle = element.evaluate_handle(@query_one, selector)
|
|
110
|
+
element = handle.as_element
|
|
111
|
+
|
|
112
|
+
if element
|
|
113
|
+
return element
|
|
73
114
|
end
|
|
115
|
+
handle.dispose
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
74
118
|
|
|
119
|
+
private def query_all_with_query_all(element, selector)
|
|
75
120
|
handles = element.evaluate_handle(@query_all, selector)
|
|
76
|
-
|
|
121
|
+
properties = handles.properties
|
|
122
|
+
handles.dispose
|
|
123
|
+
properties.values.map(&:as_element).compact
|
|
77
124
|
end
|
|
78
125
|
end
|
|
@@ -17,40 +17,42 @@ module Puppeteer::DefineAsyncMethod
|
|
|
17
17
|
if method_defined?(original_method_name) && original_method_name.start_with?('wait_for_')
|
|
18
18
|
# def wait_for_xxx(xx, yy, &block)
|
|
19
19
|
#
|
|
20
|
-
# ->
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# ).first
|
|
20
|
+
# -> start wait_for_xxx in a child task
|
|
21
|
+
# -> run block (awaiting its result if needed)
|
|
22
|
+
# -> wait for wait_for_xxx task, cancel on block errors
|
|
24
23
|
define_method(original_method_name) do |*args, **kwargs, &block|
|
|
25
24
|
if block
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
runner = lambda do
|
|
26
|
+
parent_task = Async::Task.current
|
|
27
|
+
wait_task = parent_task.async do
|
|
28
|
+
if kwargs.empty?
|
|
29
29
|
original_method.bind(self).call(*args)
|
|
30
|
-
|
|
31
|
-
Logger.new($stderr).warn(err)
|
|
32
|
-
raise err
|
|
33
|
-
end
|
|
34
|
-
else
|
|
35
|
-
Concurrent::Promises.future do
|
|
30
|
+
else
|
|
36
31
|
original_method.bind(self).call(*args, **kwargs)
|
|
37
|
-
rescue => err
|
|
38
|
-
Logger.new($stderr).warn(err)
|
|
39
|
-
raise err
|
|
40
32
|
end
|
|
41
33
|
end
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
begin
|
|
36
|
+
block_result = block.call
|
|
37
|
+
Puppeteer::AsyncUtils.await(block_result)
|
|
38
|
+
rescue Exception => err
|
|
39
|
+
begin
|
|
40
|
+
wait_task.stop
|
|
41
|
+
Puppeteer::AsyncUtils.async_timeout(1000, -> { wait_task.wait }).wait
|
|
42
|
+
rescue Exception
|
|
43
|
+
# Swallow cancellation errors/timeouts; original error takes priority.
|
|
44
|
+
end
|
|
45
|
+
raise err
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
wait_task.wait
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if Async::Task.current?
|
|
52
|
+
runner.call
|
|
53
|
+
else
|
|
54
|
+
Sync { runner.call }
|
|
55
|
+
end
|
|
54
56
|
else
|
|
55
57
|
if kwargs.empty? # for Ruby 2.6
|
|
56
58
|
original_method.bind(self).call(*args)
|
|
@@ -62,20 +64,12 @@ module Puppeteer::DefineAsyncMethod
|
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
define_method(async_method_name) do |*args, **kwargs|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
Async do
|
|
68
|
+
if kwargs.empty? # for Ruby 2.6
|
|
67
69
|
original_method.bind(self).call(*args)
|
|
68
|
-
|
|
69
|
-
Logger.new($stderr).warn(err)
|
|
70
|
-
raise err
|
|
71
|
-
end.extend(Puppeteer::ConcurrentRubyUtils::ConcurrentPromisesFutureExtension)
|
|
72
|
-
else
|
|
73
|
-
Concurrent::Promises.future do
|
|
70
|
+
else
|
|
74
71
|
original_method.bind(self).call(*args, **kwargs)
|
|
75
|
-
|
|
76
|
-
Logger.new($stderr).warn(err)
|
|
77
|
-
raise err
|
|
78
|
-
end.extend(Puppeteer::ConcurrentRubyUtils::ConcurrentPromisesFutureExtension)
|
|
72
|
+
end
|
|
79
73
|
end
|
|
80
74
|
end
|
|
81
75
|
end
|
data/lib/puppeteer/dialog.rb
CHANGED
|
@@ -1,34 +1,67 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
|
|
1
3
|
class Puppeteer::Dialog
|
|
2
|
-
|
|
3
|
-
|
|
4
|
+
# @rbs type: String -- Dialog type
|
|
5
|
+
# @rbs message: String -- Dialog message
|
|
6
|
+
# @rbs default_value: String? -- Default prompt value, if any
|
|
7
|
+
# @rbs return: void -- No return value
|
|
8
|
+
def initialize(type:, message:, default_value: '')
|
|
4
9
|
@type = type
|
|
5
10
|
@message = message
|
|
6
11
|
@default_value = default_value || ''
|
|
12
|
+
@handled = false
|
|
7
13
|
end
|
|
8
14
|
|
|
9
|
-
attr_reader :type
|
|
15
|
+
attr_reader :type #: String
|
|
16
|
+
attr_reader :message #: String
|
|
17
|
+
attr_reader :default_value #: String
|
|
10
18
|
|
|
11
|
-
# @
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# @returns A promise that resolves when the dialog has been accepted.
|
|
19
|
+
# @rbs prompt_text: String? -- Text entered into the prompt
|
|
20
|
+
# @rbs return: void -- No return value
|
|
15
21
|
def accept(prompt_text = nil)
|
|
16
22
|
if @handled
|
|
17
23
|
raise 'Cannot accept dialog which is already handled!'
|
|
18
24
|
end
|
|
19
25
|
@handled = true
|
|
20
|
-
|
|
21
|
-
accept: true,
|
|
22
|
-
promptText: prompt_text,
|
|
23
|
-
}.compact)
|
|
26
|
+
handle(accept: true, text: prompt_text)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
# @
|
|
29
|
+
# @rbs return: void -- No return value
|
|
27
30
|
def dismiss
|
|
28
31
|
if @handled
|
|
29
|
-
raise 'Cannot
|
|
32
|
+
raise 'Cannot dismiss dialog which is already handled!'
|
|
30
33
|
end
|
|
31
34
|
@handled = true
|
|
32
|
-
|
|
35
|
+
handle(accept: false)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @rbs accept: bool -- Whether to accept the dialog
|
|
39
|
+
# @rbs text: String? -- Text entered into the prompt
|
|
40
|
+
# @rbs return: void -- No return value
|
|
41
|
+
protected def handle(accept:, text: nil)
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Puppeteer::CdpDialog < Puppeteer::Dialog
|
|
47
|
+
# @rbs client: Puppeteer::CDPSession -- CDP session used to handle dialog
|
|
48
|
+
# @rbs type: String -- Dialog type
|
|
49
|
+
# @rbs message: String -- Dialog message
|
|
50
|
+
# @rbs default_value: String? -- Default prompt value, if any
|
|
51
|
+
# @rbs return: void -- No return value
|
|
52
|
+
def initialize(client, type:, message:, default_value:)
|
|
53
|
+
super(type: type, message: message, default_value: default_value)
|
|
54
|
+
@client = client
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @rbs accept: bool -- Whether to accept the dialog
|
|
58
|
+
# @rbs text: String? -- Text entered into the prompt
|
|
59
|
+
# @rbs return: void -- No return value
|
|
60
|
+
protected def handle(accept:, text: nil)
|
|
61
|
+
@client.send_message('Page.handleJavaScriptDialog', {
|
|
62
|
+
accept: accept,
|
|
63
|
+
promptText: text,
|
|
64
|
+
}.compact)
|
|
65
|
+
nil
|
|
33
66
|
end
|
|
34
67
|
end
|