playwright-ruby-client 0.0.6 → 0.2.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 +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
|