playwright-ruby-client 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -0
  3. data/documentation/docs/api/element_handle.md +11 -2
  4. data/documentation/docs/api/frame.md +15 -1
  5. data/documentation/docs/api/page.md +15 -1
  6. data/documentation/docs/api/response.md +16 -0
  7. data/documentation/docs/include/api_coverage.md +6 -0
  8. data/lib/playwright.rb +36 -3
  9. data/lib/playwright/channel_owners/element_handle.rb +11 -4
  10. data/lib/playwright/channel_owners/frame.rb +14 -2
  11. data/lib/playwright/channel_owners/page.rb +13 -2
  12. data/lib/playwright/channel_owners/response.rb +8 -0
  13. data/lib/playwright/connection.rb +2 -4
  14. data/lib/playwright/transport.rb +0 -1
  15. data/lib/playwright/version.rb +2 -2
  16. data/lib/playwright/web_socket_client.rb +164 -0
  17. data/lib/playwright/web_socket_transport.rb +104 -0
  18. data/lib/playwright_api/android.rb +6 -6
  19. data/lib/playwright_api/android_device.rb +8 -8
  20. data/lib/playwright_api/browser.rb +6 -6
  21. data/lib/playwright_api/browser_context.rb +6 -6
  22. data/lib/playwright_api/browser_type.rb +6 -6
  23. data/lib/playwright_api/cdp_session.rb +12 -12
  24. data/lib/playwright_api/console_message.rb +6 -6
  25. data/lib/playwright_api/dialog.rb +6 -6
  26. data/lib/playwright_api/element_handle.rb +17 -11
  27. data/lib/playwright_api/frame.rb +20 -9
  28. data/lib/playwright_api/js_handle.rb +6 -6
  29. data/lib/playwright_api/page.rb +20 -9
  30. data/lib/playwright_api/playwright.rb +6 -6
  31. data/lib/playwright_api/request.rb +6 -6
  32. data/lib/playwright_api/response.rb +16 -6
  33. data/lib/playwright_api/route.rb +11 -6
  34. data/lib/playwright_api/selectors.rb +6 -6
  35. data/lib/playwright_api/web_socket.rb +6 -6
  36. data/lib/playwright_api/worker.rb +6 -6
  37. metadata +6 -4
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '0.7.1'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.12.0'
4
+ VERSION = '0.8.0'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.13.0'
6
6
  end
