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
@@ -28,7 +28,7 @@ module Playwright
28
28
  result = @frame.expect(nil, expression, expect_options, title)
29
29
 
30
30
  if result["matches"] == @is_not
31
- actual = result["received"]
31
+ actual = received_actual(result)
32
32
 
33
33
  log =
34
34
  if result.key?("log")
@@ -53,19 +53,35 @@ module Playwright
53
53
  if result['errorMessage']
54
54
  error_message = "\n#{result['errorMessage']}"
55
55
  end
56
- out = "#{out_message}\nActual value #{actual}#{error_message} #{log}"
56
+ out = "#{out_message}\nActual value #{actual}#{error_message} #{log}#{aria_snapshot(result)}"
57
57
  raise AssertionError.new(out)
58
58
  else
59
59
  true
60
60
  end
61
61
  end
62
62
 
63
+ private def received_actual(result)
64
+ received = result['received']
65
+ if received.is_a?(Hash)
66
+ received['value']
67
+ else
68
+ received
69
+ end
70
+ end
71
+
72
+ private def aria_snapshot(result)
73
+ aria_snapshot = result.dig('received', 'ariaSnapshot')
74
+ return '' unless aria_snapshot
75
+
76
+ "\nAria snapshot:\n#{aria_snapshot}\n"
77
+ end
78
+
63
79
  private def _not # "not" is reserved in Ruby
64
80
  PageAssertionsImpl.new(
65
81
  @page,
66
82
  @default_expect_timeout,
67
83
  !@is_not,
68
- @message
84
+ @custom_message
69
85
  )
70
86
  end
71
87
 
@@ -150,5 +166,19 @@ module Playwright
150
166
  )
151
167
  end
152
168
  _define_negation :to_have_url
169
+
170
+ def to_match_aria_snapshot(expected, timeout: nil)
171
+ expect_impl(
172
+ 'to.match.aria',
173
+ {
174
+ expectedValue: expected,
175
+ timeout: timeout,
176
+ },
177
+ expected,
178
+ 'Page expected to match Aria snapshot',
179
+ 'Expect "to_match_aria_snapshot"',
180
+ )
181
+ end
182
+ _define_negation :to_match_aria_snapshot
153
183
  end
154
184
  end
@@ -63,11 +63,12 @@ module Playwright
63
63
  @page.send(:channel).send_message_to_server('screencastChapter', params)
64
64
  end
65
65
 
66
- def show_actions(duration: nil, fontSize: nil, position: nil)
66
+ def show_actions(duration: nil, fontSize: nil, position: nil, cursor: nil)
67
67
  params = {}
68
68
  params[:duration] = duration if duration
69
69
  params[:fontSize] = fontSize if fontSize
70
70
  params[:position] = position if position
71
+ params[:cursor] = cursor if cursor
71
72
  @page.send(:channel).send_message_to_server('screencastShowActions', params)
72
73
  DisposableStub.new { hide_actions }
73
74
  end
@@ -85,7 +86,12 @@ module Playwright
85
86
  end
86
87
 
87
88
  private def handle_screencast_frame(event)
88
- @on_frame&.call(Base64.strict_decode64(event['data']))
89
+ @on_frame&.call({
90
+ data: Base64.strict_decode64(event['data']),
91
+ timestamp: event['timestamp'],
92
+ viewportWidth: event['viewportWidth'],
93
+ viewportHeight: event['viewportHeight'],
94
+ })
89
95
  end
90
96
  end
91
97
  end
@@ -5,6 +5,7 @@ module Playwright
5
5
  def initialize(url, base_url:)
6
6
  @url = url
7
7
  @base_url = base_url
8
+ validate_glob_pattern if @url.is_a?(String)
8
9
  end
9
10
 
10
11
  def as_pattern
@@ -37,5 +38,39 @@ module Playwright
37
38
  @url
38
39
  end
39
40
  end
