playwright-ruby-client 1.59.1 → 1.61.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/documentation/docs/api/api_request.md +25 -1
  3. data/documentation/docs/api/api_request_context.md +13 -11
  4. data/documentation/docs/api/api_response.md +18 -0
  5. data/documentation/docs/api/browser.md +18 -0
  6. data/documentation/docs/api/browser_context.md +7 -1
  7. data/documentation/docs/api/browser_type.md +19 -0
  8. data/documentation/docs/api/credentials.md +132 -0
  9. data/documentation/docs/api/frame.md +5 -4
  10. data/documentation/docs/api/frame_locator.md +1 -0
  11. data/documentation/docs/api/locator.md +37 -2
  12. data/documentation/docs/api/locator_assertions.md +1 -1
  13. data/documentation/docs/api/page.md +24 -4
  14. data/documentation/docs/api/page_assertions.md +28 -0
  15. data/documentation/docs/api/playwright.md +5 -0
  16. data/documentation/docs/api/touchscreen.md +1 -1
  17. data/documentation/docs/api/tracing.md +29 -0
  18. data/documentation/docs/api/web_storage.md +65 -0
  19. data/documentation/docs/include/api_coverage.md +30 -56
  20. data/lib/playwright/api_request_impl.rb +70 -0
  21. data/lib/playwright/api_response_impl.rb +8 -0
  22. data/lib/playwright/channel_owners/api_request_context.rb +5 -1
  23. data/lib/playwright/channel_owners/binding_call.rb +3 -9
  24. data/lib/playwright/channel_owners/browser.rb +17 -0
  25. data/lib/playwright/channel_owners/browser_context.rb +12 -55
  26. data/lib/playwright/channel_owners/browser_type.rb +35 -1
  27. data/lib/playwright/channel_owners/frame.rb +118 -13
  28. data/lib/playwright/channel_owners/json_pipe.rb +4 -0
  29. data/lib/playwright/channel_owners/local_utils.rb +11 -2
  30. data/lib/playwright/channel_owners/page.rb +28 -13
  31. data/lib/playwright/channel_owners/playwright.rb +4 -0
  32. data/lib/playwright/channel_owners/tracing.rb +105 -4
  33. data/lib/playwright/connection.rb +12 -2
  34. data/lib/playwright/console_message_impl.rb +10 -1
  35. data/lib/playwright/credentials_impl.rb +35 -0
  36. data/lib/playwright/errors.rb +7 -3
  37. data/lib/playwright/events.rb +7 -0
  38. data/lib/playwright/json_pipe_transport.rb +40 -0
  39. data/lib/playwright/locator_assertions_impl.rb +21 -4
  40. data/lib/playwright/locator_impl.rb +17 -3
  41. data/lib/playwright/locator_utils.rb +11 -1
  42. data/lib/playwright/page_assertions_impl.rb +33 -3
  43. data/lib/playwright/screencast.rb +8 -2
  44. data/lib/playwright/url_matcher.rb +35 -0
  45. data/lib/playwright/version.rb +2 -2
  46. data/lib/playwright/waiter.rb +9 -41
  47. data/lib/playwright/web_storage_impl.rb +34 -0
  48. data/lib/playwright.rb +1 -0
  49. data/lib/playwright_api/api_request.rb +1 -1
  50. data/lib/playwright_api/api_request_context.rb +21 -17
  51. data/lib/playwright_api/api_response.rb +12 -0
  52. data/lib/playwright_api/browser.rb +8 -8
  53. data/lib/playwright_api/browser_context.rb +20 -13
  54. data/lib/playwright_api/browser_type.rb +12 -9
  55. data/lib/playwright_api/cdp_session.rb +6 -6
  56. data/lib/playwright_api/credentials.rb +120 -0
  57. data/lib/playwright_api/dialog.rb +6 -6
  58. data/lib/playwright_api/element_handle.rb +6 -6
  59. data/lib/playwright_api/frame.rb +25 -14
  60. data/lib/playwright_api/frame_locator.rb +2 -1
  61. data/lib/playwright_api/js_handle.rb +6 -6
  62. data/lib/playwright_api/locator.rb +39 -10
  63. data/lib/playwright_api/locator_assertions.rb +2 -2
  64. data/lib/playwright_api/page.rb +44 -25
  65. data/lib/playwright_api/page_assertions.rb +22 -0
  66. data/lib/playwright_api/playwright.rb +7 -7
  67. data/lib/playwright_api/request.rb +8 -8
  68. data/lib/playwright_api/response.rb +6 -6
  69. data/lib/playwright_api/route.rb +6 -6
  70. data/lib/playwright_api/touchscreen.rb +1 -1
  71. data/lib/playwright_api/tracing.rb +27 -4
  72. data/lib/playwright_api/web_socket.rb +6 -6
  73. data/lib/playwright_api/web_storage.rb +48 -0
  74. data/lib/playwright_api/worker.rb +8 -8
  75. data/sig/playwright.rbs +51 -39
  76. metadata +11 -12
  77. data/documentation/docs/api/experimental/android.md +0 -42
  78. data/documentation/docs/api/experimental/android_device.md +0 -109
  79. data/documentation/docs/api/experimental/android_input.md +0 -43
  80. data/documentation/docs/api/experimental/android_socket.md +0 -7
  81. data/documentation/docs/api/experimental/android_web_view.md +0 -7
  82. data/lib/playwright_api/android.rb +0 -68
  83. data/lib/playwright_api/android_device.rb +0 -229
  84. data/lib/playwright_api/android_input.rb +0 -34
  85. data/lib/playwright_api/android_socket.rb +0 -18
  86. data/lib/playwright_api/android_web_view.rb +0 -24
