playwright-ruby-client 0.0.6 → 0.2.0
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 +117 -5
- data/docs/api_coverage.md +359 -0
- data/lib/playwright.rb +6 -2
- 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 +6 -7
- 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 +14 -14
- data/lib/playwright/channel_owners/browser_context.rb +10 -2
- data/lib/playwright/channel_owners/download.rb +27 -0
- data/lib/playwright/channel_owners/element_handle.rb +243 -1
- data/lib/playwright/channel_owners/frame.rb +269 -22
- data/lib/playwright/channel_owners/js_handle.rb +51 -0
- data/lib/playwright/channel_owners/page.rb +379 -34
- data/lib/playwright/channel_owners/request.rb +9 -1
- 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 +15 -0
- data/lib/playwright/javascript/function.rb +15 -0
- data/lib/playwright/javascript/value_parser.rb +1 -1
- data/lib/playwright/javascript/value_serializer.rb +11 -11
- data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
- data/lib/playwright/mouse_impl.rb +7 -0
- data/lib/playwright/playwright_api.rb +66 -19
- data/lib/playwright/select_option_values.rb +42 -0
- data/lib/playwright/timeout_settings.rb +1 -1
- data/lib/playwright/touchscreen_impl.rb +7 -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/android.rb +32 -0
- data/lib/playwright_api/android_device.rb +77 -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 +22 -22
- data/lib/playwright_api/browser_context.rb +31 -22
- data/lib/playwright_api/browser_type.rb +16 -56
- data/lib/playwright_api/chromium_browser_context.rb +10 -8
- data/lib/playwright_api/console_message.rb +9 -10
- data/lib/playwright_api/dialog.rb +7 -3
- data/lib/playwright_api/download.rb +28 -11
- data/lib/playwright_api/element_handle.rb +139 -127
- data/lib/playwright_api/file_chooser.rb +17 -9
- data/lib/playwright_api/frame.rb +148 -148
- data/lib/playwright_api/js_handle.rb +26 -22
- data/lib/playwright_api/keyboard.rb +6 -6
- data/lib/playwright_api/page.rb +215 -179
- data/lib/playwright_api/playwright.rb +34 -46
- data/lib/playwright_api/request.rb +7 -8
- data/lib/playwright_api/response.rb +10 -6
- data/lib/playwright_api/selectors.rb +13 -9
- data/lib/playwright_api/web_socket.rb +10 -1
- data/lib/playwright_api/worker.rb +13 -13
- data/playwright.gemspec +1 -0
- metadata +32 -6
- data/lib/playwright/input_type.rb +0 -19
- data/lib/playwright/input_types/mouse.rb +0 -4
- data/lib/playwright/input_types/touchscreen.rb +0 -4
@@ -3,7 +3,7 @@ require 'base64'
|
|
3
3
|
module Playwright
|
4
4
|
# @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
|
5
5
|
define_channel_owner :Request do
|
6
|
-
def after_initialize
|
6
|
+
private def after_initialize
|
7
7
|
@redirected_from = ChannelOwners::Request.from_nullable(@initializer['redirectedFrom'])
|
8
8
|
@redirected_from&.send(:update_redirected_to, self)
|
9
9
|
@timing = {
|
@@ -86,6 +86,14 @@ module Playwright
|
|
86
86
|
@redirected_to = request
|
87
87
|
end
|
88
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
|
+
|
89
97
|
private def parse_headers(headers)
|
90
98
|
headers.map do |header|
|
91
99
|
[header['name'].downcase, header['value']]
|
@@ -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
|
@@ -47,6 +47,21 @@ module Playwright
|
|
47
47
|
)
|
48
48
|
ValueParser.new(value).parse
|
49
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
|
50
65
|
end
|
51
66
|
end
|
52
67
|
end
|
@@ -47,6 +47,21 @@ module Playwright
|
|
47
47
|
)
|
48
48
|
ValueParser.new(value).parse
|
49
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
|
50
65
|
end
|
51
66
|
end
|
52
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
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module Playwright
|
2
|
-
|
2
|
+
define_api_implementation :KeyboardImpl do
|
3
|
+
def initialize(channel)
|
4
|
+
@channel = channel
|
5
|
+
end
|
6
|
+
|
3
7
|
def down(key)
|
4
8
|
@channel.send_message_to_server('keyboardDown', key: key)
|
5
9
|
nil
|
@@ -13,7 +17,7 @@ module Playwright
|
|
13
17
|
@channel.send_message_to_server('keyboardInsertText', text: text)
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
20
|
+
def type(text, delay: nil)
|
17
21
|
params = {
|
18
22
|
text: text,
|
19
23
|
delay: delay,
|
@@ -1,27 +1,34 @@
|
|
1
1
|
module Playwright
|
2
2
|
class PlaywrightApi
|
3
|
-
# Wrap ChannelOwner.
|
3
|
+
# Wrap ChannelOwner / ApiImplementation.
|
4
4
|
# Playwright::ChannelOwners::XXXXX will be wrapped as Playwright::XXXXX
|
5
|
+
# Playwright::YYYYImpl will be wrapped as Playwright::YYYY
|
5
6
|
# Playwright::XXXXX is automatically generated by development/generate_api
|
6
7
|
#
|
7
|
-
# @param channel_owner [ChannelOwner]
|
8
|
+
# @param channel_owner [ChannelOwner|ApiImplementation]
|
8
9
|
# @note Intended for internal use only.
|
9
|
-
def self.
|
10
|
-
|
10
|
+
def self.wrap(channel_owner_or_api_implementation)
|
11
|
+
case channel_owner_or_api_implementation
|
12
|
+
when ChannelOwner
|
13
|
+
ChannelOwnerWrapper.new(channel_owner_or_api_implementation).wrap
|
14
|
+
when ApiImplementation
|
15
|
+
ApiImplementationWrapper.new(channel_owner_or_api_implementation).wrap
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
11
19
|
end
|
12
20
|
|
13
|
-
class
|
14
|
-
def initialize(impl
|
21
|
+
class ChannelOwnerWrapper
|
22
|
+
def initialize(impl)
|
15
23
|
impl_class_name = impl.class.name
|
16
|
-
unless impl_class_name.include?("
|
17
|
-
raise "#{impl_class_name} is not
|
24
|
+
unless impl_class_name.include?("::ChannelOwners::")
|
25
|
+
raise "#{impl_class_name} is not ChannelOwners"
|
18
26
|
end
|
19
27
|
|
20
28
|
@impl = impl
|
21
|
-
@module_name = module_name
|
22
29
|
end
|
23
30
|
|
24
|
-
def
|
31
|
+
def wrap
|
25
32
|
api_class = detect_class_for(@impl.class)
|
26
33
|
if api_class
|
27
34
|
api_class.new(@impl)
|
@@ -33,11 +40,11 @@ module Playwright
|
|
33
40
|
private
|
34
41
|
|
35
42
|
def expected_class_name_for(klass)
|
36
|
-
klass.name.split("
|
43
|
+
klass.name.split("::ChannelOwners::").last
|
37
44
|
end
|
38
45
|
|
39
46
|
def superclass_exist?(klass)
|
40
|
-
![::Playwright::ChannelOwner,
|
47
|
+
![::Playwright::ChannelOwner, Object].include?(klass.superclass)
|
41
48
|
end
|
42
49
|
|
43
50
|
def detect_class_for(klass)
|
@@ -52,7 +59,44 @@ module Playwright
|
|
52
59
|
end
|
53
60
|
end
|
54
61
|
|
55
|
-
|
62
|
+
class ApiImplementationWrapper
|
63
|
+
def initialize(impl)
|
64
|
+
impl_class_name = impl.class.name
|
65
|
+
unless impl_class_name.end_with?("Impl")
|
66
|
+
raise "#{impl_class_name} is not Impl"
|
67
|
+
end
|
68
|
+
|
69
|
+
@impl = impl
|
70
|
+
end
|
71
|
+
|
72
|
+
def wrap
|
73
|
+
api_class = detect_class_for(@impl.class)
|
74
|
+
if api_class
|
75
|
+
api_class.new(@impl)
|
76
|
+
else
|
77
|
+
raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def expected_class_name_for(klass)
|
84
|
+
# KeyboardImpl -> Keyboard
|
85
|
+
# MouseImpl -> Mouse
|
86
|
+
klass.name[0...-4].split("::").last
|
87
|
+
end
|
88
|
+
|
89
|
+
def detect_class_for(klass)
|
90
|
+
class_name = expected_class_name_for(klass)
|
91
|
+
if ::Playwright.const_defined?(class_name)
|
92
|
+
::Playwright.const_get(class_name)
|
93
|
+
else
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param impl [Playwright::ChannelOwner|Playwright::ApiImplementation]
|
56
100
|
def initialize(impl)
|
57
101
|
@impl = impl
|
58
102
|
end
|
@@ -72,13 +116,16 @@ module Playwright
|
|
72
116
|
end
|
73
117
|
|
74
118
|
private def wrap_impl(object)
|
75
|
-
|
76
|
-
when ChannelOwner
|
77
|
-
PlaywrightApi.from_channel_owner(object)
|
78
|
-
when InputType
|
79
|
-
Factory.new(object, 'InputTypes').create
|
80
|
-
when Array
|
119
|
+
if object.is_a?(Array)
|
81
120
|
object.map { |obj| wrap_impl(obj) }
|
121
|
+
else
|
122
|
+
::Playwright::PlaywrightApi.wrap(object) || object
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private def unwrap_impl(object)
|
127
|
+
if object.is_a?(PlaywrightApi)
|
128
|
+
object.instance_variable_get(:@impl)
|
82
129
|
else
|
83
130
|
object
|
84
131
|
end
|