41
+
42
+ private def validate_glob_pattern
43
+ in_group = false
44
+ escaped = false
45
+
46
+ @url.each_char do |char|
47
+ if escaped
48
+ escaped = false
49
+ next
50
+ end
51
+
52
+ if char == '\\'
53
+ escaped = true
54
+ next
55
+ end
56
+
57
+ case char
58
+ when '{'
59
+ if in_group
60
+ raise ArgumentError.new("Invalid glob pattern #{@url.inspect}: nested '{' is not supported")
61
+ end
62
+ in_group = true
63
+ when '}'
64
+ unless in_group
65
+ raise ArgumentError.new("Invalid glob pattern #{@url.inspect}: unmatched '}'")
66
+ end
67
+ in_group = false
68
+ end
69
+ end
70
+
71
+ if in_group
72
+ raise ArgumentError.new("Invalid glob pattern #{@url.inspect}: unmatched '{'")
73
+ end
74
+ end
40
75
  end
41
76
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Playwright
4
- VERSION = '1.59.1'
5
- COMPATIBLE_PLAYWRIGHT_VERSION = '1.59.0'
4
+ VERSION = '1.61.0'
5
+ COMPATIBLE_PLAYWRIGHT_VERSION = '1.61.1'
6
6
  end
@@ -12,33 +12,14 @@ module Playwright
12
12
  @registered_listeners = Set.new
13
13
  @listeners_mutex = Mutex.new
14
14
  @logs = []
15
- wait_for_event_info_before
15
+ send_wait_info(waitId: @wait_id, phase: 'before', event: @event)
16
16
  end
17
17
 
18
- private def wait_for_event_info_before
19
- @channel.async_send_message_to_server(
20
- "waitForEventInfo",
21
- {
22
- "info": {
23
- "waitId": @wait_id,
24
- "phase": "before",
25
- "event": @event,
26
- }
27
- },
28
- )
29
- end
30
-
31
- private def wait_for_event_info_after(error: nil)
32
- @channel.async_send_message_to_server(
33
- "waitForEventInfo",
34
- {
35
- "info": {
36
- "waitId": @wait_id,
37
- "phase": "after",
38
- "error": error,
39
- }.compact,
40
- },
41
- )
18
+ private def send_wait_info(params)
19
+ @channel.async_send_message_to_server('__waitInfo__', params.compact)
20
+ rescue
21
+ # Fire-and-forget. The server intentionally does not reply and this must
22
+ # not affect the API call that is being waited for.
42
23
  end
43
24
 
44
25
  def reject_on_event(emitter, event, error_or_proc, predicate: nil)
@@ -89,7 +70,7 @@ module Playwright
89
70
  cleanup
90
71
  return if @result.resolved?
91
72
  @result.fulfill(result)
92
- wait_for_event_info_after
73
+ send_wait_info(waitId: @wait_id, phase: 'after')
93
74
  end
94
75
 
95
76
  private def reject(error)
@@ -98,7 +79,7 @@ module Playwright
98
79
  klass = error.is_a?(TimeoutError) ? TimeoutError : Error
99
80
  ex = klass.new(message: "#{error.message}#{format_log_recording(@logs)}")
100
81
  @result.reject(ex)
101
- wait_for_event_info_after(error: ex)
82
+ send_wait_info(waitId: @wait_id, phase: 'after', error: ex.message)
102
83
  end
103
84
 
104
85
  # @param [Playwright::EventEmitter]
@@ -135,20 +116,7 @@ module Playwright
135
116
 
136
117
  def log(message)
137
118
  @logs << message
138
- begin
139
- @channel.async_send_message_to_server(
140
- "waitForEventInfo",
141
- {
142
- "info": {
143
- "waitId": @wait_id,
144
- "phase": "log",
145
- "message": message,
146
- },
147
- },
148
- )
149
- rescue => err
150
- # ignore
151
- end
119
+ send_wait_info(waitId: @wait_id, phase: 'log', message: message)
152
120
  end
153
121
 
154
122
  # @param logs [Array<String>]
