playwright-ruby-client 0.0.8 → 0.3.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 +3 -3
- data/docs/api_coverage.md +159 -101
- data/lib/playwright.rb +5 -2
- data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
- data/lib/playwright/api_implementation.rb +18 -0
- data/lib/playwright/channel.rb +13 -6
- data/lib/playwright/channel_owner.rb +3 -5
- data/lib/playwright/channel_owners/android.rb +1 -1
- data/lib/playwright/channel_owners/android_device.rb +12 -12
- data/lib/playwright/channel_owners/binding_call.rb +3 -0
- data/lib/playwright/channel_owners/browser.rb +1 -1
- data/lib/playwright/channel_owners/browser_context.rb +157 -3
- data/lib/playwright/channel_owners/dialog.rb +28 -0
- data/lib/playwright/channel_owners/download.rb +27 -0
- data/lib/playwright/channel_owners/element_handle.rb +15 -4
- data/lib/playwright/channel_owners/frame.rb +24 -5
- data/lib/playwright/channel_owners/js_handle.rb +1 -1
- data/lib/playwright/channel_owners/page.rb +367 -29
- data/lib/playwright/channel_owners/playwright.rb +4 -0
- data/lib/playwright/channel_owners/request.rb +35 -3
- 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/errors.rb +1 -1
- 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 +1 -1
- data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +5 -1
- data/lib/playwright/mouse_impl.rb +7 -0
- data/lib/playwright/playwright_api.rb +59 -20
- data/lib/playwright/route_handler_entry.rb +36 -0
- data/lib/playwright/select_option_values.rb +14 -4
- data/lib/playwright/touchscreen_impl.rb +7 -0
- data/lib/playwright/utils.rb +4 -3
- data/lib/playwright/version.rb +1 -1
- data/lib/playwright/wait_helper.rb +1 -1
- data/lib/playwright_api/android.rb +82 -11
- data/lib/playwright_api/android_device.rb +148 -32
- 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/binding_call.rb +14 -5
- data/lib/playwright_api/browser.rb +22 -23
- data/lib/playwright_api/browser_context.rb +49 -35
- data/lib/playwright_api/browser_type.rb +24 -64
- data/lib/playwright_api/chromium_browser_context.rb +10 -8
- data/lib/playwright_api/console_message.rb +10 -6
- data/lib/playwright_api/dialog.rb +37 -6
- data/lib/playwright_api/download.rb +28 -11
- data/lib/playwright_api/element_handle.rb +107 -96
- data/lib/playwright_api/file_chooser.rb +18 -10
- data/lib/playwright_api/frame.rb +124 -117
- data/lib/playwright_api/js_handle.rb +20 -22
- data/lib/playwright_api/keyboard.rb +5 -5
- data/lib/playwright_api/page.rb +206 -148
- data/lib/playwright_api/playwright.rb +33 -45
- data/lib/playwright_api/request.rb +11 -12
- data/lib/playwright_api/response.rb +30 -16
- data/lib/playwright_api/route.rb +27 -5
- data/lib/playwright_api/selectors.rb +14 -10
- data/lib/playwright_api/web_socket.rb +10 -1
- data/lib/playwright_api/worker.rb +13 -13
- metadata +16 -7
- 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
@@ -20,6 +20,10 @@ module Playwright
|
|
20
20
|
@electron ||= ::Playwright::ChannelOwners::Electron.from(@initializer['electron'])
|
21
21
|
end
|
22
22
|
|
23
|
+
def selectors
|
24
|
+
@selectors ||= ::Playwright::ChannelOwners::Selectors.from(@initializer['selectors'])
|
25
|
+
end
|
26
|
+
|
23
27
|
class DeviceDescriptor
|
24
28
|
class Viewport
|
25
29
|
def initialize(hash)
|
@@ -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 = {
|
@@ -29,7 +29,7 @@ module Playwright
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def method
|
32
|
-
@
|
32
|
+
@initializer['method']
|
33
33
|
end
|
34
34
|
|
35
35
|
def post_data
|
@@ -69,7 +69,7 @@ module Playwright
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def frame
|
72
|
-
@initializer['frame']
|
72
|
+
ChannelOwners::Frame.from(@initializer['frame'])
|
73
73
|
end
|
74
74
|
|
75
75
|
def navigation_request?
|
@@ -86,6 +86,38 @@ 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_timings(
|
94
|
+
start_time:,
|
95
|
+
domain_lookup_start:,
|
96
|
+
domain_lookup_end:,
|
97
|
+
connect_start:,
|
98
|
+
secure_connection_start:,
|
99
|
+
connect_end:,
|
100
|
+
request_start:,
|
101
|
+
response_start:)
|
102
|
+
|
103
|
+
@timing["startTime"] = start_time
|
104
|
+
@timing["domainLookupStart"] = domain_lookup_start
|
105
|
+
@timing["domainLookupEnd"] = domain_lookup_end
|
106
|
+
@timing["connectStart"] = connect_start
|
107
|
+
@timing["secureConnectionStart"] = secure_connection_start
|
108
|
+
@timing["connectEnd"] = connect_end
|
109
|
+
@timing["requestStart"] = request_start
|
110
|
+
@timing["responseStart"] = response_start
|
111
|
+
end
|
112
|
+
|
113
|
+
private def update_headers(headers)
|
114
|
+
@headers = parse_headers(headers)
|
115
|
+
end
|
116
|
+
|
117
|
+
private def update_response_end_timing(response_end_timing)
|
118
|
+
@timing[:responseEnd] = response_end_timing
|
119
|
+
end
|
120
|
+
|
89
121
|
private def parse_headers(headers)
|
90
122
|
headers.map do |header|
|
91
123
|
[header['name'].downcase, header['value']]
|
@@ -1,5 +1,65 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
|
1
4
|
module Playwright
|
2
5
|
# @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
|
3
6
|
define_channel_owner :Response do
|
7
|
+
def after_initialize
|
8
|
+
@request = ChannelOwners::Request.from(@initializer['request'])
|
9
|
+
timing = @initializer['timing']
|
10
|
+
@request.send(:update_timings,
|
11
|
+
start_time: timing["startTime"],
|
12
|
+
domain_lookup_start: timing["domainLookupStart"],
|
13
|
+
domain_lookup_end: timing["domainLookupEnd"],
|
14
|
+
connect_start: timing["connectStart"],
|
15
|
+
secure_connection_start: timing["secureConnectionStart"],
|
16
|
+
connect_end: timing["connectEnd"],
|
17
|
+
request_start: timing["requestStart"],
|
18
|
+
response_start: timing["responseStart"],
|
19
|
+
)
|
20
|
+
@request.send(:update_headers, @initializer['requestHeaders'])
|
21
|
+
end
|
22
|
+
attr_reader :request
|
23
|
+
|
24
|
+
def url
|
25
|
+
@initializer['url']
|
26
|
+
end
|
27
|
+
|
28
|
+
def ok
|
29
|
+
status == 0 || (200...300).include?(status)
|
30
|
+
end
|
31
|
+
alias_method :ok?, :ok
|
32
|
+
|
33
|
+
def status
|
34
|
+
@initializer['status']
|
35
|
+
end
|
36
|
+
|
37
|
+
def status_text
|
38
|
+
@initializer['statusText']
|
39
|
+
end
|
40
|
+
|
41
|
+
def headers
|
42
|
+
@initializer['headers'].map do |header|
|
43
|
+
[header['name'].downcase, header['value']]
|
44
|
+
end.to_h
|
45
|
+
end
|
46
|
+
|
47
|
+
def finished
|
48
|
+
@channel.send_message_to_server('finished')
|
49
|
+
end
|
50
|
+
|
51
|
+
def body
|
52
|
+
binary = @channel.send_message_to_server("body")
|
53
|
+
Base64.strict_decode64(binary)
|
54
|
+
end
|
55
|
+
alias_method :text, :body
|
56
|
+
|
57
|
+
def json
|
58
|
+
JSON.parse(text)
|
59
|
+
end
|
60
|
+
|
61
|
+
def frame
|
62
|
+
@request.frame
|
63
|
+
end
|
4
64
|
end
|
5
65
|
end
|
@@ -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
|
data/lib/playwright/errors.rb
CHANGED
@@ -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
|
@@ -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,15 +116,10 @@ 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) }
|
82
121
|
else
|
83
|
-
object
|
122
|
+
::Playwright::PlaywrightApi.wrap(object) || object
|
84
123
|
end
|
85
124
|
end
|
86
125
|
|