@@ -0,0 +1,164 @@
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:)
66
+ @impl = DriverImpl.new(url)
67
+ @driver = ::WebSocket::Driver.client(@impl, max_length: max_payload_size)
68
+
69
+ setup
70
+ end
71
+
72
+ class TransportError < StandardError; end
73
+
74
+ private def setup
75
+ @ready_state = STATE_CONNECTING
76
+ @driver.on(:open) do
77
+ @ready_state = STATE_OPENED
78
+ handle_on_open
79
+ end
80
+ @driver.on(:close) do |event|
81
+ @ready_state = STATE_CLOSED
82
+ handle_on_close(reason: event.reason, code: event.code)
83
+ end
84
+ @driver.on(:error) do |event|
85
+ if !handle_on_error(error_message: event.message)
86
+ raise TransportError.new(event.message)
87
+ end
88
+ end
89
+ @driver.on(:message) do |event|
90
+ handle_on_message(event.data)
91
+ end
92
+ end
93
+
94
+ private def wait_for_data
95
+ @driver.parse(@impl.readpartial)
96
+ end
97
+
98
+ def start
99
+ @driver.start
100
+
101
+ Thread.new do
102
+ wait_for_data until @ready_state >= STATE_CLOSING
103
+ rescue EOFError
104
+ # Google Chrome was gone.
105
+ # We have nothing todo. Just finish polling.
106
+ if @ready_state < STATE_CLOSING
107
+ handle_on_close(reason: 'Going Away', code: 1001)
108
+ end
109
+ end
110
+ end
111
+
112
+ # @param message [String]
113
+ def send_text(message)
114
+ return if @ready_state >= STATE_CLOSING
115
+ @driver.text(message)
116
+ end
117
+
118
+ def close(code: 1000, reason: "")
119
+ return if @ready_state >= STATE_CLOSING
120
+ @ready_state = STATE_CLOSING
121
+ @driver.close(reason, code)
122
+ end
123
+
124
+ def on_open(&block)
125
+ @on_open = block
126
+ end
127
+
128
+ # @param block [Proc(reason: String, code: Numeric)]
129
+ def on_close(&block)
130
+ @on_close = block
131
+ end
132
+
133
+ # @param block [Proc(error_message: String)]
134
+ def on_error(&block)
135
+ @on_error = block
136
+ end
137
+
138
+ def on_message(&block)
139
+ @on_message = block
140
+ end
141
+
142
+ private def handle_on_open
143
+ @on_open&.call
144
+ end
145
+
146
+ private def handle_on_close(reason:, code:)
147
+ @on_close&.call(reason, code)
148
+ @impl.disconnect
149
+ end
150
+
151
+ private def handle_on_error(error_message:)
152
+ return false if @on_error.nil?
153
+
154
+ @on_error.call(error_message)
155
+ true
156
+ end
157
+
158
+ private def handle_on_message(data)
159
+ return if @ready_state != STATE_OPENED
160
+
161
+ @on_message&.call(data)
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,104 @@
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:)
10
+ @ws_endpoint = ws_endpoint
11
+ @debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
12
+ end
13
+
14
+ def on_message_received(&block)
15
+ @on_message = block
16
+ end
17
+
18
+ def on_driver_crashed(&block)
19
+ @on_driver_crashed = block
20
+ end
21
+
22
+ class AlreadyDisconnectedError < StandardError ; end
23
+
24
+ # @param message [Hash]
25
+ def send_message(message)
26
+ debug_send_message(message) if @debug
27
+ msg = JSON.dump(message)
28
+
29
+ @ws.send_text(msg)
30
+ rescue Errno::EPIPE, IOError
31
+ raise AlreadyDisconnectedError.new('send_message failed')
32
+ end
33
+
34
+ # Terminate playwright-cli driver.
35
+ def stop
36
+ return unless @ws
37
+
38
+ future = Concurrent::Promises.resolvable_future
39
+
40
+ @ws.on_close do
41
+ future.fulfill(nil)
42
+ end
43
+
44
+ begin
45
+ @ws.close
46
+ rescue EOFError => err
47
+ # ignore EOLError. The connection is already closed.
48
+ future.fulfill(err)
49
+ end
50
+
51
+ # Wait for closed actually.
52
+ future.value!
53
+ end
54
+
55
+ # Start `playwright-cli run-driver`
56
+ #
57
+ # @note This method blocks until playwright-cli exited. Consider using Thread or Future.
58
+ def async_run
59
+ ws = WebSocketClient.new(
60
+ url: @ws_endpoint,
61
+ max_payload_size: 256 * 1024 * 1024, # 256MB
62
+ )
63
+ promise = Concurrent::Promises.resolvable_future
64
+ ws.on_open do
65
+ promise.fulfill(ws)
66
+ end
67
+ ws.on_error do |error_message|
68
+ promise.reject(WebSocketClient::TransportError.new(error_message))
69
+ end
70
+
71
+ # Some messages can be sent just after start, before setting @ws.on_message
72
+ # So set this handler before ws.start.
73
+ ws.on_message do |data|
74
+ handle_on_message(data)
75
+ end
76
+
77
+ ws.start
78
+ @ws = promise.value!
79
+ @ws.on_error do |error|
80
+ puts "[WebSocketTransport] error: #{error}"
81
+ @on_driver_crashed&.call
82
+ end
83
+ rescue Errno::ECONNREFUSED => err
84
+ raise WebSocketClient::TransportError.new(err)
85
+ end
86
+
87
+ private
88
+
89
+ def handle_on_message(data)
90
+ obj = JSON.parse(data)
91
+
92
+ debug_recv_message(obj) if @debug
93
+ @on_message&.call(obj)
94
+ end
95
+
96
+ def debug_send_message(message)
97
+ puts "\x1b[33mSEND>\x1b[0m#{message}"
98
+ end
99
+
100
+ def debug_recv_message(message)
101
+ puts "\x1b[33mRECV>\x1b[0m#{message}"
102
+ end
103
+ end
104
+ end
@@ -25,20 +25,20 @@ module Playwright
25
25
 
26
26
  # -- inherited from EventEmitter --
27
27
  # @nodoc
28
- def once(event, callback)
29
- event_emitter_proxy.once(event, callback)
28
+ def off(event, callback)
29
+ event_emitter_proxy.off(event, callback)
30
30
  end
31
31
 
32
32
  # -- inherited from EventEmitter --
33
33
  # @nodoc
34
- def on(event, callback)
35
- event_emitter_proxy.on(event, callback)
34
+ def once(event, callback)
35
+ event_emitter_proxy.once(event, callback)
36
36
  end
37
37
 
38
38
  # -- inherited from EventEmitter --
39
39
  # @nodoc
40
- def off(event, callback)
41
- event_emitter_proxy.off(event, callback)
40
+ def on(event, callback)
41
+ event_emitter_proxy.on(event, callback)
42
42
  end
43
43
 
44
44
  private def event_emitter_proxy
@@ -161,14 +161,20 @@ module Playwright
161
161
  raise NotImplementedError.new('web_views is not implemented yet.')
162
162
  end
163
163
 
164
+ # @nodoc
165
+ def tap_on(selector, duration: nil, timeout: nil)
166
+ wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
167
+ end
168
+
164
169
  # @nodoc
165
170
  def tree
166
171
  wrap_impl(@impl.tree)
167
172
  end
168
173
 
174
+ # -- inherited from EventEmitter --
169
175
  # @nodoc
170
- def tap_on(selector, duration: nil, timeout: nil)
171
- wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
176
+ def off(event, callback)
177
+ event_emitter_proxy.off(event, callback)
172
178
  end
173
179
 
174
180
  # -- inherited from EventEmitter --
@@ -183,12 +189,6 @@ module Playwright
183
189
  event_emitter_proxy.on(event, callback)