@@ -0,0 +1,34 @@
1
+ module Playwright
2
+ # ref: https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/webStorage.ts
3
+ define_api_implementation :WebStorageImpl do
4
+ # @param page [ChannelOwners::Page]
5
+ # @param kind [String] 'local' or 'session'
6
+ def initialize(page, kind)
7
+ @page = page
8
+ @kind = kind
9
+ end
10
+
11
+ def items
12
+ @page.channel.send_message_to_server('webStorageItems', kind: @kind)
13
+ end
14
+
15
+ def get_item(name)
16
+ @page.channel.send_message_to_server('webStorageGetItem', kind: @kind, name: name)
17
+ end
18
+
19
+ def set_item(name, value)
20
+ @page.channel.send_message_to_server('webStorageSetItem', kind: @kind, name: name, value: value)
21
+ nil
22
+ end
23
+
24
+ def remove_item(name)
25
+ @page.channel.send_message_to_server('webStorageRemoveItem', kind: @kind, name: name)
26
+ nil
27
+ end
28
+
29
+ def clear
30
+ @page.channel.send_message_to_server('webStorageClear', kind: @kind)
31
+ nil
32
+ end
33
+ end
34
+ end
data/lib/playwright.rb CHANGED
@@ -27,6 +27,7 @@ require 'playwright/route_handler'
27
27
  require 'playwright/select_option_values'
28
28
  require 'playwright/timeout_settings'
29
29
  require 'playwright/transport'
30
+ require 'playwright/json_pipe_transport'
30
31
  require 'playwright/url_matcher'
31
32
  require 'playwright/version'
32
33
  require 'playwright/screencast'
@@ -20,7 +20,7 @@ module Playwright
20
20
  storageState: nil,
21
21
  timeout: nil,
22
22
  userAgent: nil)
23
- raise NotImplementedError.new('new_context is not implemented yet.')
23
+ wrap_impl(@impl.new_context(baseURL: unwrap_impl(baseURL), clientCertificates: unwrap_impl(clientCertificates), extraHTTPHeaders: unwrap_impl(extraHTTPHeaders), failOnStatusCode: unwrap_impl(failOnStatusCode), httpCredentials: unwrap_impl(httpCredentials), ignoreHTTPSErrors: unwrap_impl(ignoreHTTPSErrors), maxRedirects: unwrap_impl(maxRedirects), proxy: unwrap_impl(proxy), storageState: unwrap_impl(storageState), timeout: unwrap_impl(timeout), userAgent: unwrap_impl(userAgent)))
24
24
  end
25
25
  end
26
26
  end
@@ -3,21 +3,21 @@ module Playwright
3
3
  # This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
4
4
  # environment or the service to your e2e test.
5
5
  #
6
- # Each Playwright browser context has associated with it `APIRequestContext` instance which shares cookie storage with
7
- # the browser context and can be accessed via [`property: BrowserContext.request`] or [`property: Page.request`].
8
- # It is also possible to create a new APIRequestContext instance manually by calling [`method: APIRequest.newContext`].
6
+ # Each Playwright browser context has an associated `APIRequestContext`, accessible via
7
+ # [`property: BrowserContext.request`] or [`property: Page.request`] (these return the
8
+ #
9
+ # **same instance** — `page.request` is a shortcut for `page.context().request`).
10
+ # You can also create a standalone, isolated instance with [`method: APIRequest.newContext`].
9
11
  #
10
12
  # **Cookie management**
11
13
  #
12
- # `APIRequestContext` returned by [`property: BrowserContext.request`] and [`property: Page.request`] shares cookie
13
- # storage with the corresponding `BrowserContext`. Each API request will have `Cookie` header populated with the
14
- # values from the browser context. If the API response contains `Set-Cookie` header it will automatically update
15
- # `BrowserContext` cookies and requests made from the page will pick them up. This means that if you log in using
16
- # this API, your e2e test will be logged in and vice versa.
14
+ # The `APIRequestContext` returned by [`property: BrowserContext.request`] and
15
+ #
16
+ # [`property: Page.request`] uses the same cookie jar as its `BrowserContext`:
17
17
  #
