playwright-ruby-client 0.0.4 → 0.0.9
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 +120 -6
- data/docs/api_coverage.md +351 -0
- data/lib/playwright.rb +9 -0
- data/lib/playwright/channel_owner.rb +16 -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 +20 -27
- data/lib/playwright/channel_owners/browser_context.rb +51 -0
- data/lib/playwright/channel_owners/console_message.rb +0 -4
- data/lib/playwright/channel_owners/element_handle.rb +306 -0
- data/lib/playwright/channel_owners/frame.rb +473 -7
- data/lib/playwright/channel_owners/js_handle.rb +51 -0
- data/lib/playwright/channel_owners/page.rb +589 -4
- data/lib/playwright/channel_owners/request.rb +98 -0
- data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
- data/lib/playwright/connection.rb +15 -14
- data/lib/playwright/errors.rb +1 -1
- data/lib/playwright/event_emitter.rb +17 -1
- data/lib/playwright/http_headers.rb +20 -0
- data/lib/playwright/input_files.rb +42 -0
- data/lib/playwright/input_type.rb +19 -0
- data/lib/playwright/input_types/android_input.rb +19 -0
- data/lib/playwright/input_types/keyboard.rb +32 -0
- data/lib/playwright/input_types/mouse.rb +4 -0
- data/lib/playwright/input_types/touchscreen.rb +4 -0
- data/lib/playwright/javascript.rb +13 -0
- data/lib/playwright/javascript/expression.rb +67 -0
- data/lib/playwright/javascript/function.rb +67 -0
- data/lib/playwright/javascript/value_parser.rb +75 -0
- data/lib/playwright/javascript/value_serializer.rb +54 -0
- data/lib/playwright/playwright_api.rb +45 -25
- data/lib/playwright/select_option_values.rb +32 -0
- data/lib/playwright/timeout_settings.rb +19 -0
- data/lib/playwright/url_matcher.rb +19 -0
- data/lib/playwright/utils.rb +37 -0
- data/lib/playwright/version.rb +1 -1
- data/lib/playwright/wait_helper.rb +73 -0
- data/lib/playwright_api/accessibility.rb +46 -6
- data/lib/playwright_api/android.rb +33 -0
- data/lib/playwright_api/android_device.rb +78 -0
- data/lib/playwright_api/android_input.rb +25 -0
- data/lib/playwright_api/binding_call.rb +18 -0
- data/lib/playwright_api/browser.rb +93 -12
- data/lib/playwright_api/browser_context.rb +279 -28
- data/lib/playwright_api/browser_type.rb +68 -5
- data/lib/playwright_api/cdp_session.rb +23 -1
- data/lib/playwright_api/chromium_browser_context.rb +26 -0
- data/lib/playwright_api/console_message.rb +20 -7
- data/lib/playwright_api/dialog.rb +48 -2
- data/lib/playwright_api/download.rb +19 -4
- data/lib/playwright_api/element_handle.rb +278 -104
- data/lib/playwright_api/file_chooser.rb +20 -3
- data/lib/playwright_api/frame.rb +452 -147
- data/lib/playwright_api/js_handle.rb +78 -19
- data/lib/playwright_api/keyboard.rb +99 -9
- data/lib/playwright_api/mouse.rb +22 -0
- data/lib/playwright_api/page.rb +864 -222
- data/lib/playwright_api/playwright.rb +116 -14
- data/lib/playwright_api/request.rb +86 -24
- data/lib/playwright_api/response.rb +18 -7
- data/lib/playwright_api/route.rb +49 -0
- data/lib/playwright_api/selectors.rb +28 -2
- 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 +3 -0
- metadata +66 -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)
|
@@ -192,12 +195,6 @@ module Playwright
|
|
192
195
|
end
|
193
196
|
initializer = replace_guids_with_channels(initializer)
|
194
197
|
|
195
|
-
params = [
|
196
|
-
parent,
|
197
|
-
type,
|
198
|
-
guid,
|
199
|
-
initializer,
|
200
|
-
]
|
201
198
|
class_name = case type
|
202
199
|
when 'Browser'
|
203
200
|
case initializer['name']
|
@@ -215,7 +212,6 @@ module Playwright
|
|
215
212
|
if browser_name == 'chromium'
|
216
213
|
'ChromiumBrowserContext'
|
217
214
|
else
|
218
|
-
params << browser_name
|
219
215
|
'BrowserContext'
|
220
216
|
end
|
221
217
|
else
|
@@ -224,7 +220,12 @@ module Playwright
|
|
224
220
|
|
225
221
|
result =
|
226
222
|
begin
|
227
|
-
ChannelOwners.const_get(class_name).new(
|
223
|
+
ChannelOwners.const_get(class_name).new(
|
224
|
+
parent,
|
225
|
+
type,
|
226
|
+
guid,
|
227
|
+
initializer,
|
228
|
+
)
|
228
229
|
rescue NameError
|
229
230
|
raise "Missing type #{type}"
|
230
231
|
end
|
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
|
)
|
@@ -17,15 +17,31 @@ module Playwright
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
module EventListenerInterface
|
21
|
+
def on(event, callback)
|
22
|
+
raise NotImplementedError.new('NOT IMPLEMENTED')
|
23
|
+
end
|
24
|
+
|
25
|
+
def off(event, callback)
|
26
|
+
raise NotImplementedError.new('NOT IMPLEMENTED')
|
27
|
+
end
|
28
|
+
|
29
|
+
def once(event, callback)
|
30
|
+
raise NotImplementedError.new('NOT IMPLEMENTED')
|
31
|
+
end
|
32
|
+
end
|
20
33
|
|
21
34
|
# A subset of Events/EventEmitter in Node.js
|
22
35
|
module EventEmitter
|
23
36
|
# @param event [String]
|
37
|
+
# @returns [Boolean]
|
24
38
|
def emit(event, *args)
|
39
|
+
handled = false
|
25
40
|
(@__event_emitter ||= {})[event.to_s]&.each do |callback|
|
26
41
|
callback.call(*args)
|
42
|
+
handled = true
|
27
43
|
end
|
28
|
-
|
44
|
+
handled
|
29
45
|
end
|
30
46
|
|
31
47
|
# @param event [String]
|
@@ -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 || 'application/octet-stream'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Playwright
|
2
|
+
class InputType
|
3
|
+
def initialize(channel)
|
4
|
+
@channel = channel
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# namespace declaration
|
9
|
+
module InputTypes ; end
|
10
|
+
|
11
|
+
def self.define_input_type(class_name, &block)
|
12
|
+
klass = Class.new(InputType)
|
13
|
+
klass.class_eval(&block) if block
|
14
|
+
InputTypes.const_set(class_name, klass)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# load subclasses
|
19
|
+
Dir[File.join(__dir__, 'input_types', '*.rb')].each { |f| require f }
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Playwright
|
2
|
+
define_input_type :AndroidInput do
|
3
|
+
def type(text)
|
4
|
+
@channel.send_message_to_server('inputType', text: text)
|
5
|
+
end
|
6
|
+
|
7
|
+
def press(key)
|
8
|
+
@channel.send_message_to_server('inputPress', key: key)
|
9
|
+
end
|
10
|
+
|
11
|
+
def tap_point(point)
|
12
|
+
@channel.send_message_to_server('inputTap', point: point)
|
13
|
+
end
|
14
|
+
|
15
|
+
def drag(from, to, steps)
|
16
|
+
@channel.send_message_to_server('inputDrag', from: from, to: to, steps: steps)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Playwright
|
2
|
+
define_input_type :Keyboard do
|
3
|
+
def down(key)
|
4
|
+
@channel.send_message_to_server('keyboardDown', key: key)
|
5
|
+
nil
|
6
|
+
end
|
7
|
+
|
8
|
+
def up(key)
|
9
|
+
@channel.send_message_to_server('keyboardDown', key: key)
|
10
|
+
end
|
11
|
+
|
12
|
+
def insert_text(text)
|
13
|
+
@channel.send_message_to_server('keyboardInsertText', text: text)
|
14
|
+
end
|
15
|
+
|
16
|
+
def type(text, delay: nil)
|
17
|
+
params = {
|
18
|
+
text: text,
|
19
|
+
delay: delay,
|
20
|
+
}.compact
|
21
|
+
@channel.send_message_to_server('keyboardType', params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def press(key, delay: nil)
|
25
|
+
params = {
|
26
|
+
key: key,
|
27
|
+
delay: delay,
|
28
|
+
}.compact
|
29
|
+
@channel.send_message_to_server('keyboardPress', params)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative './javascript/expression'
|
2
|
+
require_relative './javascript/function'
|
3
|
+
require_relative './javascript/value_parser'
|
4
|
+
require_relative './javascript/value_serializer'
|
5
|
+
|
6
|
+
module Playwright
|
7
|
+
module JavaScript
|
8
|
+
# Detect if str is likely to be a function
|
9
|
+
module_function def function?(str)
|
10
|
+
['async', 'function'].any? { |key| str.strip.start_with?(key) } || str.include?('=>')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Playwright
|
2
|
+
module JavaScript
|
3
|
+
class Expression
|
4
|
+
def initialize(expression)
|
5
|
+
@expression = expression
|
6
|
+
@serialized_arg = ValueSerializer.new(nil).serialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def evaluate(channel)
|
10
|
+
value = channel.send_message_to_server(
|
11
|
+
'evaluateExpression',
|
12
|
+
expression: @expression,
|
13
|
+
isFunction: false,
|
14
|
+
arg: @serialized_arg,
|
15
|
+
)
|
16
|
+
ValueParser.new(value).parse
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate_handle(channel)
|
20
|
+
resp = channel.send_message_to_server(
|
21
|
+
'evaluateExpressionHandle',
|
22
|
+
expression: @expression,
|
23
|
+
isFunction: false,
|
24
|
+
arg: @serialized_arg,
|
25
|
+
)
|
26
|
+
::Playwright::ChannelOwner.from(resp)
|
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
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|