playwright-ruby-client 0.0.5 → 0.1.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -2
  3. data/docs/api_coverage.md +351 -0
  4. data/lib/playwright.rb +7 -1
  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 +3 -2
  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 +13 -13
  12. data/lib/playwright/channel_owners/browser_context.rb +9 -1
  13. data/lib/playwright/channel_owners/download.rb +27 -0
  14. data/lib/playwright/channel_owners/element_handle.rb +306 -0
  15. data/lib/playwright/channel_owners/frame.rb +371 -19
  16. data/lib/playwright/channel_owners/js_handle.rb +51 -0
  17. data/lib/playwright/channel_owners/page.rb +416 -19
  18. data/lib/playwright/channel_owners/request.rb +98 -0
  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 +37 -0
  28. data/lib/playwright/javascript/function.rb +37 -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/keyboard_impl.rb +36 -0
  32. data/lib/playwright/mouse_impl.rb +7 -0
  33. data/lib/playwright/playwright_api.rb +84 -29
  34. data/lib/playwright/select_option_values.rb +32 -0
  35. data/lib/playwright/timeout_settings.rb +2 -2
  36. data/lib/playwright/touchscreen_impl.rb +7 -0
  37. data/lib/playwright/url_matcher.rb +19 -0
  38. data/lib/playwright/utils.rb +18 -0
  39. data/lib/playwright/version.rb +1 -1
  40. data/lib/playwright/wait_helper.rb +1 -1
  41. data/lib/playwright_api/accessibility.rb +46 -6
  42. data/lib/playwright_api/android.rb +37 -0
  43. data/lib/playwright_api/android_device.rb +82 -0
  44. data/lib/playwright_api/android_input.rb +25 -0
  45. data/lib/playwright_api/binding_call.rb +10 -6
  46. data/lib/playwright_api/browser.rb +85 -18
  47. data/lib/playwright_api/browser_context.rb +269 -37
  48. data/lib/playwright_api/browser_type.rb +60 -11
  49. data/lib/playwright_api/cdp_session.rb +23 -1
  50. data/lib/playwright_api/chromium_browser_context.rb +18 -6
  51. data/lib/playwright_api/console_message.rb +14 -15
  52. data/lib/playwright_api/dialog.rb +48 -2
  53. data/lib/playwright_api/download.rb +47 -10
  54. data/lib/playwright_api/element_handle.rb +269 -110
  55. data/lib/playwright_api/file_chooser.rb +23 -7
  56. data/lib/playwright_api/frame.rb +439 -154
  57. data/lib/playwright_api/js_handle.rb +69 -24
  58. data/lib/playwright_api/keyboard.rb +99 -9
  59. data/lib/playwright_api/mouse.rb +22 -0
  60. data/lib/playwright_api/page.rb +856 -229
  61. data/lib/playwright_api/playwright.rb +108 -20
  62. data/lib/playwright_api/request.rb +77 -29
  63. data/lib/playwright_api/response.rb +10 -13
  64. data/lib/playwright_api/route.rb +49 -0
  65. data/lib/playwright_api/selectors.rb +20 -8
  66. data/lib/playwright_api/video.rb +8 -0
  67. data/lib/playwright_api/web_socket.rb +0 -8
  68. data/lib/playwright_api/worker.rb +25 -13
  69. data/playwright.gemspec +1 -0
  70. metadata +33 -2
@@ -1,5 +1,103 @@
1
+ require 'base64'
2
+
1
3
  module Playwright
2
4
  # @ref https://github.com/microsoft/playwright-python/blob/master/playwright/_impl/_network.py
3
5
  define_channel_owner :Request do
6
+ def after_initialize
7
+ @redirected_from = ChannelOwners::Request.from_nullable(@initializer['redirectedFrom'])
8
+ @redirected_from&.send(:update_redirected_to, self)
9
+ @timing = {
10
+ startTime: 0,
11
+ domainLookupStart: -1,
12
+ domainLookupEnd: -1,
13
+ connectStart: -1,
14
+ secureConnectionStart: -1,
15
+ connectEnd: -1,
16
+ requestStart: -1,
17
+ responseStart: -1,
18
+ responseEnd: -1,
19
+ }
20
+ @headers = parse_headers(@initializer['headers'])
21
+ end
22
+
23
+ def url
24
+ @initializer['url']
25
+ end
26
+
27
+ def resource_type
28
+ @initializer['resourceType']
29
+ end
30
+
31
+ def method
32
+ @initialize['method']
33
+ end
34
+
35
+ def post_data
36
+ post_data_buffer
37
+ end
38
+
39
+ def post_data_json
40
+ data = post_data
41
+ return unless data
42
+
43
+ content_type = @headers['content-type']
44
+ return unless content_type
45
+
46
+ if content_type == "application/x-www-form-urlencoded"
47
+ URI.decode_www_form(data).to_h
48
+ else
49
+ JSON.parse(data)
50
+ end
51
+ end
52
+
53
+ def post_data_buffer
54
+ base64_content = @initializer['postData']
55
+ if base64_content
56
+ Base64.strict_decode64(base64_content)
57
+ else
58
+ nil
59
+ end
60
+ end
61
+
62
+ def headers
63
+ @headers
64
+ end
65
+
66
+ def response
67
+ resp = @channel.send_message_to_server('response')
68
+ ChannelOwners::Response.from_nullable(resp)
69
+ end
70
+
71
+ def frame
72
+ @initializer['frame']
73
+ end
74
+
75
+ def navigation_request?
76
+ @initializer['isNavigationRequest']
77
+ end
78
+
79
+ def failure
80
+ @failure_text
81
+ end
82
+
83
+ attr_reader :headers, :redirected_from, :redirected_to, :timing
84
+
85
+ private def update_redirected_to(request)
86
+ @redirected_to = request
87
+ end
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
+
97
+ private def parse_headers(headers)
98
+ headers.map do |header|
99
+ [header['name'].downcase, header['value']]
100
+ end.to_h
101
+ end
4
102
  end
5
103
  end
@@ -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
@@ -25,6 +25,43 @@ module Playwright
25
25
  )
26
26
  ::Playwright::ChannelOwner.from(resp)
27
27
  end
28
+
29
+ def eval_on_selector(channel, selector)
30
+ value = channel.send_message_to_server(
31
+ 'evalOnSelector',
32
+ selector: selector,
33
+ expression: @expression,
34
+ isFunction: false,
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: @expression,
45
+ isFunction: false,
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: @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
28
65
  end
29
66
  end
30
67
  end
@@ -25,6 +25,43 @@ module Playwright
25
25
  )
26
26
  ::Playwright::ChannelOwner.from(resp)
27
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
28
65
  end
29
66
  end
30
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