@@ -0,0 +1,4 @@
1
+ module Playwright
2
+ define_channel_owner :JsonPipe do
3
+ end
4
+ end
@@ -37,8 +37,12 @@ module Playwright
37
37
  @channel.async_send_message_to_server('harClose', harId: har_id)
38
38
  end
39
39
 
40
- def har_unzip(zip_file, har_file)
41
- @channel.send_message_to_server('harUnzip', zipFile: zip_file, harFile: har_file)
40
+ def har_unzip(zip_file, har_file, resources_dir: nil)
41
+ @channel.send_message_to_server('harUnzip', {
42
+ zipFile: zip_file,
43
+ harFile: har_file,
44
+ resourcesDir: resources_dir,
45
+ }.compact)
42
46
  end
43
47
 
44
48
  def tracing_started(traces_dir, trace_name)
@@ -58,6 +62,11 @@ module Playwright
58
62
  nil
59
63
  end
60
64
 
65
+ def connect(params)
66
+ result = @channel.send_message_to_server_result('connect', params)
67
+ ChannelOwners::JsonPipe.from(result['pipe'])
68
+ end
69
+
61
70
  private def parse_device_descriptor(descriptor)
62
71
  # This return value can be passed into Browser#new_context as it is.
63
72
  # ex:
@@ -53,7 +53,7 @@ module Playwright
53
53
  on_frame_detached(ChannelOwners::Frame.from(params['frame']))
54
54
  })
55
55
  @channel.on('pageError', ->(params) {
56
- emit(Events::Page::PageError, Error.parse(params['error']['error']))
56
+ emit(Events::Page::PageError, Error.parse(params.dig('error', 'error') || params['error']))
57
57
  })
58
58
  @channel.on('route', ->(params) { on_route(ChannelOwners::Route.from(params['route'])) })
59
59
  if @initializer['video']
@@ -86,16 +86,26 @@ module Playwright
86
86
  :viewport_size,
87
87
  :main_frame
88
88
 
89
+ def local_storage
90
+ @local_storage ||= WebStorageImpl.new(self, 'local')
91
+ end
92
+
93
+ def session_storage
94
+ @session_storage ||= WebStorageImpl.new(self, 'session')
95
+ end
96
+
89
97
  private def on_frame_attached(frame)
90
98
  frame.send(:update_page_from_page, self)
91
99
  @frames << frame
92
100
  emit(Events::Page::FrameAttached, frame)
101
+ @browser_context.emit(Events::BrowserContext::FrameAttached, frame)
93
102
  end
94
103
 
95
104
  private def on_frame_detached(frame)
96
105
  @frames.delete(frame)
97
106
  frame.detached = true
98
107
  emit(Events::Page::FrameDetached, frame)
108
+ @browser_context.emit(Events::BrowserContext::FrameDetached, frame)
99
109
  end
100
110
 
101
111
  private def on_route(route)