18
- # If you want API requests to not interfere with the browser cookies you should create a new `APIRequestContext` by
19
- # calling [`method: APIRequest.newContext`]. Such `APIRequestContext` object will have its own isolated cookie
20
- # storage.
18
+ # If you want API requests that do **not** share cookies with the browser, create an
19
+ # isolated context via [`method: APIRequest.newContext`]. Such `APIRequestContext`
20
+ # object will have its own isolated cookie storage.
21
21
  #
22
22
  # ```python sync
23
23
  # import os
@@ -67,6 +67,10 @@ module Playwright
67
67
  # ```
68
68
  class APIRequestContext < PlaywrightApi
69
69
 
70
+ def tracing # property
71
+ wrap_impl(@impl.tracing)
72
+ end
73
+
70
74
  #
71
75
  # Sends HTTP(S) [DELETE](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE) request and returns its response.
72
76
  # The method will populate request cookies from the context and update
@@ -286,12 +290,6 @@ module Playwright
286
290
  raise NotImplementedError.new('storage_state is not implemented yet.')
287
291
  end
288
292
 
289
- # -- inherited from EventEmitter --
290
- # @nodoc
291
- def on(event, callback)
292
- event_emitter_proxy.on(event, callback)
293
- end
294
-
295
293
  # -- inherited from EventEmitter --
296
294
  # @nodoc
297
295
  def off(event, callback)
@@ -304,6 +302,12 @@ module Playwright
304
302
  event_emitter_proxy.once(event, callback)
305
303
  end
306
304
 
305
+ # -- inherited from EventEmitter --
306
+ # @nodoc
307
+ def on(event, callback)
308
+ event_emitter_proxy.on(event, callback)
309
+ end
310
+
307
311
  private def event_emitter_proxy
308
312
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
309
313
  end
@@ -55,6 +55,18 @@ module Playwright
55
55
  wrap_impl(@impl.ok)
56
56
  end
57
57
 
58
+ #
59
+ # Returns SSL and other security information. Resolves to `null` for non-HTTPS responses. For redirected requests, returns the information for the last request in the redirect chain.
60
+ def security_details
61
+ wrap_impl(@impl.security_details)
62
+ end
63
+
64
+ #
65
+ # Returns the IP address and port of the server. Resolves to `null` if the server address is not available. For redirected requests, returns the information for the last request in the redirect chain.
66
+ def server_addr
67
+ wrap_impl(@impl.server_addr)
68
+ end
69
+
58
70
  #
59
71
  # Contains the status code of the response (e.g., 200 for a success).
60
72
  def status
@@ -175,7 +175,7 @@ module Playwright
175
175
  #
176
176
  # Binds the browser to a named pipe or web socket, making it available for other clients to connect to.
177
177
  def bind(title, host: nil, port: nil, workspaceDir: nil)
178
- raise NotImplementedError.new('bind is not implemented yet.')
178
+ wrap_impl(@impl.bind(unwrap_impl(title), host: unwrap_impl(host), port: unwrap_impl(port), workspaceDir: unwrap_impl(workspaceDir)))
179
179
  end
180
180
 
181
181
  #
@@ -206,7 +206,7 @@ module Playwright
206
206
  #
207
207
  # Unbinds the browser server previously bound with [`method: Browser.bind`].
208
208
  def unbind
209
- raise NotImplementedError.new('unbind is not implemented yet.')
209
+ wrap_impl(@impl.unbind)
210
210
  end
211
211
 
212
212
  #
@@ -215,12 +215,6 @@ module Playwright
215
215
  wrap_impl(@impl.version)
216
216
  end
217
217
 
218
- # -- inherited from EventEmitter --
219
- # @nodoc
220
- def on(event, callback)
221
- event_emitter_proxy.on(event, callback)
222
- end
223
-
224
218
  # -- inherited from EventEmitter --
225
219
  # @nodoc
226
220
  def off(event, callback)
@@ -233,6 +227,12 @@ module Playwright
233
227
  event_emitter_proxy.once(event, callback)
234
228
  end
235
229
 
