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.
Files changed (209) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +4 -0
  3. data/CLAUDE/api_generation.md +28 -0
  4. data/CLAUDE/ci_expectations.md +23 -0
  5. data/CLAUDE/gem_release_flow.md +39 -0
  6. data/CLAUDE/past_upgrade_pr_patterns.md +42 -0
  7. data/CLAUDE/playwright_upgrade_workflow.md +35 -0
  8. data/CLAUDE/rspec_debugging.md +30 -0
  9. data/CLAUDE/unimplemented_examples.md +18 -0
  10. data/CLAUDE.md +32 -0
  11. data/CONTRIBUTING.md +5 -0
  12. data/README.md +60 -16
  13. data/documentation/README.md +33 -0
  14. data/documentation/babel.config.js +3 -0
  15. data/documentation/docs/api/api_request.md +7 -0
  16. data/documentation/docs/api/api_request_context.md +298 -0
  17. data/documentation/docs/api/api_response.md +114 -0
  18. data/documentation/docs/api/browser.md +237 -0
  19. data/documentation/docs/api/browser_context.md +503 -0
  20. data/documentation/docs/api/browser_type.md +184 -0
  21. data/documentation/docs/api/cdp_session.md +44 -0
  22. data/documentation/docs/api/clock.md +154 -0
  23. data/documentation/docs/api/console_message.md +85 -0
  24. data/documentation/docs/api/dialog.md +84 -0
  25. data/documentation/docs/api/download.md +111 -0
  26. data/documentation/docs/api/element_handle.md +694 -0
  27. data/documentation/docs/api/experimental/_category_.yml +3 -0
  28. data/documentation/docs/api/experimental/android.md +42 -0
  29. data/documentation/docs/api/experimental/android_device.md +109 -0
  30. data/documentation/docs/api/experimental/android_input.md +43 -0
  31. data/documentation/docs/api/experimental/android_socket.md +7 -0
  32. data/documentation/docs/api/experimental/android_web_view.md +7 -0
  33. data/documentation/docs/api/file_chooser.md +53 -0
  34. data/documentation/docs/api/frame.md +1218 -0
  35. data/documentation/docs/api/frame_locator.md +348 -0
  36. data/documentation/docs/api/js_handle.md +121 -0
  37. data/documentation/docs/api/keyboard.md +170 -0
  38. data/documentation/docs/api/locator.md +1495 -0
  39. data/documentation/docs/api/locator_assertions.md +827 -0
  40. data/documentation/docs/api/mouse.md +86 -0
  41. data/documentation/docs/api/page.md +1946 -0
  42. data/documentation/docs/api/page_assertions.md +65 -0
  43. data/documentation/docs/api/playwright.md +66 -0
  44. data/documentation/docs/api/request.md +255 -0
  45. data/documentation/docs/api/response.md +176 -0
  46. data/documentation/docs/api/route.md +205 -0
  47. data/documentation/docs/api/selectors.md +63 -0
  48. data/documentation/docs/api/touchscreen.md +22 -0
  49. data/documentation/docs/api/tracing.md +129 -0
  50. data/documentation/docs/api/web_socket.md +51 -0
  51. data/documentation/docs/api/worker.md +83 -0
  52. data/documentation/docs/article/api_coverage.mdx +11 -0
  53. data/documentation/docs/article/getting_started.md +161 -0
  54. data/documentation/docs/article/guides/_category_.yml +3 -0
  55. data/documentation/docs/article/guides/download_playwright_driver.md +55 -0
  56. data/documentation/docs/article/guides/inspector.md +31 -0
  57. data/documentation/docs/article/guides/launch_browser.md +121 -0
  58. data/documentation/docs/article/guides/playwright_on_alpine_linux.md +112 -0
  59. data/documentation/docs/article/guides/rails_integration.md +278 -0
  60. data/documentation/docs/article/guides/rails_integration_with_null_driver.md +145 -0
  61. data/documentation/docs/article/guides/recording_video.md +79 -0
  62. data/documentation/docs/article/guides/rspec_integration.md +59 -0
  63. data/documentation/docs/article/guides/semi_automation.md +71 -0
  64. data/documentation/docs/article/guides/use_storage_state.md +78 -0
  65. data/documentation/docs/include/api_coverage.md +671 -0
  66. data/documentation/docusaurus.config.js +114 -0
  67. data/documentation/package.json +39 -0
  68. data/documentation/sidebars.js +15 -0
  69. data/documentation/src/components/HomepageFeatures.js +61 -0
  70. data/documentation/src/components/HomepageFeatures.module.css +13 -0
  71. data/documentation/src/css/custom.css +44 -0
  72. data/documentation/src/pages/index.js +49 -0
  73. data/documentation/src/pages/index.module.css +41 -0
  74. data/documentation/src/pages/markdown-page.md +7 -0
  75. data/documentation/static/.nojekyll +0 -0
  76. data/documentation/static/img/playwright-logo.svg +9 -0
  77. data/documentation/static/img/playwright-ruby-client.png +0 -0
  78. data/documentation/static/img/undraw_dropdown_menu.svg +1 -0
  79. data/documentation/static/img/undraw_web_development.svg +1 -0
  80. data/documentation/static/img/undraw_windows.svg +1 -0
  81. data/documentation/yarn.lock +9005 -0
  82. data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
  83. data/lib/playwright/api_implementation.rb +18 -0
  84. data/lib/playwright/api_response_impl.rb +77 -0
  85. data/lib/playwright/channel.rb +62 -1
  86. data/lib/playwright/channel_owner.rb +70 -7
  87. data/lib/playwright/channel_owners/android.rb +16 -3
  88. data/lib/playwright/channel_owners/android_device.rb +22 -66
  89. data/lib/playwright/channel_owners/api_request_context.rb +247 -0
  90. data/lib/playwright/channel_owners/artifact.rb +40 -0
  91. data/lib/playwright/channel_owners/binding_call.rb +70 -0
  92. data/lib/playwright/channel_owners/browser.rb +114 -22
  93. data/lib/playwright/channel_owners/browser_context.rb +589 -15
  94. data/lib/playwright/channel_owners/browser_type.rb +90 -1
  95. data/lib/playwright/channel_owners/cdp_session.rb +19 -0
  96. data/lib/playwright/channel_owners/dialog.rb +32 -0
  97. data/lib/playwright/channel_owners/element_handle.rb +107 -43
  98. data/lib/playwright/channel_owners/fetch_request.rb +8 -0
  99. data/lib/playwright/channel_owners/frame.rb +334 -104
  100. data/lib/playwright/channel_owners/js_handle.rb +9 -13
  101. data/lib/playwright/channel_owners/local_utils.rb +82 -0
  102. data/lib/playwright/channel_owners/page.rb +778 -95
  103. data/lib/playwright/channel_owners/playwright.rb +25 -30
  104. data/lib/playwright/channel_owners/request.rb +120 -18
  105. data/lib/playwright/channel_owners/response.rb +113 -0
  106. data/lib/playwright/channel_owners/route.rb +181 -0
  107. data/lib/playwright/channel_owners/stream.rb +30 -0
  108. data/lib/playwright/channel_owners/tracing.rb +117 -0
  109. data/lib/playwright/channel_owners/web_socket.rb +96 -0
  110. data/lib/playwright/channel_owners/worker.rb +46 -0
  111. data/lib/playwright/channel_owners/writable_stream.rb +14 -0
  112. data/lib/playwright/clock_impl.rb +67 -0
  113. data/lib/playwright/connection.rb +111 -63
  114. data/lib/playwright/console_message_impl.rb +29 -0
  115. data/lib/playwright/download_impl.rb +32 -0
  116. data/lib/playwright/errors.rb +42 -5
  117. data/lib/playwright/event_emitter.rb +17 -3
  118. data/lib/playwright/event_emitter_proxy.rb +49 -0
  119. data/lib/playwright/events.rb +10 -5
  120. data/lib/playwright/file_chooser_impl.rb +24 -0
  121. data/lib/playwright/frame_locator_impl.rb +66 -0
  122. data/lib/playwright/har_router.rb +89 -0
  123. data/lib/playwright/http_headers.rb +14 -0
  124. data/lib/playwright/input_files.rb +102 -15
  125. data/lib/playwright/javascript/expression.rb +7 -11
  126. data/lib/playwright/javascript/regex.rb +23 -0
  127. data/lib/playwright/javascript/source_url.rb +16 -0
  128. data/lib/playwright/javascript/value_parser.rb +108 -19
  129. data/lib/playwright/javascript/value_serializer.rb +47 -8
  130. data/lib/playwright/javascript/visitor_info.rb +26 -0
  131. data/lib/playwright/javascript.rb +2 -10
  132. data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
  133. data/lib/playwright/locator_assertions_impl.rb +571 -0
  134. data/lib/playwright/locator_impl.rb +544 -0
  135. data/lib/playwright/locator_utils.rb +136 -0
  136. data/lib/playwright/mouse_impl.rb +57 -0
  137. data/lib/playwright/page_assertions_impl.rb +154 -0
  138. data/lib/playwright/playwright_api.rb +102 -30
  139. data/lib/playwright/raw_headers.rb +61 -0
  140. data/lib/playwright/route_handler.rb +78 -0
  141. data/lib/playwright/select_option_values.rb +34 -13
  142. data/lib/playwright/selectors_impl.rb +45 -0
  143. data/lib/playwright/test.rb +102 -0
  144. data/lib/playwright/timeout_settings.rb +9 -4
  145. data/lib/playwright/touchscreen_impl.rb +14 -0
  146. data/lib/playwright/transport.rb +61 -10
  147. data/lib/playwright/url_matcher.rb +24 -2
  148. data/lib/playwright/utils.rb +48 -13
  149. data/lib/playwright/version.rb +2 -1
  150. data/lib/playwright/video.rb +54 -0
  151. data/lib/playwright/waiter.rb +166 -0
  152. data/lib/playwright/web_socket_client.rb +167 -0
  153. data/lib/playwright/web_socket_transport.rb +116 -0
  154. data/lib/playwright.rb +188 -11
  155. data/lib/playwright_api/android.rb +46 -11
  156. data/lib/playwright_api/android_device.rb +182 -31
  157. data/lib/playwright_api/android_input.rb +22 -13
  158. data/lib/playwright_api/android_socket.rb +18 -0
  159. data/lib/playwright_api/android_web_view.rb +24 -0
  160. data/lib/playwright_api/api_request.rb +26 -0
  161. data/lib/playwright_api/api_request_context.rb +311 -0
  162. data/lib/playwright_api/api_response.rb +92 -0
  163. data/lib/playwright_api/browser.rb +116 -103
  164. data/lib/playwright_api/browser_context.rb +290 -389
  165. data/lib/playwright_api/browser_type.rb +96 -118
  166. data/lib/playwright_api/cdp_session.rb +36 -39
  167. data/lib/playwright_api/clock.rb +121 -0
  168. data/lib/playwright_api/console_message.rb +35 -19
  169. data/lib/playwright_api/dialog.rb +53 -50
  170. data/lib/playwright_api/download.rb +49 -43
  171. data/lib/playwright_api/element_handle.rb +354 -402
  172. data/lib/playwright_api/file_chooser.rb +15 -18
  173. data/lib/playwright_api/frame.rb +703 -603
  174. data/lib/playwright_api/frame_locator.rb +285 -0
  175. data/lib/playwright_api/js_handle.rb +50 -76
  176. data/lib/playwright_api/keyboard.rb +67 -146
  177. data/lib/playwright_api/locator.rb +1304 -0
  178. data/lib/playwright_api/locator_assertions.rb +704 -0
  179. data/lib/playwright_api/mouse.rb +23 -29
  180. data/lib/playwright_api/page.rb +1196 -1176
  181. data/lib/playwright_api/page_assertions.rb +60 -0
  182. data/lib/playwright_api/playwright.rb +54 -122
  183. data/lib/playwright_api/request.rb +112 -74
  184. data/lib/playwright_api/response.rb +92 -20
  185. data/lib/playwright_api/route.rb +152 -62
  186. data/lib/playwright_api/selectors.rb +47 -61
  187. data/lib/playwright_api/touchscreen.rb +8 -2
  188. data/lib/playwright_api/tracing.rb +128 -0
  189. data/lib/playwright_api/web_socket.rb +43 -5
  190. data/lib/playwright_api/worker.rb +74 -34
  191. data/playwright.gemspec +14 -9
  192. data/sig/playwright.rbs +658 -0
  193. metadata +216 -50
  194. data/docs/api_coverage.md +0 -354
  195. data/lib/playwright/channel_owners/chromium_browser.rb +0 -8
  196. data/lib/playwright/channel_owners/chromium_browser_context.rb +0 -8
  197. data/lib/playwright/channel_owners/console_message.rb +0 -21
  198. data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
  199. data/lib/playwright/channel_owners/selectors.rb +0 -4
  200. data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
  201. data/lib/playwright/input_type.rb +0 -19
  202. data/lib/playwright/input_types/mouse.rb +0 -4
  203. data/lib/playwright/input_types/touchscreen.rb +0 -4
  204. data/lib/playwright/javascript/function.rb +0 -67
  205. data/lib/playwright/wait_helper.rb +0 -73
  206. data/lib/playwright_api/accessibility.rb +0 -93
  207. data/lib/playwright_api/binding_call.rb +0 -23
  208. data/lib/playwright_api/chromium_browser_context.rb +0 -57
  209. 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/wait_helper'
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
- raise ArgumentError.new("block must be provided") unless block
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
- connection = Connection.new(playwright_cli_executable_path: playwright_cli_executable_path)
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
- playwright_promise = connection.async_wait_for_object_with_known_name('Playwright')
39
- Thread.new { connection.run }
40
- playwright = PlaywrightApi.from_channel_owner(playwright_promise.value!)
41
- begin
42
- block.call(playwright)
43
- ensure
44
- connection.stop
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
- # @nodoc
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 devices
7
- wrap_impl(@impl.devices)
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 after_initialize
12
- wrap_impl(@impl.after_initialize)
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
- wrap_impl(@impl.on(unwrap_impl(event), unwrap_impl(callback)))
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
- wrap_impl(@impl.off(unwrap_impl(event), unwrap_impl(callback)))
61
+ event_emitter_proxy.off(event, callback)
25
62
  end
26
63
 
27
- # -- inherited from EventEmitter --
28
- # @nodoc
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