playwright-ruby-client 0.0.4 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,67 @@
|
|
1
|
+
module Playwright
|
2
|
+
module JavaScript
|
3
|
+
class Function
|
4
|
+
def initialize(definition, arg)
|
5
|
+
@definition = definition
|
6
|
+
@serialized_arg = ValueSerializer.new(arg).serialize
|
7
|
+
end
|
8
|
+
|
9
|
+
def evaluate(channel)
|
10
|
+
value = channel.send_message_to_server(
|
11
|
+
'evaluateExpression',
|
12
|
+
expression: @definition,
|
13
|
+
isFunction: true,
|
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: @definition,
|
23
|
+
isFunction: true,
|
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: @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
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Playwright
|
4
|
+
module JavaScript
|
5
|
+
class ValueParser
|
6
|
+
def initialize(hash)
|
7
|
+
@hash = hash
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Hash]
|
11
|
+
def parse
|
12
|
+
if @hash.nil?
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
parse_hash(@hash)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# ref: https://github.com/microsoft/playwright/blob/b45905ae3f1a066a8ecb358035ce745ddd21cf3a/src/protocol/serializers.ts#L42
|
20
|
+
# ref: https://github.com/microsoft/playwright-python/blob/25a99d53e00e35365cf5113b9525272628c0e65f/playwright/_impl/_js_handle.py#L140
|
21
|
+
private def parse_hash(hash)
|
22
|
+
%w(n s b).each do |key|
|
23
|
+
return hash[key] if hash.key?(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
if hash.key?('v')
|
27
|
+
return
|
28
|
+
case hash['v']
|
29
|
+
when 'undefined'
|
30
|
+
nil
|
31
|
+
when 'null'
|
32
|
+
nil
|
33
|
+
when 'NaN'
|
34
|
+
Float::NAN
|
35
|
+
when 'Infinity'
|
36
|
+
Float::INFINITY
|
37
|
+
when '-Infinity'
|
38
|
+
-Float::INFINITY
|
39
|
+
when '-0'
|
40
|
+
-0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if hash.key?('d')
|
45
|
+
return DateTime.parse(hash['d'])
|
46
|
+
end
|
47
|
+
|
48
|
+
if hash.key?('r')
|
49
|
+
# @see https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp
|
50
|
+
# @see https://docs.ruby-lang.org/ja/latest/class/Regexp.html
|
51
|
+
js_regex_flag = hash['r']['f']
|
52
|
+
flags = []
|
53
|
+
flags << Regexp::IGNORECASE if js_regex_flag.include?('i')
|
54
|
+
flags << Regexp::MULTILINE if js_regex_flag.include?('m') || js_regex_flag.include?('s')
|
55
|
+
|
56
|
+
return Regexp.compile(hash['r']['p'], flags.inject(:|))
|
57
|
+
end
|
58
|
+
|
59
|
+
if hash.key?('a')
|
60
|
+
return hash['a'].map { |value| parse_hash(value) }
|
61
|
+
end
|
62
|
+
|
63
|
+
if hash.key?('o')
|
64
|
+
return hash['o'].map { |obj| [obj['k'], parse_hash(obj['v'])] }.to_h
|
65
|
+
end
|
66
|
+
|
67
|
+
if hash.key?('h')
|
68
|
+
return @handles[hash['h']]
|
69
|
+
end
|
70
|
+
|
71
|
+
raise ArgumentError.new("Unexpected value: #{hash}")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Playwright
|
2
|
+
module JavaScript
|
3
|
+
class ValueSerializer
|
4
|
+
def initialize(ruby_value)
|
5
|
+
@value = ruby_value
|
6
|
+
end
|
7
|
+
|
8
|
+
# @return [Hash]
|
9
|
+
def serialize
|
10
|
+
@handles = []
|
11
|
+
{ value: serialize_value(@value), handles: @handles }
|
12
|
+
end
|
13
|
+
|
14
|
+
# ref: https://github.com/microsoft/playwright/blob/b45905ae3f1a066a8ecb358035ce745ddd21cf3a/src/protocol/serializers.ts#L84
|
15
|
+
# ref: https://github.com/microsoft/playwright-python/blob/25a99d53e00e35365cf5113b9525272628c0e65f/playwright/_impl/_js_handle.py#L99
|
16
|
+
private def serialize_value(value)
|
17
|
+
case value
|
18
|
+
when ChannelOwners::JSHandle
|
19
|
+
index = @handles.count
|
20
|
+
@handles << value.channel
|
21
|
+
{ h: index }
|
22
|
+
when nil
|
23
|
+
{ v: 'undefined' }
|
24
|
+
when Float::NAN
|
25
|
+
{ v: 'NaN'}
|
26
|
+
when Float::INFINITY
|
27
|
+
{ v: 'Infinity' }
|
28
|
+
when -Float::INFINITY
|
29
|
+
{ v: '-Infinity' }
|
30
|
+
when true, false
|
31
|
+
{ b: value }
|
32
|
+
when Numeric
|
33
|
+
{ n: value }
|
34
|
+
when String
|
35
|
+
{ s: value }
|
36
|
+
when Time
|
37
|
+
require 'time'
|
38
|
+
{ d: value.utc.iso8601 }
|
39
|
+
when Regexp
|
40
|
+
flags = []
|
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
|
+
when Array
|
45
|
+
{ a: value.map { |v| serialize_value(v) } }
|
46
|
+
when Hash
|
47
|
+
{ o: value.map { |key, v| { k: key, v: serialize_value(v) } } }
|
48
|
+
else
|
49
|
+
raise ArgumentError.new("Unexpected value: #{value}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -7,66 +7,86 @@ module Playwright
|
|
7
7
|
# @param channel_owner [ChannelOwner]
|
8
8
|
# @note Intended for internal use only.
|
9
9
|
def self.from_channel_owner(channel_owner)
|
10
|
-
Factory.new(channel_owner).create
|
10
|
+
Factory.new(channel_owner, 'ChannelOwners').create
|
11
11
|
end
|
12
12
|
|
13
|
-
private
|
14
|
-
|
15
13
|
class Factory
|
16
|
-
def initialize(
|
17
|
-
|
18
|
-
|
14
|
+
def initialize(impl, module_name)
|
15
|
+
impl_class_name = impl.class.name
|
16
|
+
unless impl_class_name.include?("::#{module_name}::")
|
17
|
+
raise "#{impl_class_name} is not #{module_name}"
|
18
|
+
end
|
19
19
|
|
20
|
-
@
|
20
|
+
@impl = impl
|
21
|
+
@module_name = module_name
|
21
22
|
end
|
22
23
|
|
23
24
|
def create
|
24
|
-
api_class = detect_class_for(@
|
25
|
+
api_class = detect_class_for(@impl.class)
|
25
26
|
if api_class
|
26
|
-
api_class.new(@
|
27
|
+
api_class.new(@impl)
|
27
28
|
else
|
28
|
-
raise NotImplementedError.new("Playwright::#{expected_class_name_for(@
|
29
|
+
raise NotImplementedError.new("Playwright::#{expected_class_name_for(@impl.class)} is not implemented")
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
33
34
|
|
34
35
|
def expected_class_name_for(klass)
|
35
|
-
klass.name.split(
|
36
|
+
klass.name.split("::#{@module_name}::").last
|
37
|
+
end
|
38
|
+
|
39
|
+
def superclass_exist?(klass)
|
40
|
+
![::Playwright::ChannelOwner, ::Playwright::InputType, Object].include?(klass.superclass)
|
36
41
|
end
|
37
42
|
|
38
43
|
def detect_class_for(klass)
|
39
44
|
class_name = expected_class_name_for(klass)
|
40
45
|
if ::Playwright.const_defined?(class_name)
|
41
46
|
::Playwright.const_get(class_name)
|
47
|
+
elsif superclass_exist?(klass)
|
48
|
+
detect_class_for(klass.superclass)
|
42
49
|
else
|
43
|
-
|
44
|
-
nil
|
45
|
-
else
|
46
|
-
detect_class_for(klass.superclass)
|
47
|
-
end
|
50
|
+
nil
|
48
51
|
end
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
52
|
-
# @param
|
53
|
-
def initialize(
|
54
|
-
@
|
55
|
+
# @param impl [Playwright::ChannelOwner|Playwright::InputType]
|
56
|
+
def initialize(impl)
|
57
|
+
@impl = impl
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
@impl.to_s == other.instance_variable_get(:'@impl').to_s
|
55
62
|
end
|
56
63
|
|
57
64
|
# @param block [Proc]
|
58
|
-
def wrap_block_call(block)
|
65
|
+
private def wrap_block_call(block)
|
66
|
+
return nil unless block.is_a?(Proc)
|
67
|
+
|
59
68
|
-> (*args) {
|
60
|
-
wrapped_args = args.map { |arg|
|
69
|
+
wrapped_args = args.map { |arg| wrap_impl(arg) }
|
61
70
|
block.call(*wrapped_args)
|
62
71
|
}
|
63
72
|
end
|
64
73
|
|
65
|
-
def
|
66
|
-
|
74
|
+
private def wrap_impl(object)
|
75
|
+
case object
|
76
|
+
when ChannelOwner
|
67
77
|
PlaywrightApi.from_channel_owner(object)
|
68
|
-
|
69
|
-
object
|
78
|
+
when InputType
|
79
|
+
Factory.new(object, 'InputTypes').create
|
80
|
+
when Array
|
81
|
+
object.map { |obj| wrap_impl(obj) }
|
82
|
+
else
|
83
|
+
object
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private def unwrap_impl(object)
|
88
|
+
if object.is_a?(PlaywrightApi)
|
89
|
+
object.instance_variable_get(:@impl)
|
70
90
|
else
|
71
91
|
object
|
72
92
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Playwright
|
2
|
+
class SelectOptionValues
|
3
|
+
def initialize(values)
|
4
|
+
@params = convert(values)
|
5
|
+
end
|
6
|
+
|
7
|
+
# @return [Hash]
|
8
|
+
def as_params
|
9
|
+
@params
|
10
|
+
end
|
11
|
+
|
12
|
+
private def convert(values)
|
13
|
+
return {} unless values
|
14
|
+
return convert([values]) unless values.is_a?('Array')
|
15
|
+
return {} if values.empty?
|
16
|
+
values.each_with_index do |value, index|
|
17
|
+
unless values
|
18
|
+
raise ArgumentError.new("options[#{index}]: expected object, got null")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
case values.first
|
23
|
+
when ElementHandle
|
24
|
+
{ elements: values.map(&:channel) }
|
25
|
+
when String
|
26
|
+
{ options: values.map { |value| { value: value } } }
|
27
|
+
else
|
28
|
+
{ options: values }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Playwright
|
2
|
+
class TimeoutSettings
|
3
|
+
DEFAULT_TIMEOUT = 30000
|
4
|
+
|
5
|
+
def initialize(parent = nil)
|
6
|
+
@parent = parent
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_writer :default_timeout, :default_navigation_timeout
|
10
|
+
|
11
|
+
def navigation_timeout
|
12
|
+
@default_navigation_timeout || @default_timeout || @parent&.navigation_timeout || DEFAULT_TIMEOUT
|
13
|
+
end
|
14
|
+
|
15
|
+
def timeout
|
16
|
+
@default_timeout || @parent&.timeout || DEFAULT_TIMEOUT
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Playwright
|
2
|
+
class UrlMatcher
|
3
|
+
# @param url [String|Regexp]
|
4
|
+
def initialize(url)
|
5
|
+
@url = url
|
6
|
+
end
|
7
|
+
|
8
|
+
def match?(target_url)
|
9
|
+
case @url
|
10
|
+
when String
|
11
|
+
@url == target_url
|
12
|
+
when Regexp
|
13
|
+
@url.match?(target_url)
|
14
|
+
else
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Playwright
|
2
|
+
module Utils
|
3
|
+
module PrepareBrowserContextOptions
|
4
|
+
# @see https://github.com/microsoft/playwright/blob/5a2cfdbd47ed3c3deff77bb73e5fac34241f649d/src/client/browserContext.ts#L265
|
5
|
+
private def prepare_browser_context_options(params)
|
6
|
+
if params[:viewport] == 0
|
7
|
+
params.delete(:viewport)
|
8
|
+
params[:noDefaultViewport] = true
|
9
|
+
end
|
10
|
+
if params[:extraHTTPHeaders]
|
11
|
+
params[:extraHTTPHeaders] = ::Playwright::HttpHeaders.new(params[:extraHTTPHeaders]).as_serialized
|
12
|
+
end
|
13
|
+
if params[:storageState].is_a?(String)
|
14
|
+
params[:storageState] = JSON.parse(File.read(params[:storageState]))
|
15
|
+
end
|
16
|
+
|
17
|
+
params
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Errors
|
22
|
+
module SafeCloseError
|
23
|
+
# @param err [Exception]
|
24
|
+
private def safe_close_error?(err)
|
25
|
+
return true if err.is_a?(Transport::AlreadyDisconnectedError)
|
26
|
+
|
27
|
+
[
|
28
|
+
'Browser has been closed',
|
29
|
+
'Target page, context or browser has been closed',
|
30
|
+
].any? do |closed_message|
|
31
|
+
err.message.end_with?(closed_message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/playwright/version.rb
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Playwright
|
2
|
+
# ref: https://github.com/microsoft/playwright-python/blob/30946ae3099d51f9b7f355f9ae7e8c04d748ce36/playwright/_impl/_wait_helper.py
|
3
|
+
# ref: https://github.com/microsoft/playwright/blob/01fb3a6045cbdb4b5bcba0809faed85bd917ab87/src/client/waiter.ts#L21
|
4
|
+
class WaitHelper
|
5
|
+
def initialize
|
6
|
+
@promise = Concurrent::Promises.resolvable_future
|
7
|
+
@registered_listeners = Set.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def reject_on_event(emitter, event, error, predicate: nil)
|
11
|
+
listener = -> (*args) {
|
12
|
+
if !predicate || predicate.call(*args)
|
13
|
+
reject(error)
|
14
|
+
end
|
15
|
+
}
|
16
|
+
emitter.on(event, listener)
|
17
|
+
@registered_listeners << [emitter, event, listener]
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def reject_on_timeout(timeout_ms, message)
|
23
|
+
return if timeout_ms <= 0
|
24
|
+
|
25
|
+
Concurrent::Promises.schedule(timeout_ms / 1000.0) {
|
26
|
+
reject(TimeoutError.new(message))
|
27
|
+
}
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [Playwright::EventEmitter]
|
33
|
+
# @param
|
34
|
+
def wait_for_event(emitter, event, predicate: nil)
|
35
|
+
listener = -> (*args) {
|
36
|
+
begin
|
37
|
+
if !predicate || predicate.call(*args)
|
38
|
+
fulfill(*args)
|
39
|
+
end
|
40
|
+
rescue => err
|
41
|
+
reject(err)
|
42
|
+
end
|
43
|
+
}
|
44
|
+
emitter.on(event, listener)
|
45
|
+
@registered_listeners << [emitter, event, listener]
|
46
|
+
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :promise
|
51
|
+
|
52
|
+
private def cleanup
|
53
|
+
@registered_listeners.each do |emitter, event, listener|
|
54
|
+
emitter.off(event, listener)
|
55
|
+
end
|
56
|
+
@registered_listeners.clear
|
57
|
+
end
|
58
|
+
|
59
|
+
private def fulfill(*args)
|
60
|
+
cleanup
|
61
|
+
unless @promise.resolved?
|
62
|
+
@promise.fulfill(args.first)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private def reject(error)
|
67
|
+
cleanup
|
68
|
+
unless @promise.resolved?
|
69
|
+
@promise.reject(error)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|