184
190
  end
185
191
 
186
- # -- inherited from EventEmitter --
187
- # @nodoc
188
- def off(event, callback)
189
- event_emitter_proxy.off(event, callback)
190
- end
191
-
192
192
  private def event_emitter_proxy
193
193
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
194
194
  end
@@ -158,20 +158,20 @@ module Playwright
158
158
 
159
159
  # -- inherited from EventEmitter --
160
160
  # @nodoc
161
- def once(event, callback)
162
- event_emitter_proxy.once(event, callback)
161
+ def off(event, callback)
162
+ event_emitter_proxy.off(event, callback)
163
163
  end
164
164
 
165
165
  # -- inherited from EventEmitter --
166
166
  # @nodoc
167
- def on(event, callback)
168
- event_emitter_proxy.on(event, callback)
167
+ def once(event, callback)
168
+ event_emitter_proxy.once(event, callback)
169
169
  end
170
170
 
171
171
  # -- inherited from EventEmitter --
172
172
  # @nodoc
173
- def off(event, callback)
174
- event_emitter_proxy.off(event, callback)
173
+ def on(event, callback)
174
+ event_emitter_proxy.on(event, callback)
175
175
  end
176
176
 
177
177
  private def event_emitter_proxy
@@ -384,20 +384,20 @@ module Playwright
384
384
 
385
385
  # -- inherited from EventEmitter --
386
386
  # @nodoc
387
- def once(event, callback)
388
- event_emitter_proxy.once(event, callback)
387
+ def off(event, callback)
388
+ event_emitter_proxy.off(event, callback)
389
389
  end
390
390
 
391
391
  # -- inherited from EventEmitter --
392
392
  # @nodoc
393
- def on(event, callback)
394
- event_emitter_proxy.on(event, callback)
393
+ def once(event, callback)
394
+ event_emitter_proxy.once(event, callback)
395
395
  end
396
396
 
397
397
  # -- inherited from EventEmitter --
398
398
  # @nodoc
399
- def off(event, callback)
400
- event_emitter_proxy.off(event, callback)
399
+ def on(event, callback)
400
+ event_emitter_proxy.on(event, callback)
401
401
  end
402
402
 
403
403
  private def event_emitter_proxy
@@ -145,20 +145,20 @@ module Playwright
145
145
 
146
146
  # -- inherited from EventEmitter --
147
147
  # @nodoc
148
- def once(event, callback)
149
- event_emitter_proxy.once(event, callback)
148
+ def off(event, callback)
149
+ event_emitter_proxy.off(event, callback)
150
150
  end
151
151
 
152
152
  # -- inherited from EventEmitter --
153
153
  # @nodoc
154
- def on(event, callback)
155
- event_emitter_proxy.on(event, callback)
154
+ def once(event, callback)
155
+ event_emitter_proxy.once(event, callback)
156
156
  end
157
157
 
158
158
  # -- inherited from EventEmitter --
159
159
  # @nodoc
160
- def off(event, callback)
161
- event_emitter_proxy.off(event, callback)
160
+ def on(event, callback)
161
+ event_emitter_proxy.on(event, callback)
162
162
  end
163
163
 
164
164
  private def event_emitter_proxy
@@ -13,12 +13,12 @@ module Playwright
13
13
  #
14
14
  # ```python sync
15
15
  # client = page.context().new_cdp_session(page)
16
- # client.send("animation.enable")
17
- # client.on("animation.animation_created", lambda: print("animation created!"))
18
- # response = client.send("animation.get_playback_rate")
19
- # print("playback rate is " + response["playback_rate"])
20
- # client.send("animation.set_playback_rate", {
21
- # playback_rate: response["playback_rate"] / 2
16
+ # client.send("Animation.enable")
17
+ # client.on("Animation.animationCreated", lambda: print("animation created!"))
18
+ # response = client.send("Animation.getPlaybackRate")
19
+ # print("playback rate is " + str(response["playbackRate"]))
20
+ # client.send("Animation.setPlaybackRate", {
21
+ # playbackRate: response["playbackRate"] / 2
22
22
  # })
23
23
  # ```
24
24
  class CDPSession < PlaywrightApi
@@ -35,20 +35,20 @@ module Playwright
35
35
 
36
36
  # -- inherited from EventEmitter --
37
37
  # @nodoc
38
- def once(event, callback)
39
- event_emitter_proxy.once(event, callback)
38
+ def off(event, callback)
39
+ event_emitter_proxy.off(event, callback)
40
40
  end
41
41
 
42
42
  # -- inherited from EventEmitter --
43
43
  # @nodoc
44
- def on(event, callback)
45
- event_emitter_proxy.on(event, callback)
44
+ def once(event, callback)
45
+ event_emitter_proxy.once(event, callback)
46
46
  end
47
47
 
48
48
  # -- inherited from EventEmitter --
49
49
  # @nodoc
50
- def off(event, callback)
51
- event_emitter_proxy.off(event, callback)
50
+ def on(event, callback)
51
+ event_emitter_proxy.on(event, callback)
52
52
  end
53
53
 
54
54
  private def event_emitter_proxy