playwright-ruby-client 1.17.beta1 → 1.18.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/api_request.md +7 -0
  3. data/documentation/docs/api/api_request_context.md +161 -0
  4. data/documentation/docs/api/api_response.md +104 -0
  5. data/documentation/docs/api/browser_context.md +4 -0
  6. data/documentation/docs/api/download.md +1 -3
  7. data/documentation/docs/api/frame.md +1 -1
  8. data/documentation/docs/api/frame_locator.md +10 -1
  9. data/documentation/docs/api/locator.md +19 -47
  10. data/documentation/docs/api/page.md +5 -1
  11. data/documentation/docs/include/api_coverage.md +35 -13
  12. data/lib/playwright/api_response_impl.rb +73 -0
  13. data/lib/playwright/channel_owners/api_request_context.rb +232 -0
  14. data/lib/playwright/channel_owners/browser_context.rb +8 -1
  15. data/lib/playwright/channel_owners/frame.rb +2 -2
  16. data/lib/playwright/channel_owners/local_utils.rb +14 -0
  17. data/lib/playwright/channel_owners/page.rb +6 -2
  18. data/lib/playwright/frame_locator_impl.rb +2 -1
  19. data/lib/playwright/locator_impl.rb +62 -3
  20. data/lib/playwright/tracing_impl.rb +21 -7
  21. data/lib/playwright/version.rb +2 -2
  22. data/lib/playwright_api/android.rb +6 -6
  23. data/lib/playwright_api/android_device.rb +8 -8
  24. data/lib/playwright_api/api_request.rb +20 -0
  25. data/lib/playwright_api/api_request_context.rb +61 -14
  26. data/lib/playwright_api/api_response.rb +81 -0
  27. data/lib/playwright_api/browser.rb +6 -6
  28. data/lib/playwright_api/browser_context.rb +12 -12
  29. data/lib/playwright_api/browser_type.rb +6 -6
  30. data/lib/playwright_api/cdp_session.rb +6 -6
  31. data/lib/playwright_api/console_message.rb +6 -6
  32. data/lib/playwright_api/dialog.rb +6 -6
  33. data/lib/playwright_api/download.rb +0 -4
  34. data/lib/playwright_api/element_handle.rb +6 -6
  35. data/lib/playwright_api/frame.rb +8 -8
  36. data/lib/playwright_api/frame_locator.rb +11 -2
  37. data/lib/playwright_api/js_handle.rb +6 -6
  38. data/lib/playwright_api/local_utils.rb +9 -0
  39. data/lib/playwright_api/locator.rb +16 -46
  40. data/lib/playwright_api/page.rb +17 -12
  41. data/lib/playwright_api/playwright.rb +6 -6
  42. data/lib/playwright_api/request.rb +6 -6
  43. data/lib/playwright_api/response.rb +6 -6
  44. data/lib/playwright_api/route.rb +6 -6
  45. data/lib/playwright_api/selectors.rb +6 -6
  46. data/lib/playwright_api/web_socket.rb +6 -6
  47. data/lib/playwright_api/worker.rb +8 -8
  48. data/playwright.gemspec +1 -1
  49. metadata +15 -8
@@ -1,4 +1,236 @@
1
+ require 'base64'
2
+
1
3
  module Playwright
2
4
  define_channel_owner :APIRequestContext do
