playwright-ruby-client 0.2.1 → 0.5.5
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/README.md +17 -5
- data/docs/api_coverage.md +116 -74
- data/lib/playwright.rb +48 -9
- data/lib/playwright/channel.rb +12 -2
- data/lib/playwright/channel_owners/artifact.rb +30 -0
- data/lib/playwright/channel_owners/binding_call.rb +3 -0
- data/lib/playwright/channel_owners/browser.rb +21 -0
- data/lib/playwright/channel_owners/browser_context.rb +154 -3
- data/lib/playwright/channel_owners/browser_type.rb +28 -0
- data/lib/playwright/channel_owners/dialog.rb +28 -0
- data/lib/playwright/channel_owners/element_handle.rb +7 -7
- data/lib/playwright/channel_owners/frame.rb +26 -5
- data/lib/playwright/channel_owners/js_handle.rb +2 -2
- data/lib/playwright/channel_owners/page.rb +125 -25
- data/lib/playwright/channel_owners/playwright.rb +24 -27
- data/lib/playwright/channel_owners/request.rb +26 -2
- data/lib/playwright/channel_owners/response.rb +60 -0
- data/lib/playwright/channel_owners/route.rb +78 -0
- data/lib/playwright/channel_owners/selectors.rb +19 -1
- data/lib/playwright/channel_owners/stream.rb +15 -0
- data/lib/playwright/connection.rb +11 -32
- data/lib/playwright/download.rb +27 -0
- data/lib/playwright/errors.rb +6 -0
- data/lib/playwright/events.rb +2 -5
- data/lib/playwright/keyboard_impl.rb +1 -1
- data/lib/playwright/mouse_impl.rb +41 -0
- data/lib/playwright/playwright_api.rb +3 -1
- data/lib/playwright/route_handler_entry.rb +28 -0
- data/lib/playwright/select_option_values.rb +31 -22
- data/lib/playwright/transport.rb +29 -7
- data/lib/playwright/url_matcher.rb +1 -1
- data/lib/playwright/utils.rb +9 -0
- data/lib/playwright/version.rb +1 -1
- data/lib/playwright/video.rb +51 -0
- data/lib/playwright/wait_helper.rb +2 -2
- data/lib/playwright_api/accessibility.rb +39 -1
- data/lib/playwright_api/android.rb +74 -2
- data/lib/playwright_api/android_device.rb +141 -23
- data/lib/playwright_api/android_input.rb +17 -13
- data/lib/playwright_api/android_socket.rb +16 -0
- data/lib/playwright_api/android_web_view.rb +21 -0
- data/lib/playwright_api/browser.rb +77 -2
- data/lib/playwright_api/browser_context.rb +178 -25
- data/lib/playwright_api/browser_type.rb +40 -9
- data/lib/playwright_api/dialog.rb +54 -7
- data/lib/playwright_api/element_handle.rb +105 -31
- data/lib/playwright_api/file_chooser.rb +6 -1
- data/lib/playwright_api/frame.rb +229 -36
- data/lib/playwright_api/js_handle.rb +23 -0
- data/lib/playwright_api/keyboard.rb +48 -1
- data/lib/playwright_api/mouse.rb +26 -5
- data/lib/playwright_api/page.rb +491 -81
- data/lib/playwright_api/playwright.rb +21 -4
- data/lib/playwright_api/request.rb +30 -2
- data/lib/playwright_api/response.rb +21 -11
- data/lib/playwright_api/route.rb +51 -5
- data/lib/playwright_api/selectors.rb +27 -1
- data/lib/playwright_api/touchscreen.rb +1 -1
- data/lib/playwright_api/worker.rb +25 -1
- data/playwright.gemspec +4 -2
- metadata +42 -14
- 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/download.rb +0 -27
- data/lib/playwright/channel_owners/firefox_browser.rb +0 -8
- data/lib/playwright/channel_owners/webkit_browser.rb +0 -8
- data/lib/playwright_api/binding_call.rb +0 -27
- data/lib/playwright_api/chromium_browser_context.rb +0 -59
- data/lib/playwright_api/download.rb +0 -95
- data/lib/playwright_api/video.rb +0 -24
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module Playwright
|
5
|
+
define_channel_owner :Route do
|
6
|
+
def request
|
7
|
+
ChannelOwners::Request.from(@initializer['request'])
|
8
|
+
end
|
9
|
+
|
10
|
+
def abort(errorCode: nil)
|
11
|
+
params = { errorCode: errorCode }.compact
|
12
|
+
@channel.async_send_message_to_server('abort', params)
|
13
|
+
end
|
14
|
+
|
15
|
+
def fulfill(
|
16
|
+
body: nil,
|
17
|
+
contentType: nil,
|
18
|
+
headers: nil,
|
19
|
+
path: nil,
|
20
|
+
status: nil)
|
21
|
+
params = {
|
22
|
+
contentType: contentType,
|
23
|
+
status: status,
|
24
|
+
}.compact
|
25
|
+
|
26
|
+
length = 0
|
27
|
+
content =
|
28
|
+
if body
|
29
|
+
body
|
30
|
+
elsif path
|
31
|
+
File.read(path)
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
param_headers = headers || {}
|
37
|
+
if contentType
|
38
|
+
param_headers['content-type'] = contentType
|
39
|
+
elsif path
|
40
|
+
param_headers['content-type'] = mime_type_for(path)
|
41
|
+
end
|
42
|
+
|
43
|
+
if content
|
44
|
+
if content.is_a?(String)
|
45
|
+
params[:body] = content
|
46
|
+
params[:isBase64] = false
|
47
|
+
else
|
48
|
+
params[:body] = Base64.strict_encode64(content)
|
49
|
+
params[:isBase64] = true
|
50
|
+
end
|
51
|
+
param_headers['content-length'] ||= content.length.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
params[:headers] = HttpHeaders.new(param_headers).as_serialized
|
55
|
+
|
56
|
+
@channel.async_send_message_to_server('fulfill', params)
|
57
|
+
end
|
58
|
+
|
59
|
+
def continue(headers: nil, method: nil, postData: nil, url: nil)
|
60
|
+
overrides = { url: url, method: method }.compact
|
61
|
+
|
62
|
+
if headers
|
63
|
+
overrides[:headers] = HttpHeaders.new(headers).as_serialized
|
64
|
+
end
|
65
|
+
|
66
|
+
if postData
|
67
|
+
overrides[:postData] = Base64.strict_encode64(postData)
|
68
|
+
end
|
69
|
+
|
70
|
+
@channel.async_send_message_to_server('continue', overrides)
|
71
|
+
end
|
72
|
+
|
73
|
+
private def mime_type_for(filepath)
|
74
|
+
mime_types = MIME::Types.type_for(filepath)
|
75
|
+
mime_types.first.to_s || 'application/octet-stream'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,4 +1,22 @@
|
|
1
1
|
module Playwright
|
2
2
|
# https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_selectors.py
|
3
|
-
define_channel_owner :Selectors
|
3
|
+
define_channel_owner :Selectors do
|
4
|
+
def register(name, contentScript: nil, path: nil, script: nil)
|
5
|
+
source =
|
6
|
+
if path
|
7
|
+
File.read(path)
|
8
|
+
elsif script
|
9
|
+
script
|
10
|
+
else
|
11
|
+
raise ArgumentError.new('Either path or script parameter must be specified')
|
12
|
+
end
|
13
|
+
params = { name: name, source: source }
|
14
|
+
if contentScript
|
15
|
+
params[:contentScript] = true
|
16
|
+
end
|
17
|
+
@channel.send_message_to_server('register', params)
|
18
|
+
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
4
22
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Playwright
|
4
|
+
define_channel_owner :Stream do
|
5
|
+
def save_as(path)
|
6
|
+
File.open(path, 'wb') do |f|
|
7
|
+
loop do
|
8
|
+
binary = @channel.send_message_to_server('read')
|
9
|
+
break if !binary || binary.length == 0
|
10
|
+
f.write(Base64.strict_decode64(binary))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -12,6 +12,12 @@ module Playwright
|
|
12
12
|
@transport.on_message_received do |message|
|
13
13
|
dispatch(message)
|
14
14
|
end
|
15
|
+
@transport.on_driver_crashed do
|
16
|
+
@callbacks.each_value do |callback|
|
17
|
+
callback.reject(::Playwright::DriverCrashedError.new)
|
18
|
+
end
|
19
|
+
raise ::Playwright::DriverCrashedError.new
|
20
|
+
end
|
15
21
|
|
16
22
|
@objects = {} # Hash[ guid => ChannelOwner ]
|
17
23
|
@waiting_for_object = {} # Hash[ guid => Promise<ChannelOwner> ]
|
@@ -19,26 +25,22 @@ module Playwright
|
|
19
25
|
@root_object = RootChannelOwner.new(self)
|
20
26
|
end
|
21
27
|
|
22
|
-
def
|
23
|
-
@transport.
|
28
|
+
def async_run
|
29
|
+
@transport.async_run
|
24
30
|
end
|
25
31
|
|
26
32
|
def stop
|
27
33
|
@transport.stop
|
28
34
|
end
|
29
35
|
|
30
|
-
def
|
36
|
+
def wait_for_object_with_known_name(guid)
|
31
37
|
if @objects[guid]
|
32
38
|
return @objects[guid]
|
33
39
|
end
|
34
40
|
|
35
41
|
callback = Concurrent::Promises.resolvable_future
|
36
42
|
@waiting_for_object[guid] = callback
|
37
|
-
callback
|
38
|
-
end
|
39
|
-
|
40
|
-
def wait_for_object_with_known_name(guid)
|
41
|
-
async_wait_for_object_with_known_name.value!
|
43
|
+
callback.value!
|
42
44
|
end
|
43
45
|
|
44
46
|
def async_send_message_to_server(guid, method, params)
|
@@ -195,32 +197,9 @@ module Playwright
|
|
195
197
|
end
|
196
198
|
initializer = replace_guids_with_channels(initializer)
|
197
199
|
|
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
200
|
result =
|
222
201
|
begin
|
223
|
-
ChannelOwners.const_get(
|
202
|
+
ChannelOwners.const_get(type).new(
|
224
203
|
parent,
|
225
204
|
type,
|
226
205
|
guid,
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Playwright
|
2
|
+
class Download
|
3
|
+
def initialize(url:, suggested_filename:, artifact:)
|
4
|
+
@url = url
|
5
|
+
@suggested_filename = suggested_filename
|
6
|
+
@artifact = artifact
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :url, :suggested_filename
|
10
|
+
|
11
|
+
def delete
|
12
|
+
@artifact.delete
|
13
|
+
end
|
14
|
+
|
15
|
+
def failure
|
16
|
+
@artifact.failure
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
@artifact.path_after_finished
|
21
|
+
end
|
22
|
+
|
23
|
+
def save_as(path)
|
24
|
+
@artifact.save_as(path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/playwright/errors.rb
CHANGED
@@ -27,6 +27,12 @@ module Playwright
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
class DriverCrashedError < StandardError
|
31
|
+
def initialize
|
32
|
+
super("[BUG] Playwright driver is crashed!")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
30
36
|
class TimeoutError < Error
|
31
37
|
def initialize(message:, stack: [])
|
32
38
|
super(name: 'TimeoutError', message: message, stack: stack)
|
data/lib/playwright/events.rb
CHANGED
@@ -24,8 +24,10 @@ end
|
|
24
24
|
},
|
25
25
|
|
26
26
|
BrowserContext: {
|
27
|
+
BackgroundPage: 'backgroundpage',
|
27
28
|
Close: 'close',
|
28
29
|
Page: 'page',
|
30
|
+
ServiceWorker: 'serviceworker',
|
29
31
|
},
|
30
32
|
|
31
33
|
BrowserServer: {
|
@@ -67,11 +69,6 @@ end
|
|
67
69
|
Close: 'close',
|
68
70
|
},
|
69
71
|
|
70
|
-
ChromiumBrowserContext: {
|
71
|
-
BackgroundPage: 'backgroundpage',
|
72
|
-
ServiceWorker: 'serviceworker',
|
73
|
-
},
|
74
|
-
|
75
72
|
ElectronApplication: {
|
76
73
|
Close: 'close',
|
77
74
|
Window: 'window',
|
@@ -3,5 +3,46 @@ module Playwright
|
|
3
3
|
def initialize(channel)
|
4
4
|
@channel = channel
|
5
5
|
end
|
6
|
+
|
7
|
+
def move(x, y, steps: nil)
|
8
|
+
params = { x: x, y: y, steps: steps }.compact
|
9
|
+
@channel.send_message_to_server('mouseMove', params)
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def down(button: nil, clickCount: nil)
|
14
|
+
params = { button: button, clickCount: clickCount }.compact
|
15
|
+
@channel.send_message_to_server('mouseDown', params)
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def up(button: nil, clickCount: nil)
|
20
|
+
params = { button: button, clickCount: clickCount }.compact
|
21
|
+
@channel.send_message_to_server('mouseUp', params)
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def click(
|
26
|
+
x,
|
27
|
+
y,
|
28
|
+
button: nil,
|
29
|
+
clickCount: nil,
|
30
|
+
delay: nil)
|
31
|
+
|
32
|
+
params = {
|
33
|
+
x: x,
|
34
|
+
y: y,
|
35
|
+
button: button,
|
36
|
+
clickCount: clickCount,
|
37
|
+
delay: delay,
|
38
|
+
}.compact
|
39
|
+
@channel.send_message_to_server('mouseClick', params)
|
40
|
+
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def dblclick(x, y, button: nil, delay: nil)
|
45
|
+
click(x, y, button: button, clickCount: 2, delay: delay)
|
46
|
+
end
|
6
47
|
end
|
7
48
|
end
|
@@ -124,7 +124,9 @@ module Playwright
|
|
124
124
|
end
|
125
125
|
|
126
126
|
private def unwrap_impl(object)
|
127
|
-
if object.is_a?(
|
127
|
+
if object.is_a?(Array)
|
128
|
+
object.map { |obj| unwrap_impl(obj) }
|
129
|
+
elsif object.is_a?(PlaywrightApi)
|
128
130
|
object.instance_variable_get(:@impl)
|
129
131
|
else
|
130
132
|
object
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Playwright
|
2
|
+
class RouteHandlerEntry
|
3
|
+
# @param url [String]
|
4
|
+
# @param handler [Proc]
|
5
|
+
def initialize(url, handler)
|
6
|
+
@url_value = url
|
7
|
+
@url_matcher = UrlMatcher.new(url)
|
8
|
+
@handler = handler
|
9
|
+
end
|
10
|
+
|
11
|
+
def handle(route, request)
|
12
|
+
if @url_matcher.match?(request.url)
|
13
|
+
@handler.call(route, request)
|
14
|
+
true
|
15
|
+
else
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def same_value?(url:, handler: nil)
|
21
|
+
if handler
|
22
|
+
@url_value == url && @handler == handler
|
23
|
+
else
|
24
|
+
@url_value == url
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,18 +1,30 @@
|
|
1
1
|
module Playwright
|
2
2
|
class SelectOptionValues
|
3
3
|
def initialize(element: nil, index: nil, value: nil, label: nil)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
params = {}
|
5
|
+
|
6
|
+
options = []
|
7
|
+
if value
|
8
|
+
options.concat(convert(:value, value))
|
9
|
+
end
|
10
|
+
|
11
|
+
if index
|
12
|
+
options.concat(convert(:index, index))
|
13
|
+
end
|
14
|
+
|
15
|
+
if label
|
16
|
+
options.concat(convert(:label, label))
|
17
|
+
end
|
18
|
+
|
19
|
+
unless options.empty?
|
20
|
+
params[:options] = options
|
21
|
+
end
|
22
|
+
|
23
|
+
if element
|
24
|
+
params[:elements] = convert(:element, element)
|
25
|
+
end
|
26
|
+
|
27
|
+
@params = params
|
16
28
|
end
|
17
29
|
|
18
30
|
# @return [Hash]
|
@@ -20,22 +32,19 @@ module Playwright
|
|
20
32
|
@params
|
21
33
|
end
|
22
34
|
|
23
|
-
private def convert(values)
|
24
|
-
return convert([values]) unless values.is_a?(Enumerable)
|
25
|
-
return
|
35
|
+
private def convert(key, values)
|
36
|
+
return convert(key, [values]) unless values.is_a?(Enumerable)
|
37
|
+
return [] if values.empty?
|
26
38
|
values.each_with_index do |value, index|
|
27
|
-
unless
|
39
|
+
unless value
|
28
40
|
raise ArgumentError.new("options[#{index}]: expected object, got null")
|
29
41
|
end
|
30
42
|
end
|
31
43
|
|
32
|
-
|
33
|
-
|
34
|
-
{ elements: values.map(&:channel) }
|
35
|
-
when String
|
36
|
-
{ options: values.map { |value| { value: value } } }
|
44
|
+
if key == :element
|
45
|
+
values.map(&:channel)
|
37
46
|
else
|
38
|
-
{
|
47
|
+
values.map { |value| { key => value } }
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
data/lib/playwright/transport.rb
CHANGED
@@ -12,39 +12,46 @@ module Playwright
|
|
12
12
|
def initialize(playwright_cli_executable_path:)
|
13
13
|
@driver_executable_path = playwright_cli_executable_path
|
14
14
|
@debug = ENV['DEBUG'].to_s == 'true' || ENV['DEBUG'].to_s == '1'
|
15
|
+
@mutex = Mutex.new
|
15
16
|
end
|
16
17
|
|
17
18
|
def on_message_received(&block)
|
18
19
|
@on_message = block
|
19
20
|
end
|
20
21
|
|
22
|
+
def on_driver_crashed(&block)
|
23
|
+
@on_driver_crashed = block
|
24
|
+
end
|
25
|
+
|
21
26
|
class AlreadyDisconnectedError < StandardError ; end
|
22
27
|
|
23
28
|
# @param message [Hash]
|
24
29
|
def send_message(message)
|
25
30
|
debug_send_message(message) if @debug
|
26
31
|
msg = JSON.dump(message)
|
27
|
-
@
|
28
|
-
|
29
|
-
|
32
|
+
@mutex.synchronize {
|
33
|
+
@stdin.write([msg.bytes.length].pack('V')) # unsigned 32bit, little endian, real byte size instead of chars
|
34
|
+
@stdin.write(msg) # write UTF-8 in binary mode as byte stream
|
35
|
+
}
|
36
|
+
rescue Errno::EPIPE, IOError
|
30
37
|
raise AlreadyDisconnectedError.new('send_message failed')
|
31
38
|
end
|
32
39
|
|
33
40
|
# Terminate playwright-cli driver.
|
34
41
|
def stop
|
35
42
|
[@stdin, @stdout, @stderr].each { |io| io.close unless io.closed? }
|
43
|
+
@thread&.terminate
|
36
44
|
end
|
37
45
|
|
38
46
|
# Start `playwright-cli run-driver`
|
39
47
|
#
|
40
48
|
# @note This method blocks until playwright-cli exited. Consider using Thread or Future.
|
41
|
-
def
|
42
|
-
@stdin, @stdout, @stderr, @thread = Open3.popen3(@driver_executable_path
|
49
|
+
def async_run
|
50
|
+
@stdin, @stdout, @stderr, @thread = Open3.popen3("#{@driver_executable_path} run-driver")
|
51
|
+
@stdin.binmode # Ensure Strings are written 1:1 without encoding conversion, necessary for integer values
|
43
52
|
|
44
53
|
Thread.new { handle_stdout }
|
45
54
|
Thread.new { handle_stderr }
|
46
|
-
|
47
|
-
@thread.join
|
48
55
|
end
|
49
56
|
|
50
57
|
private
|
@@ -69,6 +76,21 @@ module Playwright
|
|
69
76
|
|
70
77
|
def handle_stderr
|
71
78
|
while err = @stderr.read
|
79
|
+
# sometimed driver crashes with the error below.
|
80
|
+
# --------
|
81
|
+
# undefined:1
|
82
|
+
# �
|
83
|
+
# ^
|
84
|
+
|
85
|
+
# SyntaxError: Unexpected token � in JSON at position 0
|
86
|
+
# at JSON.parse (<anonymous>)
|
87
|
+
# at Transport.transport.onmessage (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/cli/driver.js:42:73)
|
88
|
+
# at Immediate.<anonymous> (/home/runner/work/playwright-ruby-client/playwright-ruby-client/node_modules/playwright/lib/protocol/transport.js:74:26)
|
89
|
+
# at processImmediate (internal/timers.js:461:21)
|
90
|
+
if err.include?('undefined:1')
|
91
|
+
@on_driver_crashed&.call
|
92
|
+
break
|
93
|
+
end
|
72
94
|
$stderr.write(err)
|
73
95
|
end
|
74
96
|
rescue IOError
|