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,167 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
require 'socket'
|
|
3
|
+
|
|
4
|
+
begin
|
|
5
|
+
require 'websocket/driver'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
raise "websocket-driver is required. Add `gem 'websocket-driver'` to your Gemfile"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# ref: https://github.com/rails/rails/blob/master/actioncable/lib/action_cable/connection/client_socket.rb
|
|
11
|
+
# ref: https://github.com/cavalle/chrome_remote/blob/master/lib/chrome_remote/web_socket_client.rb
|
|
12
|
+
module Playwright
|
|
13
|
+
class WebSocketClient
|
|
14
|
+
class SecureSocketFactory
|
|
15
|
+
def initialize(host, port)
|
|
16
|
+
@host = host
|
|
17
|
+
@port = port || 443
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create
|
|
21
|
+
tcp_socket = TCPSocket.new(@host, @port)
|
|
22
|
+
OpenSSL::SSL::SSLSocket.new(tcp_socket).tap(&:connect)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class DriverImpl # providing #url, #write(string)
|
|
27
|
+
def initialize(url)
|
|
28
|
+
@url = url
|
|
29
|
+
|
|
30
|
+
endpoint = URI.parse(url)
|
|
31
|
+
@socket =
|
|
32
|
+
if endpoint.scheme == 'wss'
|
|
33
|
+
SecureSocketFactory.new(endpoint.host, endpoint.port).create
|
|
34
|
+
else
|
|
35
|
+
TCPSocket.new(endpoint.host, endpoint.port)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_reader :url
|
|
40
|
+
|
|
41
|
+
def write(data)
|
|
42
|
+
@socket.write(data)
|
|
43
|
+
rescue Errno::EPIPE
|
|
44
|
+
raise EOFError.new('already closed')
|
|
45
|
+
rescue Errno::ECONNRESET
|
|
46
|
+
raise EOFError.new('closed by remote')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def readpartial(maxlen = 1024)
|
|
50
|
+
@socket.readpartial(maxlen)
|
|
51
|
+
rescue Errno::ECONNRESET
|
|
52
|
+
raise EOFError.new('closed by remote')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def disconnect
|
|
56
|
+
@socket.close
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
STATE_CONNECTING = 0
|
|
61
|
+
STATE_OPENED = 1
|
|
62
|
+
STATE_CLOSING = 2
|
|
63
|
+
STATE_CLOSED = 3
|
|
64
|
+
|
|
65
|
+
def initialize(url:, max_payload_size:, headers:)
|
|
66
|
+
@impl = DriverImpl.new(url)
|
|
67
|
+
@driver = ::WebSocket::Driver.client(@impl, max_length: max_payload_size)
|
|
68
|
+
headers.each do |key, value|
|
|
69
|
+
@driver.set_header(key, value)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
setup
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class TransportError < StandardError; end
|
|
76
|
+
|
|
77
|
+
private def setup
|
|
78
|
+
@ready_state = STATE_CONNECTING
|
|
79
|
+
@driver.on(:open) do
|
|
80
|
+
@ready_state = STATE_OPENED
|
|
81
|
+
handle_on_open
|
|
82
|
+
end
|
|
83
|
+
@driver.on(:close) do |event|
|
|
84
|
+
@ready_state = STATE_CLOSED
|
|
85
|
+
handle_on_close(reason: event.reason, code: event.code)
|
|
86
|
+
end
|
|
87
|
+
@driver.on(:error) do |event|
|
|
88
|
+
if !handle_on_error(error_message: event.message)
|
|
89
|
+
raise TransportError.new(event.message)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
@driver.on(:message) do |event|
|
|
93
|
+
handle_on_message(event.data)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private def wait_for_data
|
|
98
|
+
@driver.parse(@impl.readpartial)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def start
|
|
102
|
+
@driver.start
|
|
103
|
+
|
|
104
|
+
Thread.new do
|
|
105
|
+
wait_for_data until @ready_state >= STATE_CLOSING
|
|
106
|
+
rescue EOFError
|
|
107
|
+
# Google Chrome was gone.
|
|
108
|
+
# We have nothing todo. Just finish polling.
|
|
109
|
+
if @ready_state < STATE_CLOSING
|
|
110
|
+
handle_on_close(reason: 'Going Away', code: 1001)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @param message [String]
|
|
116
|
+
def send_text(message)
|
|
117
|
+
return if @ready_state >= STATE_CLOSING
|
|
118
|
+
@driver.text(message)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def close(code: 1000, reason: "")
|
|
122
|
+
return if @ready_state >= STATE_CLOSING
|
|
123
|
+
@ready_state = STATE_CLOSING
|
|
124
|
+
@driver.close(reason, code)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def on_open(&block)
|
|
128
|
+
@on_open = block
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @param block [Proc(reason: String, code: Numeric)]
|
|
132
|
+
def on_close(&block)
|
|
133
|
+
@on_close = block
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @param block [Proc(error_message: String)]
|
|
137
|
+
def on_error(&block)
|
|
138
|
+
@on_error = block
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def on_message(&block)
|
|
142
|
+
@on_message = block
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private def handle_on_open
|
|
146
|
+
@on_open&.call
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private def handle_on_close(reason:, code:)
|
|
150
|
+
@on_close&.call(reason, code)
|
|
151
|
+
@impl.disconnect
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private def handle_on_error(error_message:)
|
|
155
|
+
return false if @on_error.nil?
|
|
156
|
+
|
|
157
|
+
@on_error.call(error_message)
|
|
158
|
+
true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private def handle_on_message(data)
|
|
162
|
+
return if @ready_state != STATE_OPENED
|
|
163
|
+
|
|
164
|
+
@on_message&.call(data)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Playwright
|
|
6
|
+
# ref: https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_transport.py
|
|
7
|
+
class WebSocketTransport
|
|
8
|
+
# @param ws_endpoint [String] EndpointURL of WebSocket
|
|
9
|
+
def initialize(ws_endpoint:, headers: {})
|
|
10
|
+
@ws_endpoint = ws_endpoint
|
|
11
|
+
@headers = headers
|
|
12
|
+
@debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def on_message_received(&block)
|
|
16
|
+
@on_message = block
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def on_driver_closed(&block)
|
|
20
|
+
@on_driver_closed = block
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def on_driver_crashed(&block)
|
|
24
|
+
@on_driver_crashed = block
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class AlreadyDisconnectedError < StandardError ; end
|
|
28
|
+
|
|
29
|
+
# @param message [Hash]
|
|
30
|
+
def send_message(message)
|
|
31
|
+
debug_send_message(message) if @debug
|
|
32
|
+
msg = JSON.dump(message)
|
|
33
|
+
|
|
34
|
+
@ws.send_text(msg)
|
|
35
|
+
rescue Errno::EPIPE, IOError
|
|
36
|
+
raise AlreadyDisconnectedError.new('send_message failed')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Terminate playwright-cli driver.
|
|
40
|
+
def stop
|
|
41
|
+
return unless @ws
|
|
42
|
+
|
|
43
|
+
future = Concurrent::Promises.resolvable_future
|
|
44
|
+
|
|
45
|
+
@ws.on_close do
|
|
46
|
+
future.fulfill(nil)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
@ws.close
|
|
51
|
+
rescue EOFError => err
|
|
52
|
+
# ignore EOLError. The connection is already closed.
|
|
53
|
+
future.fulfill(err)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Wait for closed actually.
|
|
57
|
+
future.value!(2)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Start `playwright-cli run-driver`
|
|
61
|
+
#
|
|
62
|
+
# @note This method blocks until playwright-cli exited. Consider using Thread or Future.
|
|
63
|
+
def async_run
|
|
64
|
+
ws = WebSocketClient.new(
|
|
65
|
+
url: @ws_endpoint,
|
|
66
|
+
max_payload_size: 256 * 1024 * 1024, # 256MB
|
|
67
|
+
headers: @headers,
|
|
68
|
+
)
|
|
69
|
+
promise = Concurrent::Promises.resolvable_future
|
|
70
|
+
ws.on_open do
|
|
71
|
+
promise.fulfill(ws)
|
|
72
|
+
end
|
|
73
|
+
ws.on_error do |error_message|
|
|
74
|
+
promise.reject(WebSocketClient::TransportError.new(error_message))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Some messages can be sent just after start, before setting @ws.on_message
|
|
78
|
+
# So set this handler before ws.start.
|
|
79
|
+
ws.on_message do |data|
|
|
80
|
+
handle_on_message(data)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ws.start
|
|
84
|
+
@ws = promise.value!
|
|
85
|
+
@ws.on_close do |reason, code|
|
|
86
|
+
puts "[WebSocketTransport] closed with code: #{code}, reason: #{reason}"
|
|
87
|
+
@on_driver_closed&.call(reason, code)
|
|
88
|
+
end
|
|
89
|
+
@ws.on_error do |error|
|
|
90
|
+
puts "[WebSocketTransport] error: #{error}"
|
|
91
|
+
@on_driver_crashed&.call
|
|
92
|
+
end
|
|
93
|
+
rescue Errno::ECONNREFUSED => err
|
|
94
|
+
raise WebSocketClient::TransportError.new(err)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def handle_on_message(data)
|
|
100
|
+
obj = JSON.parse(data)
|
|
101
|
+
|
|
102
|
+
debug_recv_message(obj) if @debug
|
|
103
|
+
@on_message&.call(obj)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def debug_send_message(message)
|
|
107
|
+
metadata = message.delete(:metadata)
|
|
108
|
+
puts "\x1b[33mSEND>\x1b[0m#{message}"
|
|
109
|
+
message[:metadata] = metadata
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def debug_recv_message(message)
|
|
113
|
+
puts "\x1b[33mRECV>\x1b[0m#{message}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/playwright.rb
CHANGED
|
@@ -10,38 +10,215 @@ require 'concurrent'
|
|
|
10
10
|
require 'playwright/errors'
|
|
11
11
|
require 'playwright/events'
|
|
12
12
|
require 'playwright/event_emitter'
|
|
13
|
+
require 'playwright/event_emitter_proxy'
|
|
13
14
|
require 'playwright/javascript'
|
|
14
15
|
require 'playwright/utils'
|
|
15
16
|
|
|
17
|
+
require 'playwright/api_implementation'
|
|
16
18
|
require 'playwright/channel'
|
|
17
19
|
require 'playwright/channel_owner'
|
|
20
|
+
require 'playwright/http_headers'
|
|
18
21
|
require 'playwright/input_files'
|
|
19
|
-
require 'playwright/input_type'
|
|
20
22
|
require 'playwright/connection'
|
|
23
|
+
require 'playwright/har_router'
|
|
24
|
+
require 'playwright/raw_headers'
|
|
25
|
+
require 'playwright/route_handler'
|
|
21
26
|
require 'playwright/select_option_values'
|
|
22
27
|
require 'playwright/timeout_settings'
|
|
23
28
|
require 'playwright/transport'
|
|
24
29
|
require 'playwright/url_matcher'
|
|
25
30
|
require 'playwright/version'
|
|
26
|
-
require 'playwright/
|
|
31
|
+
require 'playwright/video'
|
|
32
|
+
require 'playwright/waiter'
|
|
27
33
|
|
|
28
34
|
require 'playwright/playwright_api'
|
|
29
35
|
# load generated files
|
|
30
36
|
Dir[File.join(__dir__, 'playwright_api', '*.rb')].each { |f| require f }
|
|
31
37
|
|
|
32
38
|
module Playwright
|
|
39
|
+
class Execution
|
|
40
|
+
def initialize(connection, playwright, browser = nil)
|
|
41
|
+
@connection = connection
|
|
42
|
+
@playwright = playwright
|
|
43
|
+
@browser = browser
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def stop
|
|
47
|
+
@browser&.close
|
|
48
|
+
@connection.stop
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
attr_reader :playwright, :browser
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class AndroidExecution
|
|
55
|
+
def initialize(connection, playwright, device = nil)
|
|
56
|
+
@connection = connection
|
|
57
|
+
@playwright = playwright
|
|
58
|
+
@device = device
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def stop
|
|
62
|
+
@device&.close
|
|
63
|
+
@connection.stop
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
attr_reader :playwright, :device
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Recommended to call this method with block.
|
|
71
|
+
#
|
|
72
|
+
# Playwright.create(...) do |playwright|
|
|
73
|
+
# browser = playwright.chromium.launch
|
|
74
|
+
# ...
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# When we use this method without block, an instance of Playwright::Execution is returned
|
|
78
|
+
# and we *must* call execution.stop on the end.
|
|
79
|
+
# The instance of playwright is available by calling execution.playwright
|
|
33
80
|
module_function def create(playwright_cli_executable_path:, &block)
|
|
34
|
-
|
|
81
|
+
transport = Transport.new(playwright_cli_executable_path: playwright_cli_executable_path)
|
|
82
|
+
connection = Connection.new(transport)
|
|
83
|
+
connection.async_run
|
|
84
|
+
|
|
85
|
+
execution =
|
|
86
|
+
begin
|
|
87
|
+
playwright = connection.initialize_playwright
|
|
88
|
+
Execution.new(connection, PlaywrightApi.wrap(playwright))
|
|
89
|
+
rescue
|
|
90
|
+
connection.stop
|
|
91
|
+
raise
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if block
|
|
95
|
+
begin
|
|
96
|
+
block.call(execution.playwright)
|
|
97
|
+
ensure
|
|
98
|
+
execution.stop
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
execution
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @Deprecated. Playwright >= 1.54 does not support this method.
|
|
106
|
+
module_function def connect_to_playwright_server(ws_endpoint, &block)
|
|
107
|
+
if Gem::Version.new(COMPATIBLE_PLAYWRIGHT_VERSION) >= Gem::Version.new('1.54.0')
|
|
108
|
+
raise NotImplementedError, 'connect_to_playwright_server is deprecated and not supported in Playwright >= 1.54. Use connect_to_browser_server instead.'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
require 'playwright/web_socket_client'
|
|
112
|
+
require 'playwright/web_socket_transport'
|
|
113
|
+
|
|
114
|
+
transport = WebSocketTransport.new(ws_endpoint: ws_endpoint)
|
|
115
|
+
connection = Connection.new(transport)
|
|
116
|
+
connection.async_run
|
|
117
|
+
|
|
118
|
+
execution =
|
|
119
|
+
begin
|
|
120
|
+
playwright = connection.initialize_playwright
|
|
121
|
+
Execution.new(connection, PlaywrightApi.wrap(playwright))
|
|
122
|
+
rescue
|
|
123
|
+
connection.stop
|
|
124
|
+
raise
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if block
|
|
128
|
+
begin
|
|
129
|
+
block.call(execution.playwright)
|
|
130
|
+
ensure
|
|
131
|
+
execution.stop
|
|
132
|
+
end
|
|
133
|
+
else
|
|
134
|
+
execution
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Connects to Playwright server, launched by `npx playwright launch-server --browser chromium` or `npx playwright run-server`
|
|
139
|
+
#
|
|
140
|
+
# Playwright.connect_to_browser_server('ws://....') do |browser|
|
|
141
|
+
# page = browser.new_page
|
|
142
|
+
# ...
|
|
143
|
+
# end
|
|
144
|
+
#
|
|
145
|
+
# @experimental
|
|
146
|
+
module_function def connect_to_browser_server(ws_endpoint, browser_type: 'chromium', &block)
|
|
147
|
+
known_browser_types = ['chromium', 'firefox', 'webkit']
|
|
148
|
+
unless known_browser_types.include?(browser_type)
|
|
149
|
+
raise ArgumentError, "Unknown browser type: #{browser_type}. Known types are: #{known_browser_types.join(', ')}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
require 'playwright/web_socket_client'
|
|
153
|
+
require 'playwright/web_socket_transport'
|
|
154
|
+
|
|
155
|
+
transport = WebSocketTransport.new(
|
|
156
|
+
ws_endpoint: ws_endpoint,
|
|
157
|
+
headers: { 'x-playwright-browser' => browser_type },
|
|
158
|
+
)
|
|
159
|
+
connection = Connection.new(transport)
|
|
160
|
+
connection.mark_as_remote
|
|
161
|
+
connection.async_run
|
|
162
|
+
|
|
163
|
+
execution =
|
|
164
|
+
begin
|
|
165
|
+
playwright = connection.initialize_playwright
|
|
166
|
+
browser = playwright.send(:pre_launched_browser)
|
|
167
|
+
browser.send(:connect_to_browser_type, playwright.send(browser_type), nil)
|
|
168
|
+
browser.send(:should_close_connection_on_close!)
|
|
169
|
+
Execution.new(connection, PlaywrightApi.wrap(playwright), PlaywrightApi.wrap(browser))
|
|
170
|
+
rescue
|
|
171
|
+
connection.stop
|
|
172
|
+
raise
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if block
|
|
176
|
+
begin
|
|
177
|
+
block.call(execution.browser)
|
|
178
|
+
ensure
|
|
179
|
+
execution.stop
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
execution
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Connects to Playwright server, launched by `npx playwright launch-server --browser _android` or `playwright._android.launchServer()`
|
|
187
|
+
#
|
|
188
|
+
# Playwright.connect_to_android_server('ws://....') do |browser|
|
|
189
|
+
# page = browser.new_page
|
|
190
|
+
# ...
|
|
191
|
+
# end
|
|
192
|
+
#
|
|
193
|
+
# @experimental
|
|
194
|
+
module_function def connect_to_android_server(ws_endpoint, &block)
|
|
195
|
+
require 'playwright/web_socket_client'
|
|
196
|
+
require 'playwright/web_socket_transport'
|
|
197
|
+
|
|
198
|
+
transport = WebSocketTransport.new(ws_endpoint: ws_endpoint)
|
|
199
|
+
connection = Connection.new(transport)
|
|
200
|
+
connection.mark_as_remote
|
|
201
|
+
connection.async_run
|
|
35
202
|
|
|
36
|
-
|
|
203
|
+
execution =
|
|
204
|
+
begin
|
|
205
|
+
playwright = connection.initialize_playwright
|
|
206
|
+
android_device = playwright.send(:pre_connected_android_device)
|
|
207
|
+
android_device.should_close_connection_on_close!
|
|
208
|
+
AndroidExecution.new(connection, PlaywrightApi.wrap(playwright), PlaywrightApi.wrap(android_device))
|
|
209
|
+
rescue
|
|
210
|
+
connection.stop
|
|
211
|
+
raise
|
|
212
|
+
end
|
|
37
213
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
214
|
+
if block
|
|
215
|
+
begin
|
|
216
|
+
block.call(execution.device)
|
|
217
|
+
ensure
|
|
218
|
+
execution.stop
|
|
219
|
+
end
|
|
220
|
+
else
|
|
221
|
+
execution
|
|
45
222
|
end
|
|
46
223
|
end
|
|
47
224
|
end
|
|
@@ -1,33 +1,68 @@
|
|
|
1
1
|
module Playwright
|
|
2
|
-
#
|
|
2
|
+
#
|
|
3
|
+
# Playwright has **experimental** support for Android automation. This includes Chrome for Android and Android WebView.
|
|
4
|
+
#
|
|
5
|
+
# *Requirements*
|
|
6
|
+
# - Android device or AVD Emulator.
|
|
7
|
+
# - [ADB daemon](https://developer.android.com/studio/command-line/adb) running and authenticated with your device. Typically running `adb devices` is all you need to do.
|
|
8
|
+
# - [`Chrome 87`](https://play.google.com/store/apps/details?id=com.android.chrome) or newer installed on the device
|
|
9
|
+
# - "Enable command line on non-rooted devices" enabled in `chrome://flags`.
|
|
10
|
+
#
|
|
11
|
+
# *Known limitations*
|
|
12
|
+
# - Raw USB operation is not yet supported, so you need ADB.
|
|
13
|
+
# - Device needs to be awake to produce screenshots. Enabling "Stay awake" developer mode will help.
|
|
14
|
+
# - We didn't run all the tests against the device, so not everything works.
|
|
15
|
+
#
|
|
16
|
+
# *How to run*
|
|
17
|
+
#
|
|
18
|
+
# An example of the Android automation script would be:
|
|
3
19
|
class Android < PlaywrightApi
|
|
4
20
|
|
|
21
|
+
#
|
|
22
|
+
# This methods attaches Playwright to an existing Android device.
|
|
23
|
+
# Use [`method: Android.launchServer`] to launch a new Android server instance.
|
|
24
|
+
def connect(wsEndpoint, headers: nil, slowMo: nil, timeout: nil)
|
|
25
|
+
raise NotImplementedError.new('connect is not implemented yet.')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# Returns the list of detected Android devices.
|
|
30
|
+
def devices(host: nil, omitDriverInstall: nil, port: nil)
|
|
31
|
+
wrap_impl(@impl.devices(host: unwrap_impl(host), omitDriverInstall: unwrap_impl(omitDriverInstall), port: unwrap_impl(port)))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# This setting will change the default maximum time for all the methods accepting `timeout` option.
|
|
36
|
+
def set_default_timeout(timeout)
|
|
37
|
+
wrap_impl(@impl.set_default_timeout(unwrap_impl(timeout)))
|
|
38
|
+
end
|
|
39
|
+
alias_method :default_timeout=, :set_default_timeout
|
|
40
|
+
|
|
5
41
|
# @nodoc
|
|
6
|
-
def
|
|
7
|
-
wrap_impl(@impl.
|
|
42
|
+
def set_default_navigation_timeout(timeout)
|
|
43
|
+
wrap_impl(@impl.set_default_navigation_timeout(unwrap_impl(timeout)))
|
|
8
44
|
end
|
|
9
45
|
|
|
46
|
+
# -- inherited from EventEmitter --
|
|
10
47
|
# @nodoc
|
|
11
|
-
def
|
|
12
|
-
|
|
48
|
+
def once(event, callback)
|
|
49
|
+
event_emitter_proxy.once(event, callback)
|
|
13
50
|
end
|
|
14
51
|
|
|
15
52
|
# -- inherited from EventEmitter --
|
|
16
53
|
# @nodoc
|
|
17
54
|
def on(event, callback)
|
|
18
|
-
|
|
55
|
+
event_emitter_proxy.on(event, callback)
|
|
19
56
|
end
|
|
20
57
|
|
|
21
58
|
# -- inherited from EventEmitter --
|
|
22
59
|
# @nodoc
|
|
23
60
|
def off(event, callback)
|
|
24
|
-
|
|
61
|
+
event_emitter_proxy.off(event, callback)
|
|
25
62
|
end
|
|
26
63
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def once(event, callback)
|
|
30
|
-
wrap_impl(@impl.once(unwrap_impl(event), unwrap_impl(callback)))
|
|
64
|
+
private def event_emitter_proxy
|
|
65
|
+
@event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
|
|
31
66
|
end
|
|
32
67
|
end
|
|
33
68
|
end
|