230
+ # -- inherited from EventEmitter --
231
+ # @nodoc
232
+ def on(event, callback)
233
+ event_emitter_proxy.on(event, callback)
234
+ end
235
+
236
236
  private def event_emitter_proxy
237
237
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
238
238
  end
@@ -25,6 +25,13 @@ module Playwright
25
25
  wrap_impl(@impl.clock)
26
26
  end
27
27
 
28
+ #
29
+ # Virtual WebAuthn authenticator for this context. Lets tests seed credentials and intercept
30
+ # `navigator.credentials.create()` / `navigator.credentials.get()` ceremonies.
31
+ def credentials # property
32
+ wrap_impl(@impl.credentials)
33
+ end
34
+
28
35
  #
29
36
  # Debugger allows to pause and resume the execution.
30
37
  def debugger # property
@@ -174,8 +181,8 @@ module Playwright
174
181
  # with sync_playwright() as playwright:
175
182
  # run(playwright)
176
183
  # ```
177
- def expose_binding(name, callback, handle: nil)
178
- wrap_impl(@impl.expose_binding(unwrap_impl(name), unwrap_impl(callback), handle: unwrap_impl(handle)))
184
+ def expose_binding(name, callback)
185
+ wrap_impl(@impl.expose_binding(unwrap_impl(name), unwrap_impl(callback)))
179
186
  end
180
187
 
181
188
  #
@@ -489,6 +496,11 @@ module Playwright
489
496
  raise NotImplementedError.new('wait_for_event is not implemented yet.')
490
497
  end
491
498
 
499
+ # @nodoc
500
+ def browser=(req)
501
+ wrap_impl(@impl.browser=(unwrap_impl(req)))
502
+ end
503
+
492
504
  # @nodoc
493
505
  def enable_debug_console!
494
506
  wrap_impl(@impl.enable_debug_console!)
@@ -509,17 +521,6 @@ module Playwright
509
521
  wrap_impl(@impl.options=(unwrap_impl(req)))
510
522
  end
511
523
 
512
- # @nodoc
513
- def browser=(req)
514
- wrap_impl(@impl.browser=(unwrap_impl(req)))
515
- end
516
-
517
- # -- inherited from EventEmitter --
518
- # @nodoc
519
- def on(event, callback)
520
- event_emitter_proxy.on(event, callback)
521
- end
522
-
523
524
  # -- inherited from EventEmitter --
524
525
  # @nodoc
525
526
  def off(event, callback)
@@ -532,6 +533,12 @@ module Playwright
532
533
  event_emitter_proxy.once(event, callback)
533
534
  end
534
535
 
536
+ # -- inherited from EventEmitter --
537
+ # @nodoc
538
+ def on(event, callback)
539
+ event_emitter_proxy.on(event, callback)
540
+ end
541
+
535
542
  private def event_emitter_proxy
536
543
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
537
544
  end
@@ -28,8 +28,9 @@ module Playwright
28
28
  exposeNetwork: nil,
29
29
  headers: nil,
30
30
  slowMo: nil,
31
- timeout: nil)
32
- raise NotImplementedError.new('connect is not implemented yet.')
31
+ timeout: nil,
32
+ &block)
33
+ wrap_impl(@impl.connect(unwrap_impl(endpoint), exposeNetwork: unwrap_impl(exposeNetwork), headers: unwrap_impl(headers), slowMo: unwrap_impl(slowMo), timeout: unwrap_impl(timeout), &wrap_block_call(block)))
33
34
  end
34
35
 
35
36
  #
@@ -50,12 +51,14 @@ module Playwright
50
51
  # ```
51
52
  def connect_over_cdp(
52
53
  endpointURL,
54
+ artifactsDir: nil,
53
55
  headers: nil,
54
56
  isLocal: nil,
57
+ noDefaults: nil,
55
58
  slowMo: nil,
56
59
  timeout: nil,
57
60
  &block)