@@ -153,6 +163,7 @@ module Playwright
153
163
  @closed_or_crashed_promise.fulfill(close_error_with_reason)
154
164
  end
155
165
  emit(Events::Page::Close)
166
+ @browser_context.emit(Events::BrowserContext::PageClose, self)
156
167
  end
157
168
 
158
169
  private def on_crash
@@ -171,6 +182,7 @@ module Playwright
171
182
  artifact: artifact,
172
183
  )
173
184
  emit(Events::Page::Download, download)
185
+ @browser_context.emit(Events::BrowserContext::Download, download)
174
186
  end
175
187
 
176
188
  private def on_viewport_size_changed(params)
@@ -309,12 +321,8 @@ module Playwright
309
321
  ChannelOwners::Disposable.from(result['disposable'])
310
322
  end
311
323
 
312
- def expose_binding(name, callback, handle: nil)
313
- params = {
314
- name: name,
315
- needsHandle: handle,
316
- }.compact
317
- result = @channel.send_message_to_server_result('exposeBinding', params)
324
+ def expose_binding(name, callback)
325
+ result = @channel.send_message_to_server_result('exposeBinding', name: name)
318
326
  @bindings[name] = callback
319
327
  ChannelOwners::Disposable.from(result['disposable'])
320
328
  end
@@ -436,7 +444,7 @@ module Playwright
436
444
 
437
445
  def route_from_har(har, notFound: nil, update: nil, url: nil, updateContent: nil, updateMode: nil)
438
446
  if update
439
- @browser_context.send(:record_into_har, har, self, url: url, update_content: updateContent, update_mode: updateMode)
447
+ @browser_context.tracing.send(:record_into_har, har, self, url: url, update_content: updateContent, update_mode: updateMode)
440
448
  return
441
449
  end
442
450
 
@@ -513,13 +521,15 @@ module Playwright
513
521
  end
514
522
  if @owned_context
515
523
  @owned_context.close
524
+ elsif runBeforeUnload
525
+ @channel.send_message_to_server('runBeforeUnload')
516
526
  else
517
- options = { runBeforeUnload: runBeforeUnload }.compact
527
+ options = { reason: reason }.compact
518
528
  @channel.send_message_to_server('close', options)
519
529
  end
520
530
  nil
521
531
  rescue => err
522
- raise if !target_closed_error?(err) || !runBeforeUnload
532
+ raise if !target_closed_error?(err) || runBeforeUnload
523
533
  end
524
534
 
525
535
  def closed?
@@ -676,9 +686,14 @@ module Playwright
676
686
  @channel.send_message_to_server('cancelPickLocator')
677
687
  end
678
688
 
679
- def aria_snapshot(depth: nil, mode: nil, timeout: nil, _track: nil)
689
+ def hide_highlight
690
+ @channel.send_message_to_server('hideHighlight')
691
+ end
692
+
693
+ def aria_snapshot(boxes: nil, depth: nil, mode: nil, timeout: nil, _track: nil)
680
694
  params = { selector: 'body' }
681
695
  params[:timeout] = @timeout_settings.timeout(timeout)
696
+ params[:boxes] = boxes unless boxes.nil?
682
697
  params[:depth] = depth if depth
683
698
  params[:mode] = mode if mode
684
699
  if _track
@@ -947,8 +962,8 @@ module Playwright
947
962
  @main_frame.locator(result['selector'])
948
963
  end
949
964
 
950
- def snapshot_for_ai(timeout: nil, depth: nil, _track: nil)
951
- aria_snapshot(mode: 'ai', timeout: timeout, depth: depth, _track: _track)
965
+ def snapshot_for_ai(timeout: nil, depth: nil, boxes: nil, _track: nil)
966
+ aria_snapshot(mode: 'ai', timeout: timeout, depth: depth, boxes: boxes, _track: _track)
952
967
  end
953
968
 
954
969
  def _assertions(timeout, is_not, message)
@@ -34,6 +34,10 @@ module Playwright
34
34
  @connection.local_utils.devices
35
35
  end
36
36
 
37
+ def request
38
+ @request ||= APIRequestImpl.new(self)
39
+ end
40
+
37
41
  # used only from Playwright#connect_to_browser_server
38
42
  private def pre_launched_browser
39
43
  unless @initializer['preLaunchedBrowser']