5
+ def dispose
6
+ @channel.send_message_to_server('dispose')
7
+ end
8
+
9
+ def delete(
10
+ url,
11
+ data: nil,
12
+ failOnStatusCode: nil,
13
+ form: nil,
14
+ headers: nil,
15
+ ignoreHTTPSErrors: nil,
16
+ multipart: nil,
17
+ params: nil,
18
+ timeout: nil)
19
+ fetch(
20
+ url,
21
+ method: 'DELETE',
22
+ data: data,
23
+ failOnStatusCode: failOnStatusCode,
24
+ form: form,
25
+ headers: headers,
26
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
27
+ multipart: multipart,
28
+ params: params,
29
+ timeout: timeout,
30
+ )
31
+ end
32
+
33
+ def head(
34
+ url,
35
+ failOnStatusCode: nil,
36
+ headers: nil,
37
+ ignoreHTTPSErrors: nil,
38
+ params: nil,
39
+ timeout: nil)
40
+ fetch(
41
+ url,
42
+ method: 'HEAD',
43
+ failOnStatusCode: failOnStatusCode,
44
+ headers: headers,
45
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
46
+ params: params,
47
+ timeout: timeout,
48
+ )
49
+ end
50
+
51
+ def get(
52
+ url,
53
+ failOnStatusCode: nil,
54
+ headers: nil,
55
+ ignoreHTTPSErrors: nil,
56
+ params: nil,
57
+ timeout: nil)
58
+ fetch(
59
+ url,
60
+ method: 'GET',
61
+ failOnStatusCode: failOnStatusCode,
62
+ headers: headers,
63
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
64
+ params: params,
65
+ timeout: timeout,
66
+ )
67
+ end
68
+
69
+ def patch(
70
+ url,
71
+ data: nil,
72
+ failOnStatusCode: nil,
73
+ form: nil,
74
+ headers: nil,
75
+ ignoreHTTPSErrors: nil,
76
+ multipart: nil,
77
+ params: nil,
78
+ timeout: nil)
79
+ fetch(
80
+ url,
81
+ method: 'PATCH',
82
+ data: data,
83
+ failOnStatusCode: failOnStatusCode,
84
+ form: form,
85
+ headers: headers,
86
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
87
+ multipart: multipart,
88
+ params: params,
89
+ timeout: timeout,
90
+ )
91
+ end
92
+
93
+ def put(
94
+ url,
95
+ data: nil,
96
+ failOnStatusCode: nil,
97
+ form: nil,
98
+ headers: nil,
99
+ ignoreHTTPSErrors: nil,
100
+ multipart: nil,
101
+ params: nil,
102
+ timeout: nil)
103
+ fetch(
104
+ url,
105
+ method: 'PUT',
106
+ data: data,
107
+ failOnStatusCode: failOnStatusCode,
108
+ form: form,
109
+ headers: headers,
110
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
111
+ multipart: multipart,
112
+ params: params,
113
+ timeout: timeout,
114
+ )
115
+ end
116
+
117
+ def post(
118
+ url,
119
+ data: nil,
120
+ failOnStatusCode: nil,
121
+ form: nil,
122
+ headers: nil,
123
+ ignoreHTTPSErrors: nil,
124
+ multipart: nil,
125
+ params: nil,
126
+ timeout: nil)
127
+ fetch(
128
+ url,
129
+ method: 'POST',
130
+ data: data,
131
+ failOnStatusCode: failOnStatusCode,
132
+ form: form,
133
+ headers: headers,
134
+ ignoreHTTPSErrors: ignoreHTTPSErrors,
135
+ multipart: multipart,
136
+ params: params,
137
+ timeout: timeout,
138
+ )
139
+ end
140
+
141
+ def fetch(
142
+ urlOrRequest,
143
+ data: nil,
144
+ failOnStatusCode: nil,
145
+ form: nil,
146
+ headers: nil,
147
+ ignoreHTTPSErrors: nil,
148
+ method: nil,
149
+ multipart: nil,
150
+ params: nil,
151
+ timeout: nil)
152
+
153
+ if [ChannelOwners::Request, String].none? { |type| urlOrRequest.is_a?(type) }
154
+ raise ArgumentError.new("First argument must be either URL string or Request")
155
+ end
156
+ if [data, form, multipart].compact.count > 1
157
+ raise ArgumentError.new("Only one of 'data', 'form' or 'multipart' can be specified")
158
+ end
159
+
160
+ request = urlOrRequest.is_a?(ChannelOwners::Request) ? urlOrRequest : nil
161
+ headers_obj = headers || request&.headers
162
+ fetch_params = {
163
+ url: request&.url || urlOrRequest,
164
+ params: object_to_array(params),
165
+ method: method || request&.method || 'GET',
166
+ headers: headers_obj ? HttpHeaders.new(headers_obj).as_serialized : nil,
167
+ }
168
+
169
+ json_data = nil
170
+ form_data = nil
171
+ multipart_data = nil
172
+ post_data_buffer = nil
173
+ if data
174
+ case data
175
+ when String
176
+ if headers_obj&.any? { |key, value| key.downcase == 'content-type' && value == 'application/json' }
177
+ json_data = data
178
+ else
179
+ post_data_buffer = data
180
+ end
181
+ when Hash, Array, Numeric, true, false
182
+ json_data = data
183
+ else
184
+ raise ArgumentError.new("Unsupported 'data' type: #{data.class}")
185
+ end
186
+ elsif form
187
+ form_data = object_to_array(form)
188
+ elsif multipart
189
+ multipart_data = multipart.map do |name, value|
190
+ if file_payload?(value)
191
+ { name: name, file: file_payload_to_json(value) }
192
+ else
193
+ { name: name, value: value.to_s }
194
+ end
195
+ end
196
+ end
197
+
198
+ if !json_data && !form_data && !multipart_data
199
+ post_data_buffer ||= request&.post_data_buffer
200
+ end
201
+ if post_data_buffer
202
+ fetch_params[:postData] = Base64.strict_encode64(post_data_buffer)
203
+ end
204
+
205
+ fetch_params[:jsonData] = json_data
206
+ fetch_params[:formData] = form_data
207
+ fetch_params[:multipartData] = multipart_data
208
+ fetch_params[:timeout] = timeout
209
+ fetch_params[:failOnStatusCode] = failOnStatusCode
210
+ fetch_params[:ignoreHTTPSErrors] = ignoreHTTPSErrors
211
+ fetch_params.compact!
212
+ response = @channel.send_message_to_server('fetch', fetch_params)
213
+
214
+ APIResponseImpl.new(self, response)
215
+ end
216
+
217
+ private def file_payload?(value)
218
+ value.is_a?(Hash) &&
219
+ %w(name mimeType buffer).all? { |key| value.has_key?(key) || value.has_key?(key.to_sym) }
220
+ end
221
+
222
+ private def file_payload_to_json(payload)
223
+ {
224
+ name: payload[:name] || payload['name'],
225
+ mimeType: payload[:mimeType] || payload['mimeType'],
226
+ buffer: Base64.strict_encode64(payload[:buffer] || payload['buffer'])
227
+ }
228
+ end
229
+
230
+ private def object_to_array(hash)
231
+ hash&.map do |key, value|
232
+ { name: key, value: value.to_s }
233
+ end
234
+ end
3
235
  end