58
- wrap_impl(@impl.connect_over_cdp(unwrap_impl(endpointURL), headers: unwrap_impl(headers), isLocal: unwrap_impl(isLocal), slowMo: unwrap_impl(slowMo), timeout: unwrap_impl(timeout), &wrap_block_call(block)))
61
+ wrap_impl(@impl.connect_over_cdp(unwrap_impl(endpointURL), artifactsDir: unwrap_impl(artifactsDir), headers: unwrap_impl(headers), isLocal: unwrap_impl(isLocal), noDefaults: unwrap_impl(noDefaults), slowMo: unwrap_impl(slowMo), timeout: unwrap_impl(timeout), &wrap_block_call(block)))
59
62
  end
60
63
 
61
64
  #
@@ -181,12 +184,6 @@ module Playwright
181
184
  wrap_impl(@impl.name)
182
185
  end
183
186
 
184
- # -- inherited from EventEmitter --
185
- # @nodoc
186
- def on(event, callback)
187
- event_emitter_proxy.on(event, callback)
188
- end
189
-
190
187
  # -- inherited from EventEmitter --
191
188
  # @nodoc
192
189
  def off(event, callback)
@@ -199,6 +196,12 @@ module Playwright
199
196
  event_emitter_proxy.once(event, callback)
200
197
  end
201
198
 
199
+ # -- inherited from EventEmitter --
200
+ # @nodoc
201
+ def on(event, callback)
202
+ event_emitter_proxy.on(event, callback)
203
+ end
204
+
202
205
  private def event_emitter_proxy
203
206
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
204
207
  end
@@ -31,12 +31,6 @@ module Playwright
31
31
  wrap_impl(@impl.send_message(unwrap_impl(method), params: unwrap_impl(params)))
32
32
  end
33
33
 
34
- # -- inherited from EventEmitter --
35
- # @nodoc
36
- def on(event, callback)
37
- event_emitter_proxy.on(event, callback)
38
- end
39
-
40
34
  # -- inherited from EventEmitter --
41
35
  # @nodoc
42
36
  def off(event, callback)
@@ -49,6 +43,12 @@ module Playwright
49
43
  event_emitter_proxy.once(event, callback)
50
44
  end
51
45
 
46
+ # -- inherited from EventEmitter --
47
+ # @nodoc
48
+ def on(event, callback)
49
+ event_emitter_proxy.on(event, callback)
50
+ end
51
+
52
52
  private def event_emitter_proxy
53
53
  @event_emitter_proxy ||= EventEmitterProxy.new(self, @impl)
54
54
  end