@@ -1,5 +1,10 @@
1
1
  module Playwright
2
2
  define_channel_owner :Tracing do
3
+ private def after_initialize
4
+ @har_recorders = {}
5
+ @har_id = nil
6
+ end
7
+
3
8
  def start(name: nil, title: nil, screenshots: nil, snapshots: nil, sources: nil, live: nil)
4
9
  params = {
5
10
  name: name,
@@ -38,10 +43,7 @@ module Playwright
38
43
  end
39
44
 
40
45
  private def do_stop_chunk(file_path:)
41
- if @is_tracing
42
- @is_tracing = false
43
- @connection.set_in_tracing(false)
44
- end
46
+ reset_stack_counter
45
47
  local_utils = @connection.local_utils
46
48
 
47
49
  unless file_path
@@ -99,6 +101,105 @@ module Playwright
99
101
  )
100
102
  end
101
103
 
104
+ def start_har(path, content: nil, mode: nil, urlFilter: nil, resourcesDir: nil)
105
+ raise 'HAR recording has already been started' if @har_id
106
+ if resourcesDir && path.end_with?('.zip')
107
+ raise 'resourcesDir option is not compatible with a .zip har file'
108
+ end
109
+
110
+ default_content = path.end_with?('.zip') ? 'attach' : 'embed'
111
+ @har_id = record_into_har(path, nil,
112
+ url: urlFilter,
113
+ update_content: content || default_content,
114
+ update_mode: mode || 'full',
115
+ resources_dir: resourcesDir,
116
+ )
117
+ DisposableStub.new { stop_har }
118
+ end
119
+
120
+ def stop_har
121
+ har_id = @har_id
122
+ raise 'HAR recording has not been started' unless har_id
123
+
124
+ @har_id = nil
125
+ export_har(har_id)
126
+ nil
127
+ end
128
+
129
+ private def record_into_har(har, page, url:, update_content:, update_mode:, resources_dir: nil)
130
+ options = {
131
+ content: update_content || 'attach',
132
+ mode: update_mode || 'minimal',
133
+ harPath: har.end_with?('.zip') ? nil : har,
134
+ resourcesDir: resources_dir,
135
+ }.compact
136
+
137
+ if url.is_a?(Regexp)
138
+ regex = ::Playwright::JavaScript::Regex.new(url)
139
+ options[:urlRegexSource] = regex.source
140
+ options[:urlRegexFlags] = regex.flag
141
+ elsif url.is_a?(String)
142
+ options[:urlGlob] = url
143
+ end
144
+
145
+ params = { options: options }
146
+ params[:page] = page.channel if page
147
+
148
+ result = @channel.send_message_to_server_result('harStart', params)
149
+ har_id = result['harId'] || result[:harId]
150
+ @har_recorders[har_id] = { path: har, resources_dir: resources_dir }
151
+ har_id
152
+ end
153
+
154
+ private def export_har(har_id)
155
+ har_params = @har_recorders.delete(har_id)
156
+ return unless har_params
157
+
158
+ path = har_params[:path]
159
+ is_zip = path.end_with?('.zip')
160
+ local_utils = @connection.local_utils
161
+
162
+ if !@connection.remote?
163
+ result = @channel.send_message_to_server_result('harExport', harId: har_id, mode: 'entries')
164
+ return unless is_zip
165
+ raise 'Cannot save zipped HAR because localUtils is unavailable.' unless local_utils
166
+
167
+ local_utils.zip(
168
+ zipFile: path,
169
+ entries: result['entries'],
170
+ mode: 'write',
171
+ includeSources: false,
172
+ )
173
+ return
174
+ end
175
+
176
+ result = @channel.send_message_to_server_result('harExport', harId: har_id, mode: 'archive')
177
+ artifact = ChannelOwners::Artifact.from(result['artifact'])
178
+ if is_zip
179
+ artifact.save_as(path)
180
+ artifact.delete
181
+ return
182
+ end
183
+
184
+ raise 'Uncompressed har is not supported in thin clients' unless local_utils
185
+
186
+ tmp_path = "#{path}.tmp"
187
+ artifact.save_as(tmp_path)
188
+ local_utils.har_unzip(tmp_path, path, resources_dir: har_params[:resources_dir])
189
+ artifact.delete
190
+ end
191
+
192
+ private def export_all_hars
193
+ @har_recorders.keys.each { |har_id| export_har(har_id) }
194
+ end
195
+
196
+ private def reset_stack_counter
197
+ if @is_tracing
198
+ @is_tracing = false
199
+ @connection.set_in_tracing(false)
200
+ end
201
+ end
202
+
102
203
  private def update_traces_dir(traces_dir)
