playwright-ruby-client 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +114 -2
- data/docs/api_coverage.md +351 -0
- data/lib/playwright.rb +7 -1
- data/lib/playwright/android_input_impl.rb +23 -0
- data/lib/playwright/api_implementation.rb +18 -0
- data/lib/playwright/channel.rb +7 -0
- data/lib/playwright/channel_owner.rb +3 -2
- data/lib/playwright/channel_owners/android.rb +10 -1
- data/lib/playwright/channel_owners/android_device.rb +163 -0
- data/lib/playwright/channel_owners/browser.rb +13 -13
- data/lib/playwright/channel_owners/browser_context.rb +9 -1
- data/lib/playwright/channel_owners/download.rb +27 -0
- data/lib/playwright/channel_owners/element_handle.rb +306 -0
- data/lib/playwright/channel_owners/frame.rb +371 -19
- data/lib/playwright/channel_owners/js_handle.rb +51 -0
- data/lib/playwright/channel_owners/page.rb +416 -19
- data/lib/playwright/channel_owners/request.rb +98 -0
- data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
- data/lib/playwright/connection.rb +9 -6
- data/lib/playwright/errors.rb +2 -2
- data/lib/playwright/event_emitter.rb +8 -1
- data/lib/playwright/event_emitter_proxy.rb +49 -0
- data/lib/playwright/file_chooser_impl.rb +23 -0
- data/lib/playwright/http_headers.rb +20 -0
- data/lib/playwright/input_files.rb +42 -0
- data/lib/playwright/javascript/expression.rb +37 -0
- data/lib/playwright/javascript/function.rb +37 -0
- data/lib/playwright/javascript/value_parser.rb +1 -1
- data/lib/playwright/javascript/value_serializer.rb +11 -11
- data/lib/playwright/keyboard_impl.rb +36 -0
- data/lib/playwright/mouse_impl.rb +7 -0
- data/lib/playwright/playwright_api.rb +84 -29
- data/lib/playwright/select_option_values.rb +32 -0
- data/lib/playwright/timeout_settings.rb +2 -2
- data/lib/playwright/touchscreen_impl.rb +7 -0
- data/lib/playwright/url_matcher.rb +19 -0
- data/lib/playwright/utils.rb +18 -0
- data/lib/playwright/version.rb +1 -1
- data/lib/playwright/wait_helper.rb +1 -1
- data/lib/playwright_api/accessibility.rb +46 -6
- data/lib/playwright_api/android.rb +37 -0
- data/lib/playwright_api/android_device.rb +82 -0
- data/lib/playwright_api/android_input.rb +25 -0
- data/lib/playwright_api/binding_call.rb +10 -6
- data/lib/playwright_api/browser.rb +85 -18
- data/lib/playwright_api/browser_context.rb +269 -37
- data/lib/playwright_api/browser_type.rb +60 -11
- data/lib/playwright_api/cdp_session.rb +23 -1
- data/lib/playwright_api/chromium_browser_context.rb +18 -6
- data/lib/playwright_api/console_message.rb +14 -15
- data/lib/playwright_api/dialog.rb +48 -2
- data/lib/playwright_api/download.rb +47 -10
- data/lib/playwright_api/element_handle.rb +269 -110
- data/lib/playwright_api/file_chooser.rb +23 -7
- data/lib/playwright_api/frame.rb +439 -154
- data/lib/playwright_api/js_handle.rb +69 -24
- data/lib/playwright_api/keyboard.rb +99 -9
- data/lib/playwright_api/mouse.rb +22 -0
- data/lib/playwright_api/page.rb +856 -229
- data/lib/playwright_api/playwright.rb +108 -20
- data/lib/playwright_api/request.rb +77 -29
- data/lib/playwright_api/response.rb +10 -13
- data/lib/playwright_api/route.rb +49 -0
- data/lib/playwright_api/selectors.rb +20 -8
- data/lib/playwright_api/video.rb +8 -0
- data/lib/playwright_api/web_socket.rb +0 -8
- data/lib/playwright_api/worker.rb +25 -13
- data/playwright.gemspec +1 -0
- metadata +33 -2
@@ -1,5 +1,103 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
module Playwright
|
2
4
|
# @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
|
3
5
|
define_channel_owner :Request do
|
6
|
+
def after_initialize
|
7
|
+
@redirected_from = ChannelOwners::Request.from_nullable(@initializer['redirectedFrom'])
|
8
|
+
@redirected_from&.send(:update_redirected_to, self)
|
9
|
+
@timing = {
|
10
|
+
startTime: 0,
|
11
|
+
domainLookupStart: -1,
|
12
|
+
domainLookupEnd: -1,
|
13
|
+
connectStart: -1,
|
14
|
+
secureConnectionStart: -1,
|
15
|
+
connectEnd: -1,
|
16
|
+
requestStart: -1,
|
17
|
+
responseStart: -1,
|
18
|
+
responseEnd: -1,
|
19
|
+
}
|
20
|
+
@headers = parse_headers(@initializer['headers'])
|
21
|
+
end
|
22
|
+
|
23
|
+
def url
|
24
|
+
@initializer['url']
|
25
|
+
end
|
26
|
+
|
27
|
+
def resource_type
|
28
|
+
@initializer['resourceType']
|
29
|
+
end
|
30
|
+
|
31
|
+
def method
|
32
|
+
@initialize['method']
|
33
|
+
end
|
34
|
+
|
35
|
+
def post_data
|
36
|
+
post_data_buffer
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_data_json
|
40
|
+
data = post_data
|
41
|
+
return unless data
|
42
|
+
|
43
|
+
content_type = @headers['content-type']
|
44
|
+
return unless content_type
|
45
|
+
|
46
|
+
if content_type == "application/x-www-form-urlencoded"
|
47
|
+
URI.decode_www_form(data).to_h
|
48
|
+
else
|
49
|
+
JSON.parse(data)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def post_data_buffer
|
54
|
+
base64_content = @initializer['postData']
|
55
|
+
if base64_content
|
56
|
+
Base64.strict_decode64(base64_content)
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def headers
|
63
|
+
@headers
|
64
|
+
end
|
65
|
+
|
66
|
+
def response
|
67
|
+
resp = @channel.send_message_to_server('response')
|
68
|
+
ChannelOwners::Response.from_nullable(resp)
|
69
|
+
end
|
70
|
+
|
71
|
+
def frame
|
72
|
+
@initializer['frame']
|
73
|
+
end
|
74
|
+
|
75
|
+
def navigation_request?
|
76
|
+
@initializer['isNavigationRequest']
|
77
|
+
end
|
78
|
+
|
79
|
+
def failure
|
80
|
+
@failure_text
|
81
|
+
end
|
82
|
+
|
83
|
+
attr_reader :headers, :redirected_from, :redirected_to, :timing
|
84
|
+
|
85
|
+
private def update_redirected_to(request)
|
86
|
+
@redirected_to = request
|
87
|
+
end
|
88
|
+
|
89
|
+
private def update_failure_text(failure_text)
|
90
|
+
@failure_text = failure_text
|
91
|
+
end
|
92
|
+
|
93
|
+
private def update_response_end_timing(response_end_timing)
|
94
|
+
@timing[:responseEnd] = response_end_timing
|
95
|
+
end
|
96
|
+
|
97
|
+
private def parse_headers(headers)
|
98
|
+
headers.map do |header|
|
99
|
+
[header['name'].downcase, header['value']]
|
100
|
+
end.to_h
|
101
|
+
end
|
4
102
|
end
|
5
103
|
end
|
@@ -55,10 +55,13 @@ module Playwright
|
|
55
55
|
method: method,
|
56
56
|
params: replace_channels_with_guids(params),
|
57
57
|
}
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
begin
|
59
|
+
@transport.send_message(message)
|
60
|
+
rescue => err
|
61
|
+
@callbacks.delete(id)
|
62
|
+
callback.reject(err)
|
63
|
+
raise unless err.is_a?(Transport::AlreadyDisconnectedError)
|
64
|
+
end
|
62
65
|
end
|
63
66
|
|
64
67
|
callback
|
@@ -132,7 +135,7 @@ module Playwright
|
|
132
135
|
unless object
|
133
136
|
raise "Cannot find object to dispose: #{guid}"
|
134
137
|
end
|
135
|
-
object.dispose
|
138
|
+
object.send(:dispose!)
|
136
139
|
return
|
137
140
|
end
|
138
141
|
|
@@ -153,7 +156,7 @@ module Playwright
|
|
153
156
|
end
|
154
157
|
|
155
158
|
if payload.is_a?(Channel)
|
156
|
-
{ guid: payload.guid }
|
159
|
+
return { guid: payload.guid }
|
157
160
|
end
|
158
161
|
|
159
162
|
if payload.is_a?(Hash)
|
data/lib/playwright/errors.rb
CHANGED
@@ -3,7 +3,7 @@ module Playwright
|
|
3
3
|
# ref: https://github.com/microsoft/playwright-python/blob/0b4a980fed366c4c1dee9bfcdd72662d629fdc8d/playwright/_impl/_helper.py#L155
|
4
4
|
def self.parse(error_payload)
|
5
5
|
if error_payload['name'] == 'TimeoutError'
|
6
|
-
|
6
|
+
TimeoutError.new(
|
7
7
|
message: error_payload['message'],
|
8
8
|
stack: error_payload['stack'].split("\n"),
|
9
9
|
)
|
@@ -28,7 +28,7 @@ module Playwright
|
|
28
28
|
end
|
29
29
|
|
30
30
|
class TimeoutError < Error
|
31
|
-
def initialize(message:, stack:)
|
31
|
+
def initialize(message:, stack: [])
|
32
32
|
super(name: 'TimeoutError', message: message, stack: stack)
|
33
33
|
end
|
34
34
|
end
|
@@ -34,11 +34,18 @@ module Playwright
|
|
34
34
|
# A subset of Events/EventEmitter in Node.js
|
35
35
|
module EventEmitter
|
36
36
|
# @param event [String]
|
37
|
+
# @returns [Boolean]
|
37
38
|
def emit(event, *args)
|
39
|
+
handled = false
|
38
40
|
(@__event_emitter ||= {})[event.to_s]&.each do |callback|
|
39
41
|
callback.call(*args)
|
42
|
+
handled = true
|
40
43
|
end
|
41
|
-
|
44
|
+
handled
|
45
|
+
end
|
46
|
+
|
47
|
+
private def listener_count(event)
|
48
|
+
((@__event_emitter ||= {})[event.to_s] ||= Set.new).count
|
42
49
|
end
|
43
50
|
|
44
51
|
# @param event [String]
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Playwright
|
2
|
+
class EventEmitterProxy
|
3
|
+
include EventEmitter
|
4
|
+
|
5
|
+
# @param src [PlaywrightApi]
|
6
|
+
# @param dest [EventEmitter]
|
7
|
+
def initialize(api, impl)
|
8
|
+
@api = api
|
9
|
+
@impl = impl
|
10
|
+
@listeners = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def on(event, callback)
|
14
|
+
if listener_count(event) == 0
|
15
|
+
subscribe(event)
|
16
|
+
end
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def once(event, callback)
|
21
|
+
if listener_count(event) == 0
|
22
|
+
subscribe(event)
|
23
|
+
end
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def off(event, callback)
|
28
|
+
super
|
29
|
+
if listener_count(event) == 0
|
30
|
+
unsubscribe(event)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private def subscribe(event)
|
35
|
+
@listeners[event] = ->(*args) {
|
36
|
+
wrapped_args = args.map { |arg| ::Playwright::PlaywrightApi.wrap(arg) }
|
37
|
+
emit(event, *wrapped_args)
|
38
|
+
}
|
39
|
+
@impl.on(event, @listeners[event])
|
40
|
+
end
|
41
|
+
|
42
|
+
private def unsubscribe(event)
|
43
|
+
listener = @listeners.delete(event)
|
44
|
+
if listener
|
45
|
+
@impl.off(event, listener)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Playwright
|
2
|
+
define_api_implementation :FileChooserImpl do
|
3
|
+
def initialize(page:, element_handle:, is_multiple:)
|
4
|
+
@page = page
|
5
|
+
@element_handle = element_handle
|
6
|
+
@is_multiple = is_multiple
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :page
|
10
|
+
|
11
|
+
def element
|
12
|
+
@element_handle
|
13
|
+
end
|
14
|
+
|
15
|
+
def multiple?
|
16
|
+
@is_multiple
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_files(files, noWaitAfter: nil, timeout: nil)
|
20
|
+
@element_handle.set_input_files(files, noWaitAfter: noWaitAfter, timeout: timeout)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Playwright
|
2
|
+
class HttpHeaders
|
3
|
+
# @param headers [Hash]
|
4
|
+
def initialize(headers)
|
5
|
+
@headers = headers
|
6
|
+
end
|
7
|
+
|
8
|
+
def as_serialized
|
9
|
+
@headers.map do |key, value|
|
10
|
+
{ name: key, value: value }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse_serialized(serialized_headers)
|
15
|
+
new(serialized_headers.map do |header|
|
16
|
+
[header['name'].downcase, header['value']]
|
17
|
+
end.to_h)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module Playwright
|
5
|
+
class InputFiles
|
6
|
+
def initialize(files)
|
7
|
+
@params = convert(files)
|
8
|
+
end
|
9
|
+
|
10
|
+
def as_params
|
11
|
+
@params
|
12
|
+
end
|
13
|
+
|
14
|
+
private def convert(files)
|
15
|
+
return convert([files]) unless files.is_a?(Array)
|
16
|
+
|
17
|
+
files.map do |file|
|
18
|
+
case file
|
19
|
+
when String
|
20
|
+
{
|
21
|
+
name: File.basename(file),
|
22
|
+
mimeType: mime_type_for(file),
|
23
|
+
buffer: Base64.strict_encode64(File.read(file)),
|
24
|
+
}
|
25
|
+
when File
|
26
|
+
{
|
27
|
+
name: File.basename(file.path),
|
28
|
+
mimeType: mime_type_for(file.path),
|
29
|
+
buffer: Base64.strict_encode64(file.read),
|
30
|
+
}
|
31
|
+
else
|
32
|
+
raise ArgumentError.new('file must be a String or File.')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private def mime_type_for(filepath)
|
38
|
+
mime_types = MIME::Types.type_for(filepath)
|
39
|
+
mime_types.first.to_s || 'application/octet-stream'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -25,6 +25,43 @@ module Playwright
|
|
25
25
|
)
|
26
26
|
::Playwright::ChannelOwner.from(resp)
|
27
27
|
end
|
28
|
+
|
29
|
+
def eval_on_selector(channel, selector)
|
30
|
+
value = channel.send_message_to_server(
|
31
|
+
'evalOnSelector',
|
32
|
+
selector: selector,
|
33
|
+
expression: @expression,
|
34
|
+
isFunction: false,
|
35
|
+
arg: @serialized_arg,
|
36
|
+
)
|
37
|
+
ValueParser.new(value).parse
|
38
|
+
end
|
39
|
+
|
40
|
+
def eval_on_selector_all(channel, selector)
|
41
|
+
value = channel.send_message_to_server(
|
42
|
+
'evalOnSelectorAll',
|
43
|
+
selector: selector,
|
44
|
+
expression: @expression,
|
45
|
+
isFunction: false,
|
46
|
+
arg: @serialized_arg,
|
47
|
+
)
|
48
|
+
ValueParser.new(value).parse
|
49
|
+
end
|
50
|
+
|
51
|
+
def wait_for_function(channel, polling:, timeout:)
|
52
|
+
params = {
|
53
|
+
expression: @expression,
|
54
|
+
isFunction: false,
|
55
|
+
arg: @serialized_arg,
|
56
|
+
polling: polling,
|
57
|
+
timeout: timeout,
|
58
|
+
}.compact
|
59
|
+
if polling.is_a?(Numeric)
|
60
|
+
params[:pollingInterval] = polling
|
61
|
+
end
|
62
|
+
resp = channel.send_message_to_server('waitForFunction', params)
|
63
|
+
ChannelOwners::JSHandle.from(resp)
|
64
|
+
end
|
28
65
|
end
|
29
66
|
end
|
30
67
|
end
|
@@ -25,6 +25,43 @@ module Playwright
|
|
25
25
|
)
|
26
26
|
::Playwright::ChannelOwner.from(resp)
|
27
27
|
end
|
28
|
+
|
29
|
+
def eval_on_selector(channel, selector)
|
30
|
+
value = channel.send_message_to_server(
|
31
|
+
'evalOnSelector',
|
32
|
+
selector: selector,
|
33
|
+
expression: @definition,
|
34
|
+
isFunction: true,
|
35
|
+
arg: @serialized_arg,
|
36
|
+
)
|
37
|
+
ValueParser.new(value).parse
|
38
|
+
end
|
39
|
+
|
40
|
+
def eval_on_selector_all(channel, selector)
|
41
|
+
value = channel.send_message_to_server(
|
42
|
+
'evalOnSelectorAll',
|
43
|
+
selector: selector,
|
44
|
+
expression: @definition,
|
45
|
+
isFunction: true,
|
46
|
+
arg: @serialized_arg,
|
47
|
+
)
|
48
|
+
ValueParser.new(value).parse
|
49
|
+
end
|
50
|
+
|
51
|
+
def wait_for_function(channel, polling:, timeout:)
|
52
|
+
params = {
|
53
|
+
expression: @definition,
|
54
|
+
isFunction: true,
|
55
|
+
arg: @serialized_arg,
|
56
|
+
polling: polling,
|
57
|
+
timeout: timeout,
|
58
|
+
}.compact
|
59
|
+
if polling.is_a?(Numeric)
|
60
|
+
params[:pollingInterval] = polling
|
61
|
+
end
|
62
|
+
resp = channel.send_message_to_server('waitForFunction', params)
|
63
|
+
ChannelOwners::JSHandle.from(resp)
|
64
|
+
end
|
28
65
|
end
|
29
66
|
end
|
30
67
|
end
|
@@ -15,9 +15,9 @@ module Playwright
|
|
15
15
|
# ref: https://github.com/microsoft/playwright-python/blob/25a99d53e00e35365cf5113b9525272628c0e65f/playwright/_impl/_js_handle.py#L99
|
16
16
|
private def serialize_value(value)
|
17
17
|
case value
|
18
|
-
when JSHandle
|
18
|
+
when ChannelOwners::JSHandle
|
19
19
|
index = @handles.count
|
20
|
-
@handles <<
|
20
|
+
@handles << value.channel
|
21
21
|
{ h: index }
|
22
22
|
when nil
|
23
23
|
{ v: 'undefined' }
|
@@ -28,23 +28,23 @@ module Playwright
|
|
28
28
|
when -Float::INFINITY
|
29
29
|
{ v: '-Infinity' }
|
30
30
|
when true, false
|
31
|
-
{ b:
|
31
|
+
{ b: value }
|
32
32
|
when Numeric
|
33
|
-
{ n:
|
33
|
+
{ n: value }
|
34
34
|
when String
|
35
|
-
{ s:
|
35
|
+
{ s: value }
|
36
36
|
when Time
|
37
37
|
require 'time'
|
38
|
-
{ d:
|
38
|
+
{ d: value.utc.iso8601 }
|
39
39
|
when Regexp
|
40
40
|
flags = []
|
41
|
-
flags << 'ms' if (
|
42
|
-
flags << 'i' if (
|
43
|
-
{ r: { p:
|
41
|
+
flags << 'ms' if (value.options & Regexp::MULTILINE) != 0
|
42
|
+
flags << 'i' if (value.options & Regexp::IGNORECASE) != 0
|
43
|
+
{ r: { p: value.source, f: flags.join('') } }
|
44
44
|
when Array
|
45
|
-
{ a:
|
45
|
+
{ a: value.map { |v| serialize_value(v) } }
|
46
46
|
when Hash
|
47
|
-
{ o:
|
47
|
+
{ o: value.map { |key, v| { k: key, v: serialize_value(v) } } }
|
48
48
|
else
|
49
49
|
raise ArgumentError.new("Unexpected value: #{value}")
|
50
50
|
end
|