playwright-ruby-client 0.0.8 → 1.58.1.alpha1
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/AGENTS.md +4 -0
- data/CLAUDE/api_generation.md +28 -0
- data/CLAUDE/ci_expectations.md +23 -0
- data/CLAUDE/gem_release_flow.md +39 -0
- data/CLAUDE/past_upgrade_pr_patterns.md +42 -0
- data/CLAUDE/playwright_upgrade_workflow.md +35 -0
- data/CLAUDE/rspec_debugging.md +30 -0
- data/CLAUDE/unimplemented_examples.md +18 -0
- data/CLAUDE.md +32 -0
- data/CONTRIBUTING.md +5 -0
- data/README.md +60 -16
- data/documentation/README.md +33 -0
- data/documentation/babel.config.js +3 -0
- data/documentation/docs/api/api_request.md +7 -0
- data/documentation/docs/api/api_request_context.md +298 -0
- data/documentation/docs/api/api_response.md +114 -0
- data/documentation/docs/api/browser.md +237 -0
- data/documentation/docs/api/browser_context.md +503 -0
- data/documentation/docs/api/browser_type.md +184 -0
- data/documentation/docs/api/cdp_session.md +44 -0
- data/documentation/docs/api/clock.md +154 -0
- data/documentation/docs/api/console_message.md +85 -0
- data/documentation/docs/api/dialog.md +84 -0
- data/documentation/docs/api/download.md +111 -0
- data/documentation/docs/api/element_handle.md +694 -0
- data/documentation/docs/api/experimental/_category_.yml +3 -0
- data/documentation/docs/api/experimental/android.md +42 -0
- data/documentation/docs/api/experimental/android_device.md +109 -0
- data/documentation/docs/api/experimental/android_input.md +43 -0
- data/documentation/docs/api/experimental/android_socket.md +7 -0
- data/documentation/docs/api/experimental/android_web_view.md +7 -0
- data/documentation/docs/api/file_chooser.md +53 -0
- data/documentation/docs/api/frame.md +1218 -0
- data/documentation/docs/api/frame_locator.md +348 -0
- data/documentation/docs/api/js_handle.md +121 -0
- data/documentation/docs/api/keyboard.md +170 -0
- data/documentation/docs/api/locator.md +1495 -0
- data/documentation/docs/api/locator_assertions.md +827 -0
- data/documentation/docs/api/mouse.md +86 -0
- data/documentation/docs/api/page.md +1946 -0
- data/documentation/docs/api/page_assertions.md +65 -0
- data/documentation/docs/api/playwright.md +66 -0
- data/documentation/docs/api/request.md +255 -0
- data/documentation/docs/api/response.md +176 -0
- data/documentation/docs/api/route.md +205 -0
- data/documentation/docs/api/selectors.md +63 -0
- data/documentation/docs/api/touchscreen.md +22 -0
- data/documentation/docs/api/tracing.md +129 -0
- data/documentation/docs/api/web_socket.md +51 -0
- data/documentation/docs/api/worker.md +83 -0
- data/documentation/docs/article/api_coverage.mdx +11 -0
- data/documentation/docs/article/getting_started.md +161 -0
- data/documentation/docs/article/guides/_category_.yml +3 -0
- data/documentation/docs/article/guides/download_playwright_driver.md +55 -0
- data/documentation/docs/article/guides/inspector.md +31 -0
- data/documentation/docs/article/guides/launch_browser.md +121 -0
- data/documentation/docs/article/guides/playwright_on_alpine_linux.md +112 -0
- data/documentation/docs/article/guides/rails_integration.md +278 -0
- data/documentation/docs/article/guides/rails_integration_with_null_driver.md +145 -0
- data/documentation/docs/article/guides/recording_video.md +79 -0
- data/documentation/docs/article/guides/rspec_integration.md +59 -0
- data/documentation/docs/article/guides/semi_automation.md +71 -0
- data/documentation/docs/article/guides/use_storage_state.md +78 -0
- data/documentation/docs/include/api_coverage.md +671 -0
- data/documentation/docusaurus.config.js +114 -0
- data/documentation/package.json +39 -0
- data/documentation/sidebars.js +15 -0
- data/documentation/src/components/HomepageFeatures.js +61 -0
- data/documentation/src/components/HomepageFeatures.module.css +13 -0
- data/documentation/src/css/custom.css +44 -0
- data/documentation/src/pages/index.js +49 -0
- data/documentation/src/pages/index.module.css +41 -0
- data/documentation/src/pages/markdown-page.md +7 -0
- data/documentation/static/.nojekyll +0 -0
- data/documentation/static/img/playwright-logo.svg +9 -0
- data/documentation/static/img/playwright-ruby-client.png +0 -0
- data/documentation/static/img/undraw_dropdown_menu.svg +1 -0
- data/documentation/static/img/undraw_web_development.svg +1 -0
- data/documentation/static/img/undraw_windows.svg +1 -0
- data/documentation/yarn.lock +9005 -0
- data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
- data/lib/playwright/api_implementation.rb +18 -0
- data/lib/playwright/api_response_impl.rb +77 -0
- data/lib/playwright/channel.rb +62 -1
- data/lib/playwright/channel_owner.rb +70 -7
- data/lib/playwright/channel_owners/android.rb +16 -3
- data/lib/playwright/channel_owners/android_device.rb +22 -66
- data/lib/playwright/channel_owners/api_request_context.rb +247 -0
- data/lib/playwright/channel_owners/artifact.rb +40 -0
- data/lib/playwright/channel_owners/binding_call.rb +70 -0
- data/lib/playwright/channel_owners/browser.rb +114 -22
- data/lib/playwright/channel_owners/browser_context.rb +589 -15
- data/lib/playwright/channel_owners/browser_type.rb +90 -1
- data/lib/playwright/channel_owners/cdp_session.rb +19 -0
- data/lib/playwright/channel_owners/dialog.rb +32 -0
- data/lib/playwright/channel_owners/element_handle.rb +107 -43
- data/lib/playwright/channel_owners/fetch_request.rb +8 -0
- data/lib/playwright/channel_owners/frame.rb +334 -104
- data/lib/playwright/channel_owners/js_handle.rb +9 -13
- data/lib/playwright/channel_owners/local_utils.rb +82 -0
- data/lib/playwright/channel_owners/page.rb +778 -95
- data/lib/playwright/channel_owners/playwright.rb +25 -30
- data/lib/playwright/channel_owners/request.rb +120 -18
- data/lib/playwright/channel_owners/response.rb +113 -0
- data/lib/playwright/channel_owners/route.rb +181 -0
- data/lib/playwright/channel_owners/stream.rb +30 -0
- data/lib/playwright/channel_owners/tracing.rb +117 -0
- data/lib/playwright/channel_owners/web_socket.rb +96 -0
- data/lib/playwright/channel_owners/worker.rb +46 -0
- data/lib/playwright/channel_owners/writable_stream.rb +14 -0
- data/lib/playwright/clock_impl.rb +67 -0
- data/lib/playwright/connection.rb +111 -63
- data/lib/playwright/console_message_impl.rb +29 -0
- data/lib/playwright/download_impl.rb +32 -0
- data/lib/playwright/errors.rb +42 -5
- data/lib/playwright/event_emitter.rb +17 -3
- data/lib/playwright/event_emitter_proxy.rb +49 -0
- data/lib/playwright/events.rb +10 -5
- data/lib/playwright/file_chooser_impl.rb +24 -0
- data/lib/playwright/frame_locator_impl.rb +66 -0
- data/lib/playwright/har_router.rb +89 -0
- data/lib/playwright/http_headers.rb +14 -0
- data/lib/playwright/input_files.rb +102 -15
- data/lib/playwright/javascript/expression.rb +7 -11
- data/lib/playwright/javascript/regex.rb +23 -0
- data/lib/playwright/javascript/source_url.rb +16 -0
- data/lib/playwright/javascript/value_parser.rb +108 -19
- data/lib/playwright/javascript/value_serializer.rb +47 -8
- data/lib/playwright/javascript/visitor_info.rb +26 -0
- data/lib/playwright/javascript.rb +2 -10
- data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
- data/lib/playwright/locator_assertions_impl.rb +571 -0
- data/lib/playwright/locator_impl.rb +544 -0
- data/lib/playwright/locator_utils.rb +136 -0
- data/lib/playwright/mouse_impl.rb +57 -0
- data/lib/playwright/page_assertions_impl.rb +154 -0
- data/lib/playwright/playwright_api.rb +102 -30
- data/lib/playwright/raw_headers.rb +61 -0
- data/lib/playwright/route_handler.rb +78 -0
- data/lib/playwright/select_option_values.rb +34 -13
- data/lib/playwright/selectors_impl.rb +45 -0
- data/lib/playwright/test.rb +102 -0
- data/lib/playwright/timeout_settings.rb +9 -4
- data/lib/playwright/touchscreen_impl.rb +14 -0
- data/lib/playwright/transport.rb +61 -10
- data/lib/playwright/url_matcher.rb +24 -2
- data/lib/playwright/utils.rb +48 -13
- data/lib/playwright/version.rb +2 -1
- data/lib/playwright/video.rb +54 -0
- data/lib/playwright/waiter.rb +166 -0
- data/lib/playwright/web_socket_client.rb +167 -0
- data/lib/playwright/web_socket_transport.rb +116 -0
- data/lib/playwright.rb +188 -11
- data/lib/playwright_api/android.rb +46 -11
- data/lib/playwright_api/android_device.rb +182 -31
- data/lib/playwright_api/android_input.rb +22 -13
- data/lib/playwright_api/android_socket.rb +18 -0
- data/lib/playwright_api/android_web_view.rb +24 -0
- data/lib/playwright_api/api_request.rb +26 -0
- data/lib/playwright_api/api_request_context.rb +311 -0
- data/lib/playwright_api/api_response.rb +92 -0
- data/lib/playwright_api/browser.rb +116 -103
- data/lib/playwright_api/browser_context.rb +290 -389
- data/lib/playwright_api/browser_type.rb +96 -118
- data/lib/playwright_api/cdp_session.rb +36 -39
- data/lib/playwright_api/clock.rb +121 -0
- data/lib/playwright_api/console_message.rb +35 -19
- data/lib/playwright_api/dialog.rb +53 -50
- data/lib/playwright_api/download.rb +49 -43
- data/lib/playwright_api/element_handle.rb +354 -402
- data/lib/playwright_api/file_chooser.rb +15 -18
- data/lib/playwright_api/frame.rb +703 -603
- data/lib/playwright_api/frame_locator.rb +285 -0
- data/lib/playwright_api/js_handle.rb +50 -76
- data/lib/playwright_api/keyboard.rb +67 -146
- data/lib/playwright_api/locator.rb +1304 -0
- data/lib/playwright_api/locator_assertions.rb +704 -0
- data/lib/playwright_api/mouse.rb +23 -29
- data/lib/playwright_api/page.rb +1196 -1176
- data/lib/playwright_api/page_assertions.rb +60 -0
- data/lib/playwright_api/playwright.rb +54 -122
- data/lib/playwright_api/request.rb +112 -74
- data/lib/playwright_api/response.rb +92 -20
- data/lib/playwright_api/route.rb +152 -62
- data/lib/playwright_api/selectors.rb +47 -61
- data/lib/playwright_api/touchscreen.rb +8 -2
- data/lib/playwright_api/tracing.rb +128 -0
- data/lib/playwright_api/web_socket.rb +43 -5
- data/lib/playwright_api/worker.rb +74 -34
- data/playwright.gemspec +14 -9
- data/sig/playwright.rbs +658 -0
- metadata +216 -50
- data/docs/api_coverage.md +0 -354
- data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
- data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
- data/lib/playwright/channel_owners/console_message.rb +0 -21
- data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
- data/lib/playwright/channel_owners/selectors.rb +0 -4
- data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
- data/lib/playwright/input_type.rb +0 -19
- data/lib/playwright/input_types/mouse.rb +0 -4
- data/lib/playwright/input_types/touchscreen.rb +0 -4
- data/lib/playwright/javascript/function.rb +0 -67
- data/lib/playwright/wait_helper.rb +0 -73
- data/lib/playwright_api/accessibility.rb +0 -93
- data/lib/playwright_api/binding_call.rb +0 -23
- data/lib/playwright_api/chromium_browser_context.rb +0 -57
- data/lib/playwright_api/video.rb +0 -24
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
define_channel_owner :Tracing do
|
|
3
|
+
def start(name: nil, title: nil, screenshots: nil, snapshots: nil, sources: nil)
|
|
4
|
+
params = {
|
|
5
|
+
name: name,
|
|
6
|
+
screenshots: screenshots,
|
|
7
|
+
snapshots: snapshots,
|
|
8
|
+
sources: sources,
|
|
9
|
+
}.compact
|
|
10
|
+
@include_sources = params[:sources] || false
|
|
11
|
+
@channel.send_message_to_server('tracingStart', params)
|
|
12
|
+
trace_name = @channel.send_message_to_server('tracingStartChunk', { title: title, name: name }.compact)
|
|
13
|
+
start_collecting_stacks(trace_name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start_chunk(title: nil, name: nil)
|
|
17
|
+
trace_name = @channel.send_message_to_server('tracingStartChunk', { title: title, name: name }.compact)
|
|
18
|
+
start_collecting_stacks(trace_name)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private def start_collecting_stacks(trace_name)
|
|
22
|
+
unless @is_tracing
|
|
23
|
+
@is_tracing = true
|
|
24
|
+
@connection.set_in_tracing(true)
|
|
25
|
+
end
|
|
26
|
+
local_utils = @connection.local_utils
|
|
27
|
+
@stacks_id = local_utils&.tracing_started(@traces_dir, trace_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def stop_chunk(path: nil)
|
|
31
|
+
do_stop_chunk(file_path: path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def stop(path: nil)
|
|
35
|
+
do_stop_chunk(file_path: path)
|
|
36
|
+
@channel.send_message_to_server('tracingStop')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def do_stop_chunk(file_path:)
|
|
40
|
+
if @is_tracing
|
|
41
|
+
@is_tracing = false
|
|
42
|
+
@connection.set_in_tracing(false)
|
|
43
|
+
end
|
|
44
|
+
local_utils = @connection.local_utils
|
|
45
|
+
|
|
46
|
+
unless file_path
|
|
47
|
+
# Not interested in any artifacts
|
|
48
|
+
@channel.send_message_to_server('tracingStopChunk', mode: 'discard')
|
|
49
|
+
if @stacks_id
|
|
50
|
+
local_utils.trace_discarded(@stacks_id) if local_utils
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
is_local = !@connection.remote?
|
|
57
|
+
if is_local
|
|
58
|
+
unless local_utils
|
|
59
|
+
raise 'Cannot save trace because localUtils is unavailable.'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
result = @channel.send_message_to_server_result('tracingStopChunk', mode: 'entries')
|
|
63
|
+
local_utils.zip(
|
|
64
|
+
zipFile: file_path,
|
|
65
|
+
entries: result['entries'],
|
|
66
|
+
stacksId: @stacks_id,
|
|
67
|
+
mode: 'write',
|
|
68
|
+
includeSources: @include_sources,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
result = @channel.send_message_to_server_result('tracingStopChunk', mode: 'archive')
|
|
76
|
+
# The artifact may be missing if the browser closed while stopping tracing.
|
|
77
|
+
unless result['artifact']
|
|
78
|
+
if @stacks_id
|
|
79
|
+
local_utils.trace_discarded(@stacks_id) if local_utils
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Save trace to the final local file.
|
|
86
|
+
artifact = ChannelOwners::Artifact.from(result['artifact'])
|
|
87
|
+
artifact.save_as(file_path)
|
|
88
|
+
artifact.delete
|
|
89
|
+
|
|
90
|
+
return unless local_utils
|
|
91
|
+
|
|
92
|
+
local_utils.zip(
|
|
93
|
+
zipFile: file_path,
|
|
94
|
+
entries: [],
|
|
95
|
+
stacksId: @stacks_id,
|
|
96
|
+
mode: 'append',
|
|
97
|
+
includeSources: @include_sources,
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private def update_traces_dir(traces_dir)
|
|
102
|
+
@traces_dir = traces_dir
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def group(name, location: nil)
|
|
106
|
+
params = {
|
|
107
|
+
name: name,
|
|
108
|
+
location: location,
|
|
109
|
+
}.compact
|
|
110
|
+
@channel.send_message_to_server('tracingGroup', params)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def group_end
|
|
114
|
+
@channel.send_message_to_server('tracingGroupEnd')
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
3
|
+
module Playwright
|
|
4
|
+
define_channel_owner :WebSocket do
|
|
5
|
+
private def after_initialize
|
|
6
|
+
@closed = false
|
|
7
|
+
|
|
8
|
+
@channel.on('frameSent', -> (params) {
|
|
9
|
+
on_frame_sent(params['opcode'], params['data'])
|
|
10
|
+
})
|
|
11
|
+
@channel.on('frameReceived', -> (params) {
|
|
12
|
+
on_frame_received(params['opcode'], params['data'])
|
|
13
|
+
})
|
|
14
|
+
@channel.on('socketError', -> (params) {
|
|
15
|
+
emit(Events::WebSocket::Error, params['error'])
|
|
16
|
+
})
|
|
17
|
+
@channel.on('close', -> (_) { on_close })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def url
|
|
21
|
+
@initializer['url']
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class SocketClosedError < StandardError
|
|
25
|
+
def initialize
|
|
26
|
+
super('Socket closed')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class SocketError < StandardError
|
|
31
|
+
def initialize
|
|
32
|
+
super('Socket error')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def expect_event(event, predicate: nil, timeout: nil, &block)
|
|
37
|
+
waiter = Waiter.new(self, wait_name: "WebSocket.expect_event(#{event})")
|
|
38
|
+
timeout_value = timeout || @parent.send(:_timeout_settings).timeout
|
|
39
|
+
waiter.reject_on_timeout(timeout_value, "Timeout #{timeout_value}ms exceeded while waiting for event \"#{event}\"")
|
|
40
|
+
|
|
41
|
+
unless event == Events::WebSocket::Close
|
|
42
|
+
waiter.reject_on_event(self, Events::WebSocket::Close, SocketClosedError.new)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
unless event == Events::WebSocket::Error
|
|
46
|
+
waiter.reject_on_event(self, Events::WebSocket::Error, SocketError.new)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
waiter.reject_on_event(@parent, 'close', -> { @parent.send(:close_error_with_reason) })
|
|
50
|
+
waiter.wait_for_event(self, event, predicate: predicate)
|
|
51
|
+
if @closed
|
|
52
|
+
if event == Events::WebSocket::Close
|
|
53
|
+
waiter.force_fulfill(nil)
|
|
54
|
+
else
|
|
55
|
+
waiter.force_reject(SocketClosedError.new)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
block&.call
|
|
59
|
+
if @closed
|
|
60
|
+
if event == Events::WebSocket::Close
|
|
61
|
+
waiter.force_fulfill(nil)
|
|
62
|
+
else
|
|
63
|
+
waiter.force_reject(SocketClosedError.new)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
waiter.result.value!
|
|
68
|
+
end
|
|
69
|
+
alias_method :wait_for_event, :expect_event
|
|
70
|
+
|
|
71
|
+
private def on_frame_sent(opcode, data)
|
|
72
|
+
if opcode == 2
|
|
73
|
+
emit(Events::WebSocket::FrameSent, Base64.strict_decode64(data))
|
|
74
|
+
else
|
|
75
|
+
emit(Events::WebSocket::FrameSent, data)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private def on_frame_received(opcode, data)
|
|
80
|
+
if opcode == 2
|
|
81
|
+
emit(Events::WebSocket::FrameReceived, Base64.strict_decode64(data))
|
|
82
|
+
else
|
|
83
|
+
emit(Events::WebSocket::FrameReceived, data)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def closed?
|
|
88
|
+
@closed
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private def on_close
|
|
92
|
+
@closed = true
|
|
93
|
+
emit(Events::WebSocket::Close)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
define_channel_owner :Worker do
|
|
3
|
+
attr_writer :context, :page
|
|
4
|
+
|
|
5
|
+
private def after_initialize
|
|
6
|
+
@channel.once('close', ->(_) { on_close })
|
|
7
|
+
|
|
8
|
+
set_event_to_subscription_mapping({
|
|
9
|
+
Events::Worker::Console => "console",
|
|
10
|
+
})
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private def on_close
|
|
14
|
+
@page&.send(:remove_worker, self)
|
|
15
|
+
@context&.send(:remove_service_worker, self)
|
|
16
|
+
emit(Events::Worker::Close, self)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def url
|
|
20
|
+
@initializer['url']
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def evaluate(expression, arg: nil)
|
|
24
|
+
JavaScript::Expression.new(expression, arg).evaluate(@channel)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def evaluate_handle(expression, arg: nil)
|
|
28
|
+
JavaScript::Expression.new(expression, arg).evaluate_handle(@channel)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def expect_event(event, predicate: nil, timeout: nil, &block)
|
|
32
|
+
waiter = Waiter.new(self, wait_name: "Worker.expect_event(#{event})")
|
|
33
|
+
timeout_value = timeout || @page&.send(:_timeout_settings)&.timeout || @context&.send(:_timeout_settings)&.timeout
|
|
34
|
+
waiter.reject_on_timeout(timeout_value, "Timeout #{timeout_value}ms exceeded while waiting for event \"#{event}\"")
|
|
35
|
+
|
|
36
|
+
unless event == Events::Worker::Close
|
|
37
|
+
waiter.reject_on_event(self, Events::Worker::Close, TargetClosedError.new)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
waiter.wait_for_event(self, event, predicate: predicate)
|
|
41
|
+
block&.call
|
|
42
|
+
|
|
43
|
+
waiter.result.value!
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
3
|
+
module Playwright
|
|
4
|
+
define_channel_owner :WritableStream do
|
|
5
|
+
# @param readable [File|IO]
|
|
6
|
+
def write(readable, bufsize = 1048576)
|
|
7
|
+
while buf = readable.read(bufsize)
|
|
8
|
+
binary = Base64.strict_encode64(buf)
|
|
9
|
+
@channel.send_message_to_server('write', binary: binary)
|
|
10
|
+
end
|
|
11
|
+
@channel.send_message_to_server('close')
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
define_api_implementation :ClockImpl do
|
|
3
|
+
def initialize(browser_context)
|
|
4
|
+
@browser_context = browser_context
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def install(time: nil)
|
|
8
|
+
if time
|
|
9
|
+
@browser_context.send(:clock_install, parse_time(time))
|
|
10
|
+
else
|
|
11
|
+
@browser_context.send(:clock_install, {})
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fast_forward(ticks)
|
|
16
|
+
@browser_context.send(:clock_fast_forward, parse_ticks(ticks))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def pause_at(time)
|
|
20
|
+
@browser_context.send(:clock_pause_at, parse_time(time))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def resume
|
|
24
|
+
@browser_context.send(:clock_resume)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run_for(ticks)
|
|
28
|
+
@browser_context.send(:clock_run_for, parse_ticks(ticks))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set_fixed_time(time)
|
|
32
|
+
@browser_context.send(:clock_set_fixed_time, parse_time(time))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set_system_time(time)
|
|
36
|
+
@browser_context.send(:clock_set_system_time, parse_time(time))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def parse_time(time)
|
|
40
|
+
case time
|
|
41
|
+
when Integer
|
|
42
|
+
{ timeNumber: time }
|
|
43
|
+
when String
|
|
44
|
+
{ timeString: time }
|
|
45
|
+
when DateTime
|
|
46
|
+
{ timeNumber: time.to_time.to_i * 1000 }
|
|
47
|
+
else
|
|
48
|
+
if time.respond_to?(:utc)
|
|
49
|
+
{ timeNumber: time.utc.to_i * 1000 }
|
|
50
|
+
else
|
|
51
|
+
raise ArgumentError.new('time must be either integer, string or a Time object')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private def parse_ticks(ticks)
|
|
57
|
+
case ticks
|
|
58
|
+
when Integer
|
|
59
|
+
{ ticksNumber: ticks }
|
|
60
|
+
when String
|
|
61
|
+
{ ticksString: ticks }
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError.new('ticks must be either integer or string')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -5,70 +5,124 @@ module Playwright
|
|
|
5
5
|
# https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_connection.py
|
|
6
6
|
# https://github.com/microsoft/playwright-java/blob/master/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
|
|
7
7
|
class Connection
|
|
8
|
-
def initialize(
|
|
9
|
-
@transport =
|
|
10
|
-
playwright_cli_executable_path: playwright_cli_executable_path
|
|
11
|
-
)
|
|
8
|
+
def initialize(transport)
|
|
9
|
+
@transport = transport
|
|
12
10
|
@transport.on_message_received do |message|
|
|
13
11
|
dispatch(message)
|
|
14
12
|
end
|
|
13
|
+
@transport.on_driver_crashed do
|
|
14
|
+
callbacks = @callbacks_mutex.synchronize { @callbacks.values }
|
|
15
|
+
callbacks.each { |callback| callback.reject(::Playwright::DriverCrashedError.new) }
|
|
16
|
+
raise ::Playwright::DriverCrashedError.new
|
|
17
|
+
end
|
|
18
|
+
@transport.on_driver_closed do
|
|
19
|
+
cleanup
|
|
20
|
+
end
|
|
15
21
|
|
|
16
22
|
@objects = {} # Hash[ guid => ChannelOwner ]
|
|
23
|
+
@objects_mutex = Mutex.new
|
|
17
24
|
@waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
|
|
18
25
|
@callbacks = {} # Hash [ guid => Promise<ChannelOwner> ]
|
|
26
|
+
@callbacks_mutex = Mutex.new
|
|
19
27
|
@root_object = RootChannelOwner.new(self)
|
|
28
|
+
@remote = false
|
|
29
|
+
@tracing_count = 0
|
|
30
|
+
@closed_error = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_reader :local_utils
|
|
34
|
+
|
|
35
|
+
def mark_as_remote
|
|
36
|
+
@remote = true
|
|
20
37
|
end
|
|
21
38
|
|
|
22
|
-
def
|
|
23
|
-
@
|
|
39
|
+
def remote?
|
|
40
|
+
@remote
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def async_run
|
|
44
|
+
@transport.async_run
|
|
24
45
|
end
|
|
25
46
|
|
|
26
47
|
def stop
|
|
27
48
|
@transport.stop
|
|
49
|
+
cleanup
|
|
28
50
|
end
|
|
29
51
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
def cleanup(cause: nil)
|
|
53
|
+
@closed_error = TargetClosedError.new(message: cause)
|
|
54
|
+
callbacks = @callbacks_mutex.synchronize do
|
|
55
|
+
@callbacks.values.tap { @callbacks.clear }
|
|
33
56
|
end
|
|
57
|
+
callbacks.each { |callback| callback.reject(@closed_error) }
|
|
58
|
+
end
|
|
34
59
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
60
|
+
def initialize_playwright
|
|
61
|
+
# Avoid Error: sdkLanguage: expected one of (javascript|python|java|csharp)
|
|
62
|
+
# ref: https://github.com/microsoft/playwright/pull/18308
|
|
63
|
+
# ref: https://github.com/YusukeIwaki/playwright-ruby-client/issues/228
|
|
64
|
+
result = send_message_to_server('', 'initialize', { sdkLanguage: 'python' })
|
|
65
|
+
ChannelOwners::Playwright.from(result['playwright'])
|
|
38
66
|
end
|
|
39
67
|
|
|
40
|
-
def
|
|
41
|
-
|
|
68
|
+
def set_in_tracing(value)
|
|
69
|
+
if value
|
|
70
|
+
@tracing_count += 1
|
|
71
|
+
else
|
|
72
|
+
@tracing_count -= 1
|
|
73
|
+
end
|
|
42
74
|
end
|
|
43
75
|
|
|
44
|
-
def async_send_message_to_server(guid, method, params)
|
|
76
|
+
def async_send_message_to_server(guid, method, params, metadata: nil)
|
|
77
|
+
return if @closed_error
|
|
78
|
+
|
|
45
79
|
callback = Concurrent::Promises.resolvable_future
|
|
46
80
|
|
|
47
81
|
with_generated_id do |id|
|
|
48
82
|
# register callback promise object first.
|
|
49
83
|
# @see https://github.com/YusukeIwaki/puppeteer-ruby/pull/34
|
|
50
|
-
@callbacks[id] = callback
|
|
84
|
+
@callbacks_mutex.synchronize { @callbacks[id] = callback }
|
|
85
|
+
|
|
86
|
+
_metadata = {}
|
|
87
|
+
frames = []
|
|
88
|
+
if metadata
|
|
89
|
+
frames = metadata[:stack]
|
|
90
|
+
_metadata[:wallTime] = metadata[:wallTime]
|
|
91
|
+
_metadata[:apiName] = metadata[:apiName]
|
|
92
|
+
_metadata[:location] = metadata[:stack].first
|
|
93
|
+
_metadata[:internal] = !metadata[:apiName]
|
|
94
|
+
if metadata[:title]
|
|
95
|
+
_metadata[:title] = metadata[:title]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
_metadata.compact!
|
|
51
99
|
|
|
52
100
|
message = {
|
|
53
101
|
id: id,
|
|
54
102
|
guid: guid,
|
|
55
103
|
method: method,
|
|
56
104
|
params: replace_channels_with_guids(params),
|
|
105
|
+
metadata: _metadata,
|
|
57
106
|
}
|
|
107
|
+
|
|
58
108
|
begin
|
|
59
109
|
@transport.send_message(message)
|
|
60
110
|
rescue => err
|
|
61
|
-
@callbacks.delete(id)
|
|
111
|
+
@callbacks_mutex.synchronize { @callbacks.delete(id) }
|
|
62
112
|
callback.reject(err)
|
|
63
113
|
raise unless err.is_a?(Transport::AlreadyDisconnectedError)
|
|
64
114
|
end
|
|
115
|
+
|
|
116
|
+
if @tracing_count > 0 && !frames.empty? && guid != 'localUtils' && !remote?
|
|
117
|
+
@local_utils.add_stack_to_tracing_no_reply(id, frames)
|
|
118
|
+
end
|
|
65
119
|
end
|
|
66
120
|
|
|
67
121
|
callback
|
|
68
122
|
end
|
|
69
123
|
|
|
70
|
-
def send_message_to_server(guid, method, params)
|
|
71
|
-
async_send_message_to_server(guid, method, params).value!
|
|
124
|
+
def send_message_to_server(guid, method, params, metadata: nil)
|
|
125
|
+
async_send_message_to_server(guid, method, params, metadata: metadata).value!
|
|
72
126
|
end
|
|
73
127
|
|
|
74
128
|
private
|
|
@@ -79,35 +133,42 @@ module Playwright
|
|
|
79
133
|
# end
|
|
80
134
|
# ````
|
|
81
135
|
def with_generated_id(&block)
|
|
82
|
-
@
|
|
83
|
-
|
|
136
|
+
id = @callbacks_mutex.synchronize do
|
|
137
|
+
@last_id ||= 0
|
|
138
|
+
@last_id += 1
|
|
139
|
+
end
|
|
140
|
+
block.call(id)
|
|
84
141
|
end
|
|
85
142
|
|
|
86
143
|
# @param guid [String]
|
|
87
144
|
# @param parent [Playwright::ChannelOwner]
|
|
88
145
|
# @note This method should be used internally. Accessed via .send method from Playwright::ChannelOwner, so keep private!
|
|
89
146
|
def update_object_from_channel_owner(guid, parent)
|
|
90
|
-
@objects[guid] = parent
|
|
147
|
+
@objects_mutex.synchronize { @objects[guid] = parent }
|
|
91
148
|
end
|
|
92
149
|
|
|
93
150
|
# @param guid [String]
|
|
94
151
|
# @note This method should be used internally. Accessed via .send method from Playwright::ChannelOwner, so keep private!
|
|
95
152
|
def delete_object_from_channel_owner(guid)
|
|
96
|
-
@objects.delete(guid)
|
|
153
|
+
@objects_mutex.synchronize { @objects.delete(guid) }
|
|
97
154
|
end
|
|
98
155
|
|
|
99
156
|
def dispatch(msg)
|
|
157
|
+
return if @closed_error
|
|
158
|
+
|
|
100
159
|
id = msg['id']
|
|
101
160
|
if id
|
|
102
|
-
callback = @callbacks.delete(id)
|
|
161
|
+
callback = @callbacks_mutex.synchronize { @callbacks.delete(id) }
|
|
103
162
|
|
|
104
163
|
unless callback
|
|
105
164
|
raise "Cannot find command to respond: #{id}"
|
|
106
165
|
end
|
|
107
166
|
|
|
108
167
|
error = msg['error']
|
|
109
|
-
if error
|
|
110
|
-
|
|
168
|
+
if error && !msg['result']
|
|
169
|
+
parsed_error = ::Playwright::Error.parse(error['error'])
|
|
170
|
+
parsed_error.log = msg['log']
|
|
171
|
+
callback.reject(parsed_error)
|
|
111
172
|
else
|
|
112
173
|
result = replace_guids_with_channels(msg['result'])
|
|
113
174
|
callback.fulfill(result)
|
|
@@ -121,28 +182,37 @@ module Playwright
|
|
|
121
182
|
params = msg['params']
|
|
122
183
|
|
|
123
184
|
if method == "__create__"
|
|
124
|
-
create_remote_object(
|
|
185
|
+
remote_object = create_remote_object(
|
|
125
186
|
parent_guid: guid,
|
|
126
187
|
type: params["type"],
|
|
127
188
|
guid: params["guid"],
|
|
128
189
|
initializer: params["initializer"],
|
|
129
190
|
)
|
|
191
|
+
if remote_object.is_a?(ChannelOwners::LocalUtils)
|
|
192
|
+
@local_utils = remote_object
|
|
193
|
+
end
|
|
130
194
|
return
|
|
131
195
|
end
|
|
132
196
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
197
|
+
object = @objects_mutex.synchronize { @objects[guid] }
|
|
198
|
+
unless object
|
|
199
|
+
raise "Cannot find object to \"#{method}\": #{guid}"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if method == "__adopt__"
|
|
203
|
+
child = @objects_mutex.synchronize { @objects[params["guid"]] }
|
|
204
|
+
unless child
|
|
205
|
+
raise "Unknown new child: #{params['guid']}"
|
|
137
206
|
end
|
|
138
|
-
object.send(:
|
|
207
|
+
object.send(:adopt!, child)
|
|
139
208
|
return
|
|
140
209
|
end
|
|
141
210
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
211
|
+
if method == "__dispose__"
|
|
212
|
+
object.send(:dispose!, reason: params["reason"])
|
|
213
|
+
return
|
|
145
214
|
end
|
|
215
|
+
|
|
146
216
|
object.channel.emit(method, replace_guids_with_channels(params))
|
|
147
217
|
end
|
|
148
218
|
|
|
@@ -177,8 +247,9 @@ module Playwright
|
|
|
177
247
|
|
|
178
248
|
if payload.is_a?(Hash)
|
|
179
249
|
guid = payload['guid']
|
|
180
|
-
if guid
|
|
181
|
-
|
|
250
|
+
if guid
|
|
251
|
+
object = @objects_mutex.synchronize { @objects[guid] }
|
|
252
|
+
return object.channel if object
|
|
182
253
|
end
|
|
183
254
|
|
|
184
255
|
return payload.map { |k, v| [k, replace_guids_with_channels(v)] }.to_h
|
|
@@ -189,38 +260,15 @@ module Playwright
|
|
|
189
260
|
|
|
190
261
|
# @return [Playwright::ChannelOwner|nil]
|
|
191
262
|
def create_remote_object(parent_guid:, type:, guid:, initializer:)
|
|
192
|
-
parent = @objects[parent_guid]
|
|
263
|
+
parent = @objects_mutex.synchronize { @objects[parent_guid] }
|
|
193
264
|
unless parent
|
|
194
265
|
raise "Cannot find parent object #{parent_guid} to create #{guid}"
|
|
195
266
|
end
|
|
196
267
|
initializer = replace_guids_with_channels(initializer)
|
|
197
268
|
|
|
198
|
-
class_name = case type
|
|
199
|
-
when 'Browser'
|
|
200
|
-
case initializer['name']
|
|
201
|
-
when 'chromium'
|
|
202
|
-
'ChromiumBrowser'
|
|
203
|
-
when 'webkit'
|
|
204
|
-
'WebKitBrowser'
|
|
205
|
-
when 'firefox'
|
|
206
|
-
'FirefoxBrowser'
|
|
207
|
-
else
|
|
208
|
-
'Browser'
|
|
209
|
-
end
|
|
210
|
-
when 'BrowserContext'
|
|
211
|
-
browser_name = initializer['browserName']
|
|
212
|
-
if browser_name == 'chromium'
|
|
213
|
-
'ChromiumBrowserContext'
|
|
214
|
-
else
|
|
215
|
-
'BrowserContext'
|
|
216
|
-
end
|
|
217
|
-
else
|
|
218
|
-
type
|
|
219
|
-
end
|
|
220
|
-
|
|
221
269
|
result =
|
|
222
270
|
begin
|
|
223
|
-
ChannelOwners.const_get(
|
|
271
|
+
ChannelOwners.const_get(type).new(
|
|
224
272
|
parent,
|
|
225
273
|
type,
|
|
226
274
|
guid,
|
|
@@ -230,7 +278,7 @@ module Playwright
|
|
|
230
278
|
raise "Missing type #{type}"
|
|
231
279
|
end
|
|
232
280
|
|
|
233
|
-
callback = @waiting_for_object.delete(guid)
|
|
281
|
+
callback = @objects_mutex.synchronize { @waiting_for_object.delete(guid) }
|
|
234
282
|
callback&.fulfill(result)
|
|
235
283
|
|
|
236
284
|
result
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Playwright
|
|
2
|
+
define_api_implementation :ConsoleMessageImpl do
|
|
3
|
+
def initialize(event, page, worker)
|
|
4
|
+
@event = event
|
|
5
|
+
@page = page
|
|
6
|
+
@worker = worker
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :page, :worker
|
|
10
|
+
|
|
11
|
+
def type
|
|
12
|
+
@event['type']
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def text
|
|
16
|
+
@event['text']
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def args
|
|
20
|
+
@event['args']&.map do |arg|
|
|
21
|
+
ChannelOwner.from(arg)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def location
|
|
26
|
+
@event['location']
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|