103
204
  @traces_dir = traces_dir
104
205
  end
@@ -74,14 +74,18 @@ module Playwright
74
74
  end
75
75
 
76
76
  def async_send_message_to_server(guid, method, params, metadata: nil)
77
+ if method == '__waitInfo__' && @closed_error
78
+ return Concurrent::Promises.fulfilled_future(nil)
79
+ end
77
80
  return if @closed_error
78
81
 
79
82
  callback = Concurrent::Promises.resolvable_future
83
+ fire_and_forget = method == '__waitInfo__'
80
84
 
81
85
  with_generated_id do |id|
82
86
  # register callback promise object first.
83
87
  # @see https://github.com/YusukeIwaki/puppeteer-ruby/pull/34
84
- @callbacks_mutex.synchronize { @callbacks[id] = callback }
88
+ @callbacks_mutex.synchronize { @callbacks[id] = callback } unless fire_and_forget
85
89
 
86
90
  _metadata = {}
87
91
  frames = []
@@ -112,6 +116,7 @@ module Playwright
112
116
  callback.reject(err)
113
117
  raise unless err.is_a?(Transport::AlreadyDisconnectedError)
114
118
  end
119
+ callback.fulfill(nil) if fire_and_forget
115
120
 
116
121
  if @tracing_count > 0 && !frames.empty? && guid != 'localUtils' && !remote?
117
122
  @local_utils.add_stack_to_tracing_no_reply(id, frames)
@@ -168,6 +173,7 @@ module Playwright
168
173
  if error && !msg['result']
169
174
  parsed_error = ::Playwright::Error.parse(error['error'])
170
175
  parsed_error.log = msg['log']
176
+ parsed_error.details = msg['errorDetails']
171
177
  callback.reject(parsed_error)
172
178
  else
173
179
  result = replace_guids_with_channels(msg['result'])
@@ -213,7 +219,11 @@ module Playwright
213
219
  return
214
220
  end
215
221
 
216
- object.channel.emit(method, replace_guids_with_channels(params))
222
+ if object.is_a?(ChannelOwners::JsonPipe) && method == 'message'
223
+ object.channel.emit(method, params)
224
+ else
225
+ object.channel.emit(method, replace_guids_with_channels(params))
226
+ end
217
227
  end
218
228
 
219
229
  def replace_channels_with_guids(payload)
@@ -23,7 +23,16 @@ module Playwright
23
23
  end
24
24
 
25
25
  def location
26
- @event['location']
26
+ location = @event['location']
27
+ return location unless location
28
+
29
+ {
30
+ 'url' => location['url'],
31
+ 'line' => location['lineNumber'],
32
+ 'column' => location['columnNumber'],
33
+ 'lineNumber' => location['lineNumber'],
34
+ 'columnNumber' => location['columnNumber'],
35
+ }
27
36
  end
28
37
 
29
38
  def timestamp
@@ -0,0 +1,35 @@
1
+ module Playwright
2
+ # ref: https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/credentials.ts
3
+ define_api_implementation :CredentialsImpl do
4
+ # @param browser_context [ChannelOwners::BrowserContext]
5
+ def initialize(browser_context)
6
+ @browser_context = browser_context
7
+ end
8
+
9
+ def install
10
+ @browser_context.channel.send_message_to_server('credentialsInstall')
11
+ nil
12
+ end
13
+
14
+ def create(rpId, id: nil, userHandle: nil, privateKey: nil, publicKey: nil)
15
+ params = {
16
+ rpId: rpId,
17
+ id: id,
18
+ userHandle: userHandle,
19
+ privateKey: privateKey,
20
+ publicKey: publicKey,
21
+ }.compact
22
+ @browser_context.channel.send_message_to_server('credentialsCreate', params)
23
+ end
24
+
25
+ def get(id: nil, rpId: nil)
26
+ params = { id: id, rpId: rpId }.compact
27
+ @browser_context.channel.send_message_to_server('credentialsGet', params)
28
+ end
29
+
30
+ def delete(id)
31
+ @browser_context.channel.send_message_to_server('credentialsDelete', id: id)
32
+ nil
33
+ end
34
+ end
35
+ end
@@ -31,10 +31,13 @@ module Playwright
31
31
  @stack = stack
