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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/docs/api_coverage.md +159 -101
  4. data/lib/playwright.rb +5 -2
  5. data/lib/playwright/{input_types/android_input.rb → android_input_impl.rb} +5 -1
  6. data/lib/playwright/api_implementation.rb +18 -0
  7. data/lib/playwright/channel.rb +13 -6
  8. data/lib/playwright/channel_owner.rb +3 -5
  9. data/lib/playwright/channel_owners/android.rb +1 -1
  10. data/lib/playwright/channel_owners/android_device.rb +12 -12
  11. data/lib/playwright/channel_owners/binding_call.rb +3 -0
  12. data/lib/playwright/channel_owners/browser.rb +1 -1
  13. data/lib/playwright/channel_owners/browser_context.rb +157 -3
  14. data/lib/playwright/channel_owners/dialog.rb +28 -0
  15. data/lib/playwright/channel_owners/download.rb +27 -0
  16. data/lib/playwright/channel_owners/element_handle.rb +15 -4
  17. data/lib/playwright/channel_owners/frame.rb +24 -5
  18. data/lib/playwright/channel_owners/js_handle.rb +1 -1
  19. data/lib/playwright/channel_owners/page.rb +367 -29
  20. data/lib/playwright/channel_owners/playwright.rb +4 -0
  21. data/lib/playwright/channel_owners/request.rb +35 -3
  22. data/lib/playwright/channel_owners/response.rb +60 -0
  23. data/lib/playwright/channel_owners/route.rb +78 -0
  24. data/lib/playwright/channel_owners/selectors.rb +19 -1
  25. data/lib/playwright/errors.rb +1 -1
  26. data/lib/playwright/event_emitter.rb +8 -1
  27. data/lib/playwright/event_emitter_proxy.rb +49 -0
  28. data/lib/playwright/file_chooser_impl.rb +23 -0
  29. data/lib/playwright/http_headers.rb +20 -0
  30. data/lib/playwright/input_files.rb +1 -1
  31. data/lib/playwright/{input_types/keyboard.rb → keyboard_impl.rb} +5 -1
  32. data/lib/playwright/mouse_impl.rb +7 -0
  33. data/lib/playwright/playwright_api.rb +59 -20
  34. data/lib/playwright/route_handler_entry.rb +36 -0
  35. data/lib/playwright/select_option_values.rb +14 -4
  36. data/lib/playwright/touchscreen_impl.rb +7 -0
  37. data/lib/playwright/utils.rb +4 -3
  38. data/lib/playwright/version.rb +1 -1
  39. data/lib/playwright/wait_helper.rb +1 -1
  40. data/lib/playwright_api/android.rb +82 -11
  41. data/lib/playwright_api/android_device.rb +148 -32
  42. data/lib/playwright_api/android_input.rb +17 -13
  43. data/lib/playwright_api/android_socket.rb +16 -0
  44. data/lib/playwright_api/android_web_view.rb +21 -0
  45. data/lib/playwright_api/binding_call.rb +14 -5
  46. data/lib/playwright_api/browser.rb +22 -23
  47. data/lib/playwright_api/browser_context.rb +49 -35
  48. data/lib/playwright_api/browser_type.rb +24 -64
  49. data/lib/playwright_api/chromium_browser_context.rb +10 -8
  50. data/lib/playwright_api/console_message.rb +10 -6
  51. data/lib/playwright_api/dialog.rb +37 -6
  52. data/lib/playwright_api/download.rb +28 -11
  53. data/lib/playwright_api/element_handle.rb +107 -96
  54. data/lib/playwright_api/file_chooser.rb +18 -10
  55. data/lib/playwright_api/frame.rb +124 -117
  56. data/lib/playwright_api/js_handle.rb +20 -22
  57. data/lib/playwright_api/keyboard.rb +5 -5
  58. data/lib/playwright_api/page.rb +206 -148
  59. data/lib/playwright_api/playwright.rb +33 -45
  60. data/lib/playwright_api/request.rb +11 -12
  61. data/lib/playwright_api/response.rb +30 -16
  62. data/lib/playwright_api/route.rb +27 -5
  63. data/lib/playwright_api/selectors.rb +14 -10
  64. data/lib/playwright_api/web_socket.rb +10 -1
  65. data/lib/playwright_api/worker.rb +13 -13
  66. metadata +16 -7
  67. data/lib/playwright/input_type.rb +0 -19
  68. data/lib/playwright/input_types/mouse.rb +0 -4
  69. 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
- @initialize['method']
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
@@ -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
@@ -36,7 +36,7 @@ module Playwright
36
36
 
37
37
  private def mime_type_for(filepath)
38
38
  mime_types = MIME::Types.type_for(filepath)
39
- mime_types.first || 'application/octet-stream'
39
+ mime_types.first.to_s || 'application/octet-stream'
40
40
  end
41
41
  end
42
42
  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
@@ -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,15 +116,10 @@ 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) }
82
121
  else
83
- object
122
+ ::Playwright::PlaywrightApi.wrap(object) || object
84
123
  end
85
124
  end
86
125