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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -5
  3. data/docs/api_coverage.md +359 -0
  4. data/lib/playwright.rb +6 -2
  5. data/lib/playwright/android_input_impl.rb +23 -0
  6. data/lib/playwright/api_implementation.rb +18 -0
  7. data/lib/playwright/channel.rb +7 -0
  8. data/lib/playwright/channel_owner.rb +6 -7
  9. data/lib/playwright/channel_owners/android.rb +10 -1
  10. data/lib/playwright/channel_owners/android_device.rb +163 -0
  11. data/lib/playwright/channel_owners/browser.rb +14 -14
  12. data/lib/playwright/channel_owners/browser_context.rb +10 -2
  13. data/lib/playwright/channel_owners/download.rb +27 -0
  14. data/lib/playwright/channel_owners/element_handle.rb +243 -1
  15. data/lib/playwright/channel_owners/frame.rb +269 -22
  16. data/lib/playwright/channel_owners/js_handle.rb +51 -0
  17. data/lib/playwright/channel_owners/page.rb +379 -34
  18. data/lib/playwright/channel_owners/request.rb +9 -1
  19. data/lib/playwright/channel_owners/webkit_browser.rb +1 -1
  20. data/lib/playwright/connection.rb +9 -6
  21. data/lib/playwright/errors.rb +2 -2
  22. data/lib/playwright/event_emitter.rb +8 -1
  23. data/lib/playwright/event_emitter_proxy.rb +49 -0
  24. data/lib/playwright/file_chooser_impl.rb +23 -0
  25. data/lib/playwright/http_headers.rb +20 -0
  26. data/lib/playwright/input_files.rb +42 -0
  27. data/lib/playwright/javascript/expression.rb +15 -0
  28. data/lib/playwright/javascript/function.rb +15 -0
  29. data/lib/playwright/javascript/value_parser.rb +1 -1
  30. data/lib/playwright/javascript/value_serializer.rb +11 -11
  31. data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +6 -2
  32. data/lib/playwright/mouse_impl.rb +7 -0
  33. data/lib/playwright/playwright_api.rb +66 -19
  34. data/lib/playwright/select_option_values.rb +42 -0
  35. data/lib/playwright/timeout_settings.rb +1 -1
  36. data/lib/playwright/touchscreen_impl.rb +7 -0
  37. data/lib/playwright/utils.rb +18 -0
  38. data/lib/playwright/version.rb +1 -1
  39. data/lib/playwright/wait_helper.rb +1 -1
  40. data/lib/playwright_api/android.rb +32 -0
  41. data/lib/playwright_api/android_device.rb +77 -0
  42. data/lib/playwright_api/android_input.rb +25 -0
  43. data/lib/playwright_api/binding_call.rb +10 -6
  44. data/lib/playwright_api/browser.rb +22 -22
  45. data/lib/playwright_api/browser_context.rb +31 -22
  46. data/lib/playwright_api/browser_type.rb +16 -56
  47. data/lib/playwright_api/chromium_browser_context.rb +10 -8
  48. data/lib/playwright_api/console_message.rb +9 -10
  49. data/lib/playwright_api/dialog.rb +7 -3
  50. data/lib/playwright_api/download.rb +28 -11
  51. data/lib/playwright_api/element_handle.rb +139 -127
  52. data/lib/playwright_api/file_chooser.rb +17 -9
  53. data/lib/playwright_api/frame.rb +148 -148
  54. data/lib/playwright_api/js_handle.rb +26 -22
  55. data/lib/playwright_api/keyboard.rb +6 -6
  56. data/lib/playwright_api/page.rb +215 -179
  57. data/lib/playwright_api/playwright.rb +34 -46
  58. data/lib/playwright_api/request.rb +7 -8
  59. data/lib/playwright_api/response.rb +10 -6
  60. data/lib/playwright_api/selectors.rb +13 -9
  61. data/lib/playwright_api/web_socket.rb +10 -1
  62. data/lib/playwright_api/worker.rb +13 -13
  63. data/playwright.gemspec +1 -0
  64. metadata +32 -6
  65. data/lib/playwright/input_type.rb +0 -19
  66. data/lib/playwright/input_types/mouse.rb +0 -4
  67. 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']]
@@ -2,7 +2,7 @@ require_relative './browser'
2
2
 
3
3
  module Playwright
4
4
  module ChannelOwners
5
- class WebkitBrowser < Browser
5
+ class WebKitBrowser < Browser
6
6
  end
7
7
  end
8
8
  end
@@ -55,10 +55,13 @@ module Playwright
55
55
  method: method,
56
56
  params: replace_channels_with_guids(params),
57
57
  }
58
- @transport.send_message(message)
59
- rescue Transport::AlreadyDisconnectedError => err
60
- @callbacks.delete(id)
61
- callback.reject(err)
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)
@@ -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
- Playwright::TimeoutError.new(
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
- self
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
@@ -61,7 +61,7 @@ module Playwright
61
61
  end
62
62
 
63
63
  if hash.key?('o')
64
- return hash['o'].map { |key, value| [key, parse_hash(value)].to_h }
64
+ return hash['o'].map { |obj| [obj['k'], parse_hash(obj['v'])] }.to_h
65
65
  end
66
66
 
67
67
  if hash.key?('h')
@@ -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 << v
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: v }
31
+ { b: value }
32
32
  when Numeric
33
- { n: v }
33
+ { n: value }
34
34
  when String
35
- { s: v }
35
+ { s: value }
36
36
  when Time
37
37
  require 'time'
38
- { d: v.utc.iso8601 }
38
+ { d: value.utc.iso8601 }
39
39
  when Regexp
40
40
  flags = []
41
- flags << 'ms' if (v.options & Regexp::MULTILINE) != 0
42
- flags << 'i' if (v.options & Regexp::IGNORECASE) != 0
43
- { r: { p: v.source, f: flags.join('') } }
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: v.map { |value| serialize_value(value) } }
45
+ { a: value.map { |v| serialize_value(v) } }
46
46
  when Hash
47
- { o: v.map { |key, value| [key, serialize_value(value)] }.to_h }
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
- define_input_type :Keyboard do
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 type_text(text, delay: nil)
20
+ def type(text, delay: nil)
17
21
  params = {
18
22
  text: text,
19
23
  delay: delay,
@@ -0,0 +1,7 @@
1
+ module Playwright
2
+ define_api_implementation :MouseImpl do
3
+ def initialize(channel)
4
+ @channel = channel
5
+ end
6
+ end
7
+ 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.from_channel_owner(channel_owner)
10
- Factory.new(channel_owner, 'ChannelOwners').create
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 Factory
14
- def initialize(impl, module_name)
21
+ class ChannelOwnerWrapper
22
+ def initialize(impl)
15
23
  impl_class_name = impl.class.name
16
- unless impl_class_name.include?("::#{module_name}::")
17
- raise "#{impl_class_name} is not #{module_name}"
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 create
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("::#{@module_name}::").last
43
+ klass.name.split("::ChannelOwners::").last
37
44
  end
38
45
 
39
46
  def superclass_exist?(klass)
40
- ![::Playwright::ChannelOwner, ::Playwright::InputType, Object].include?(klass.superclass)
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
- # @param impl [Playwright::ChannelOwner|Playwright::InputType]
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
- case object
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