4
236
  end
@@ -4,7 +4,7 @@ module Playwright
4
4
  include Utils::Errors::SafeCloseError
5
5
  attr_accessor :browser
6
6
  attr_writer :owner_page, :options
7
- attr_reader :tracing
7
+ attr_reader :tracing, :request
8
8
 
9
9
  private def after_initialize
10
10
  @pages = Set.new
@@ -15,6 +15,8 @@ module Playwright
15
15
  @background_pages = Set.new
16
16
 
17
17
  @tracing = TracingImpl.new(@channel, self)
18
+ @request = ChannelOwners::APIRequestContext.from(@initializer['APIRequestContext'])
19
+
18
20
  @channel.on('bindingCall', ->(params) { on_binding(ChannelOwners::BindingCall.from(params['binding'])) })
19
21
  @channel.once('close', ->(_) { on_close })
20
22
  @channel.on('page', ->(params) { on_page(ChannelOwners::Page.from(params['page']) )})
@@ -358,5 +360,10 @@ module Playwright
358
360
  private def base_url
359
361
  @options[:baseURL]
360
362
  end
363
+
364
+ # called from Tracing
365
+ private def remote_connection?
366
+ @connection.remote?
367
+ end
361
368
  end
362
369
  end
@@ -400,8 +400,8 @@ module Playwright
400
400
  nil
401
401
  end
402
402
 
403
- def locator(selector)
404
- LocatorImpl.new(frame: self, timeout_settings: @page.send(:timeout_settings), selector: selector)
403
+ def locator(selector, hasText: nil)
404
+ LocatorImpl.new(frame: self, timeout_settings: @page.send(:timeout_settings), selector: selector, hasText: hasText)
405
405
  end
406
406
 
407
407
  def frame_locator(selector)
@@ -0,0 +1,14 @@
1
+ module Playwright
2
+ define_channel_owner :LocalUtils do
3
+ # @param zip_file [String]
4
+ # @param name_value_array [Array<Hash<{name: string, value: string}>>]
5
+ def zip(zip_file, name_value_array)
6
+ params = {
7
+ zipFile: zip_file,
8
+ entries: name_value_array,
9
+ }
10
+ @channel.send_message_to_server('zip', params)
11
+ nil
12
+ end
13
+ end
14
+ end
@@ -558,8 +558,8 @@ module Playwright
558
558
  timeout: timeout)
559
559
  end
560
560
 
561
- def locator(selector)
562
- @main_frame.locator(selector)
561
+ def locator(selector, hasText: nil)
562
+ @main_frame.locator(selector, hasText: hasText)
563
563
  end
564
564
 
565
565
  def frame_locator(selector)
@@ -733,6 +733,10 @@ module Playwright
733
733
  @workers.to_a
734
734
  end
735
735
 