@@ -0,0 +1,120 @@
1
+ module Playwright
2
+ #
3
+ # `Credentials` is a virtual WebAuthn authenticator scoped to a `BrowserContext`. It lets tests
4
+ # register passkeys and answer `navigator.credentials.create()` / `navigator.credentials.get()`
5
+ # ceremonies in the page, without a real authenticator or hardware security key.
6
+ #
7
+ # There are two common ways to use it:
8
+ #
9
+ # **Usage: seed a known credential**
10
+ #
11
+ # ```python sync
12
+ # context = browser.new_context()
13
+ #
14
+ # # A passkey your backend already provisioned for a test user.
15
+ # context.credentials.create(
16
+ # "example.com",
17
+ # id=known_credential_id, # base64url
18
+ # user_handle=known_user_handle, # base64url
19
+ # private_key=known_private_key, # base64url PKCS#8 (DER)
20
+ # public_key=known_public_key, # base64url SPKI (DER)
21
+ # )
22
+ # context.credentials.install()
23
+ #
24
+ # page = context.new_page()
25
+ # page.goto("https://example.com/login")
26
+ # # The page's navigator.credentials.get() is answered with the seeded passkey.
27
+ # ```
28
+ #
29
+ # **Usage: capture a passkey, then reuse it**
30
+ #
31
+ # ```python sync
32
+ # # setup test: let the app register a passkey, then save it.
33
+ # context = browser.new_context()
34
+ # context.credentials.install()
35
+ #
36
+ # page = context.new_page()
37
+ # page.goto("https://example.com/register")
38
+ # page.get_by_role("button", name="Create a passkey").click()
39
+ #
40
+ # # Read back the passkey the page registered — it includes the private key.
41
+ # [credential] = context.credentials.get(rp_id="example.com")
42
+ # with open("playwright/.auth/passkey.json", "w") as f:
43
+ # json.dump(credential, f)
44
+ # ```
45
+ #
46
+ # ```python sync
47
+ # # later test: seed the captured passkey so the app starts already enrolled.
48
+ # with open("playwright/.auth/passkey.json") as f:
49
+ # credential = json.load(f)
50
+ # context = browser.new_context()
51
+ # context.credentials.create(
52
+ # credential["rpId"],
53
+ # id=credential["id"],
54
+ # user_handle=credential["userHandle"],
55
+ # private_key=credential["privateKey"],
56
+ # public_key=credential["publicKey"],
57
+ # )
58
+ # context.credentials.install()
59
+ #
60
+ # page = context.new_page()
61
+ # page.goto("https://example.com/login")
62
+ # # navigator.credentials.get() resolves the captured passkey — already signed in.
63
+ # ```
64
+ #
65
+ # **Defaults**
66
+ class Credentials < PlaywrightApi
67
+
68
+ #
69
+ # Installs the virtual WebAuthn authenticator into the context, overriding
70
+ # `navigator.credentials.create()` and `navigator.credentials.get()` in all current
71
+ # and future pages. Call this before the page first touches `navigator.credentials`.
72
+ #
73
+ # Required: until [`method: Credentials.install`] is called, no interception is in place and the page sees
74
+ # the platform's native (or absent) WebAuthn behaviour. Seeding credentials with
75
+ # [`method: Credentials.create`] without installing populates the authenticator, but the
76
+ # page will never see those credentials.
77
+ def install
78
+ wrap_impl(@impl.install)
79
+ end
80
+
81
+ #
82
+ # Seeds a virtual WebAuthn credential and returns it.
83
+ #
84
+ # With only `rpId`, generates a fresh **ECDSA P-256** keypair, credential id and user handle. The
85
+ # seeded credential is discoverable (resident), so the page can resolve it from both
86
+ # username-then-passkey and usernameless passkey flows. The returned object carries the private and public keys, so it can be persisted to disk and re-seeded in a later test.
87
+ #
88
+ # To **import a known credential**, supply all four of `id`, `userHandle`, `privateKey` and
89
+ # `publicKey` together.
90
+ #
91
+ # Call [`method: Credentials.install`] before navigating to a page that uses WebAuthn.
92
+ def create(
93
+ rpId,
94
+ id: nil,
95
+ privateKey: nil,
96
+ publicKey: nil,
97
+ userHandle: nil)
98
+ wrap_impl(@impl.create(unwrap_impl(rpId), id: unwrap_impl(id), privateKey: unwrap_impl(privateKey), publicKey: unwrap_impl(publicKey), userHandle: unwrap_impl(userHandle)))
99
+ end
100
+
101
+ #
102
+ # Removes a credential from the authenticator by its id. Works for any credential currently held —
103
+ # both those seeded with [`method: Credentials.create`] and those the page registered itself by
104
+ # calling `navigator.credentials.create()`.
105
+ def delete(id)
106
+ wrap_impl(@impl.delete(unwrap_impl(id)))
107
+ end
108
+
109
+ #
110
+ # Returns every credential currently held by the authenticator, optionally filtered by `rpId` or
111
+ # `id`. This includes both credentials seeded with [`method: Credentials.create`] and credentials
112
+ # the page registered itself by calling `navigator.credentials.create()`.
113
+ #
114
+ # Each returned credential includes its private and public keys, so a passkey the app just
115
+ # registered can be saved and re-seeded into a later test with [`method: Credentials.create`] — see the second example in the class overview.
116
+ def get(id: nil, rpId: nil)
117
+ wrap_impl(@impl.get(id: unwrap_impl(id), rpId: unwrap_impl(rpId)))
118
+ end
119
+ end
120
+ end