32
32
  end
33
33
 
34
- attr_reader :name, :message, :stack
34
+ attr_reader :name, :message, :stack, :raw_log
35
+ # Error details for `expect` failures (received value, timedOut, customErrorMessage).
36
+ attr_accessor :details
35
37
 
36
38
  def log=(log)
37
39
  return unless log
40
+ @raw_log = log
38
41
  format_call_log = log.join("\n - ")
39
42
  @message = "#{@message}\nCall log:\n#{format_call_log}\n"
40
43
  end
@@ -60,12 +63,13 @@ module Playwright
60
63
  end
61
64
 
62
65
  class WebError
63
- def initialize(error, page)
66
+ def initialize(error, page, location = nil)
64
67
  @error = error
65
68
  @page = PlaywrightApi.wrap(page)
69
+ @location = location
66
70
  end
67
71
 
68
- attr_reader :error, :page
72
+ attr_reader :error, :page, :location
69
73
  end
70
74
 
71
75
  class AssertionError < StandardError; end
@@ -20,6 +20,7 @@ end
20
20
  },
21
21
 
22
22
  Browser: {
23
+ Context: 'context',
23
24
  Disconnected: 'disconnected'
24
25
  },
25
26
 
@@ -28,7 +29,13 @@ end
28
29
  Close: 'close',
29
30
  Console: 'console',
30
31
  Dialog: 'dialog',
32
+ Download: 'download',
33
+ FrameAttached: 'frameattached',
34
+ FrameDetached: 'framedetached',
35
+ FrameNavigated: 'framenavigated',
31
36
  Page: 'page',
37
+ PageClose: 'pageclose',
38
+ PageLoad: 'pageload',
32
39
  WebError: 'weberror',
33
40
  ServiceWorker: 'serviceworker',
34
41
  Request: 'request',
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Playwright
4
+ class JsonPipeTransport
5
+ def initialize(local_utils, params)
6
+ @local_utils = local_utils
7
+ @params = params
8
+ end
9
+
10
+ def on_message_received(&block)
11
+ @on_message = block
12
+ end
13
+
14
+ def on_driver_closed(&block)
15
+ @on_driver_closed = block
16
+ end
17
+
18
+ def on_driver_crashed(&block)
19
+ @on_driver_crashed = block
20
+ end
21
+
22
+ def send_message(message)
23
+ @pipe.channel.send_message_to_server('send', message: message)
24
+ end
25
+
26
+ def stop
27
+ @pipe&.channel&.send_message_to_server('close')
28
+ rescue TargetClosedError
29
+ nil
30
+ ensure
31
+ @pipe = nil
32
+ end
33
+
34
+ def async_run
35
+ @pipe = @local_utils.connect(@params)
36
+ @pipe.channel.on('message', ->(params) { @on_message&.call(params['message']) })
37
+ @pipe.channel.on('closed', ->(_params) { @on_driver_closed&.call })
38
+ end
39
+ end
40
+ end
@@ -27,7 +27,7 @@ module Playwright
27
27
  result = @locator.expect(expression, expect_options, title)
28
28
 
29
29
  if result["matches"] == @is_not
30
- actual = result["received"]
30
+ actual = received_actual(result)
31
31
 
32
32
  log =
33
33
  if result.key?("log")
@@ -52,19 +52,35 @@ module Playwright
52
52
  if result['errorMessage']
53
53
  error_message = "\n#{result['errorMessage']}"
54
54
  end
55
- out = "#{out_message}\nActual value #{actual}#{error_message} #{log}"
55
+ out = "#{out_message}\nActual value #{actual}#{error_message} #{log}#{aria_snapshot(result)}"
56
56
  raise AssertionError.new(out)
57
57
  else
58
58
  true
59
59
  end
60
60
  end
61
61
 