736
+ def request
737
+ @browser_context.request
738
+ end
739
+
736
740
  def pause
737
741
  @browser_context.send(:pause)
738
742
  end
@@ -6,11 +6,12 @@ module Playwright
6
6
  @frame_selector = frame_selector
7
7
  end
8
8
 
9
- def locator(selector)
9
+ def locator(selector, hasText: nil)
10
10
  LocatorImpl.new(
11
11
  frame: @frame,
12
12
  timeout_settings: @timeout_settings,
13
13
  selector: "#{@frame_selector} >> control=enter-frame >> #{selector}",
14
+ hasText: hasText,
14
15
  )
15
16
  end
16
17
 
@@ -1,9 +1,46 @@
1
+ require 'json'
2
+
1
3
  module Playwright
4
+ class EscapeWithQuotes
5
+ def initialize(text, char = "'")
6
+ stringified = text.to_json
7
+ escaped_text = stringified[1...-1].gsub(/\\"/, '"')
8
+
9
+ case char
10
+ when '"'
11
+ text = escaped_text.gsub(/["]/, '\\"')
12
+ @text = "\"#{text}\""
13
+ when "'"
14
+ text = escaped_text.gsub(/[']/, '\\\'')
15
+ @text = "'#{text}'"
16
+ else
17
+ raise ArgumentError.new('Invalid escape char')
18
+ end
19
+ end
20
+
21
+ def to_s
22
+ @text
23
+ end
24
+ end
25
+
2
26
  define_api_implementation :LocatorImpl do
3
- def initialize(frame:, timeout_settings:, selector:)
27
+ def initialize(frame:, timeout_settings:, selector:, hasText: nil)
4
28
  @frame = frame
5
29
  @timeout_settings = timeout_settings
6
- @selector = selector
30
+ @selector =
31
+ case hasText
32
+ when Regexp
33
+ source = EscapeWithQuotes.new(hasText.source, '"')
34
+ flags = []
35
+ flags << 'ms' if (hasText.options & Regexp::MULTILINE) != 0
36
+ flags << 'i' if (hasText.options & Regexp::IGNORECASE) != 0
37
+ "#{selector} >> :scope:text-matches(#{source}, \"#{flags.join('')}\")"
38
+ when String
39
+ text = EscapeWithQuotes.new(hasText, '"')
40
+ "#{selector} >> :scope:has-text(#{text})"
41
+ else
42
+ selector
43
+ end
7
44
  end
8
45
 
9
46
  def to_s
@@ -102,6 +139,27 @@ module Playwright
102
139
  @frame.dispatch_event(@selector, type, strict: true, eventInit: eventInit, timeout: timeout)
103
140
  end
104
141
 
142
+ def drag_to(target,
143
+ force: nil,
144
+ noWaitAfter: nil,
145
+ sourcePosition: nil,
146
+ targetPosition: nil,
147
+ timeout: nil,
148
+ trial: nil)
149
+
150
+ @frame.drag_and_drop(
151
+ @selector,
152
+ target.instance_variable_get(:@selector),
153
+ force: force,
154
+ noWaitAfter: noWaitAfter,
155
+ sourcePosition: sourcePosition,
156
+ targetPosition: targetPosition,
157
+ timeout: timeout,
158
+ trial: trial,
159
+ strict: true,
160
+ )
161
+ end
162
+
105
163
  def evaluate(expression, arg: nil, timeout: nil)
106
164
  with_element(timeout: timeout) do |handle|
107
165
  handle.evaluate(expression, arg: arg)
@@ -122,11 +180,12 @@ module Playwright
122
180
  @frame.fill(@selector, value, strict: true, force: force, noWaitAfter: noWaitAfter, timeout: timeout)
123
181
  end
124
182
 
125
- def locator(selector)
183
+ def locator(selector, hasText: nil)
126
184
  LocatorImpl.new(
127
185
  frame: @frame,
128
186
  timeout_settings: @timeout_settings,
129
187
  selector: "#{@selector} >> #{selector}",
188
+ hasText: hasText,
130
189
  )
131
190
  end
132
191
 
@@ -20,21 +20,35 @@ module Playwright
20
20
  end
21
21
 
22
22
  def stop_chunk(path: nil)
23
- do_stop_chunk(path: path)
23
+ do_stop_chunk(file_path: path)
24
24
  end
25
25
 
26
26
  def stop(path: nil)
27
- do_stop_chunk(path: path)
27
+ do_stop_chunk(file_path: path)
28
28
  @channel.send_message_to_server('tracingStop')
29
29
  end
30
30
 
31
- private def do_stop_chunk(path:)
32
- result = @channel.send_message_to_server_result('tracingStopChunk', save: !path.nil?, skipCompress: false)
33
- artifact = ChannelOwners::Artifact.from_nullable(result['artifact'])
34
- return unless artifact
31
+ private def do_stop_chunk(file_path:)
32
+ mode = 'doNotSave'
33
+ if file_path
34
+ if @context.send(:remote_connection?)
35
+ mode = 'compressTrace'
36
+ else
37
+ mode = 'compressTraceAndSources'
38
+ end
39
+ end
35
40
 
36
- artifact.save_as(path)
41
+ result = @channel.send_message_to_server_result('tracingStopChunk', mode: mode)
42
+ return unless file_path # Not interested in artifacts.
43
+ return unless result['artifact'] # The artifact may be missing if the browser closed while stopping tracing.
44
+
45
+ artifact = ChannelOwners::Artifact.from(result['artifact'])
46
+ artifact.save_as(file_path)
37
47
  artifact.delete
48
+
49
+ # // Add local sources to the remote trace if necessary.
50
+ # if (result.sourceEntries?.length)
51
+ # await this._context._localUtils.zip(filePath, result.sourceEntries);
38
52
  end
39
53
  end
40
54
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.17.beta1'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.17.0'
4
+ VERSION = '1.18.1'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.18.1'
6
6
  end
@@ -36,12 +36,6 @@ module Playwright
36
36
  end
37
37
  alias_method :default_timeout=, :set_default_timeout
38
38
 
39
- # -- inherited from EventEmitter --
40
- # @nodoc
41
- def off(event, callback)
42
- event_emitter_proxy.off(event, callback)
43
- end
44
-
45
39
  # -- inherited from EventEmitter --
46
40
  # @nodoc
47
41
  def once(event, callback)
@@ -54,6 +48,12 @@ module Playwright
54
48
  event_emitter_proxy.on(event, callback)
55
49
  end
56
50
 
51
+ # -- inherited from EventEmitter --
52
+ # @nodoc
53
+ def off(event, callback)
54
+ event_emitter_proxy.off(event, callback)
55
+ end
56
+
57
57
  private def event_emitter_proxy
58
58
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
59
59
  end
@@ -164,20 +164,14 @@ module Playwright
164
164
  raise NotImplementedError.new('web_views is not implemented yet.')
165
165
  end
166
166
 
167
- # @nodoc
168
- def tree
169
- wrap_impl(@impl.tree)
170
- end
171
-
172
167
  # @nodoc
173
168
  def tap_on(selector, duration: nil, timeout: nil)
174
169
  wrap_impl(@impl.tap_on(unwrap_impl(selector), duration: unwrap_impl(duration), timeout: unwrap_impl(timeout)))
175
170
  end
176
171
 
177
- # -- inherited from EventEmitter --
178
172
  # @nodoc
179
- def off(event, callback)
180
- event_emitter_proxy.off(event, callback)
173
+ def tree
174
+ wrap_impl(@impl.tree)
181
175
  end
182
176
 
183
177
  # -- inherited from EventEmitter --
@@ -192,6 +186,12 @@ module Playwright
192
186
  event_emitter_proxy.on(event, callback)
193
187
  end
194
188
 
189
+ # -- inherited from EventEmitter --
190
+ # @nodoc
191
+ def off(event, callback)
192
+ event_emitter_proxy.off(event, callback)
193
+ end
194
+
195
195
  private def event_emitter_proxy
196
196
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
197
197
  end
@@ -0,0 +1,20 @@
1
+ module Playwright
2
+ # Exposes API that can be used for the Web API testing. Each Playwright browser context has a APIRequestContext instance
3
+ # attached which shares cookies with the page context. Its also possible to create a new APIRequestContext instance
4
+ # manually. For more information see [here](./class-apirequestcontext).
5
+ class APIRequest < PlaywrightApi
6
+
7
+ # Creates new instances of `APIRequestContext`.
8
+ def new_context(
9
+ baseURL: nil,
10
+ extraHTTPHeaders: nil,
11
+ httpCredentials: nil,
12
+ ignoreHTTPSErrors: nil,
13
+ proxy: nil,
14
+ storageState: nil,
15
+ timeout: nil,
16
+ userAgent: nil)
17
+ raise NotImplementedError.new('new_context is not implemented yet.')
18
+ end
19
+ end
20
+ end