62
+ private def received_actual(result)
63
+ received = result['received']
64
+ if received.is_a?(Hash)
65
+ received['value']
66
+ else
67
+ received
68
+ end
69
+ end
70
+
71
+ private def aria_snapshot(result)
72
+ aria_snapshot = result.dig('received', 'ariaSnapshot')
73
+ return '' unless aria_snapshot
74
+
75
+ "\nAria snapshot:\n#{aria_snapshot}\n"
76
+ end
77
+
62
78
  private def _not # "not" is reserved in Ruby
63
79
  LocatorAssertionsImpl.new(
64
80
  @locator,
65
81
  @default_expect_timeout,
66
82
  !@is_not,
67
- @message
83
+ @custom_message
68
84
  )
69
85
  end
70
86
 
@@ -292,12 +308,13 @@ module Playwright
292
308
  end
293
309
  _define_negation :to_have_count
294
310
 
295
- def to_have_css(name, value, timeout: nil)
311
+ def to_have_css(name, value, pseudo: nil, timeout: nil)
296
312
  expected_text = to_expected_text_values([value])
297
313
  expect_impl(
298
314
  "to.have.css",
299
315
  {
300
316
  expressionArg: name,
317
+ pseudo: pseudo,
301
318
  expectedText: expected_text,
302
319
  timeout: timeout,
303
320
  },
@@ -426,11 +426,12 @@ module Playwright
426
426
  end
427
427
  end
428
428
 
429
- def aria_snapshot(depth: nil, mode: nil, timeout: nil, _track: nil)
429
+ def aria_snapshot(boxes: nil, depth: nil, mode: nil, timeout: nil, _track: nil)
430
430
  params = {
431
431
  selector: @selector,
432
432
  timeout: _timeout(timeout),
433
433
  }
434
+ params[:boxes] = boxes unless boxes.nil?
434
435
  params[:depth] = depth if depth
435
436
  params[:mode] = mode if mode
436
437
  if _track
@@ -485,6 +486,14 @@ module Playwright
485
486
  @frame.set_input_files(@selector, files, strict: true, noWaitAfter: noWaitAfter, timeout: timeout)
486
487
  end
487
488
 
489
+ def drop(payload, position: nil, timeout: nil)
490
+ @frame.drop(@selector,
491
+ payload,
492
+ strict: true,
493
+ position: position,
494
+ timeout: timeout)
495
+ end
496
+
488
497
  def tap_point(
489
498
  force: nil,
490
499
  modifiers: nil,
@@ -549,8 +558,13 @@ module Playwright
549
558
  @frame.eval_on_selector_all(@selector, "ee => ee.map(e => e.textContent || '')")
550
559
  end
551
560
 
552
- def highlight
553
- @frame.highlight(@selector)
561
+ def highlight(style: nil)
562
+ @frame.highlight(@selector, style: style)
563
+ DisposableStub.new { hide_highlight }
564
+ end
565
+
566
+ def hide_highlight
567
+ @frame.hide_highlight(@selector)
554
568
  end
555
569
 
556
570
  def _assertions(timeout, is_not, message)
@@ -39,7 +39,15 @@ module Playwright
39
39
  end
40
40
 
41
41
  private def get_by_test_id_selector(test_id_attribute_name, test_id)
42
- "internal:testid=[#{test_id_attribute_name}=#{escape_for_attribute_selector_or_regex(test_id, true)}]"
42
+ "internal:testid=[#{encode_test_id_attribute_name(test_id_attribute_name)}=#{escape_for_attribute_selector_or_regex(test_id, true)}]"
43
+ end
44
+
45
+ private def encode_test_id_attribute_name(test_id_attribute_name)
46
+ if test_id_attribute_name.include?(',')
47
+ JSON.generate(test_id_attribute_name)
48
+ else
49
+ test_id_attribute_name
50
+ end
43
51
  end
44
52
 
45
53
  private def get_by_label_selector(text, exact:)
@@ -66,12 +74,14 @@ module Playwright
66
74
  props = []
67
75
 
68
76
  ex = {
77
+ description: -> (value) { ['description', escape_for_attribute_selector_or_regex(value, options[:exact])]},
69
78
  includeHidden: -> (value) { ['include-hidden', value.to_s] },
70
79
  name: -> (value) { ['name', escape_for_attribute_selector_or_regex(value, options[:exact])]},
71
80
  }
72
81
 
73
82
  %i[
74
83
  checked
84
+ description
75
85
  disabled
76
86
  selected
77
87
  expanded