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.
- checksums.yaml +4 -4
- data/documentation/docs/api/api_request.md +25 -1
- data/documentation/docs/api/api_request_context.md +13 -11
- data/documentation/docs/api/api_response.md +18 -0
- data/documentation/docs/api/browser.md +18 -0
- data/documentation/docs/api/browser_context.md +7 -1
- data/documentation/docs/api/browser_type.md +19 -0
- data/documentation/docs/api/credentials.md +132 -0
- data/documentation/docs/api/frame.md +5 -4
- data/documentation/docs/api/frame_locator.md +1 -0
- data/documentation/docs/api/locator.md +37 -2
- data/documentation/docs/api/locator_assertions.md +1 -1
- data/documentation/docs/api/page.md +24 -4
- data/documentation/docs/api/page_assertions.md +28 -0
- data/documentation/docs/api/playwright.md +5 -0
- data/documentation/docs/api/touchscreen.md +1 -1
- data/documentation/docs/api/tracing.md +29 -0
- data/documentation/docs/api/web_storage.md +65 -0
- data/documentation/docs/include/api_coverage.md +30 -56
- data/lib/playwright/api_request_impl.rb +70 -0
- data/lib/playwright/api_response_impl.rb +8 -0
- data/lib/playwright/channel_owners/api_request_context.rb +5 -1
- data/lib/playwright/channel_owners/binding_call.rb +3 -9
- data/lib/playwright/channel_owners/browser.rb +17 -0
- data/lib/playwright/channel_owners/browser_context.rb +12 -55
- data/lib/playwright/channel_owners/browser_type.rb +35 -1
- data/lib/playwright/channel_owners/frame.rb +118 -13
- data/lib/playwright/channel_owners/json_pipe.rb +4 -0
- data/lib/playwright/channel_owners/local_utils.rb +11 -2
- data/lib/playwright/channel_owners/page.rb +28 -13
- data/lib/playwright/channel_owners/playwright.rb +4 -0
- data/lib/playwright/channel_owners/tracing.rb +105 -4
- data/lib/playwright/connection.rb +12 -2
- data/lib/playwright/console_message_impl.rb +10 -1
- data/lib/playwright/credentials_impl.rb +35 -0
- data/lib/playwright/errors.rb +7 -3
- data/lib/playwright/events.rb +7 -0
- data/lib/playwright/json_pipe_transport.rb +40 -0
- data/lib/playwright/locator_assertions_impl.rb +21 -4
- data/lib/playwright/locator_impl.rb +17 -3
- data/lib/playwright/locator_utils.rb +11 -1
- data/lib/playwright/page_assertions_impl.rb +33 -3
- data/lib/playwright/screencast.rb +8 -2
- data/lib/playwright/url_matcher.rb +35 -0
- data/lib/playwright/version.rb +2 -2
- data/lib/playwright/waiter.rb +9 -41
- data/lib/playwright/web_storage_impl.rb +34 -0
- data/lib/playwright.rb +1 -0
- data/lib/playwright_api/api_request.rb +1 -1
- data/lib/playwright_api/api_request_context.rb +21 -17
- data/lib/playwright_api/api_response.rb +12 -0
- data/lib/playwright_api/browser.rb +8 -8
- data/lib/playwright_api/browser_context.rb +20 -13
- data/lib/playwright_api/browser_type.rb +12 -9
- data/lib/playwright_api/cdp_session.rb +6 -6
- data/lib/playwright_api/credentials.rb +120 -0
- data/lib/playwright_api/dialog.rb +6 -6
- data/lib/playwright_api/element_handle.rb +6 -6
- data/lib/playwright_api/frame.rb +25 -14
- data/lib/playwright_api/frame_locator.rb +2 -1
- data/lib/playwright_api/js_handle.rb +6 -6
- data/lib/playwright_api/locator.rb +39 -10
- data/lib/playwright_api/locator_assertions.rb +2 -2
- data/lib/playwright_api/page.rb +44 -25
- data/lib/playwright_api/page_assertions.rb +22 -0
- data/lib/playwright_api/playwright.rb +7 -7
- data/lib/playwright_api/request.rb +8 -8
- data/lib/playwright_api/response.rb +6 -6
- data/lib/playwright_api/route.rb +6 -6
- data/lib/playwright_api/touchscreen.rb +1 -1
- data/lib/playwright_api/tracing.rb +27 -4
- data/lib/playwright_api/web_socket.rb +6 -6
- data/lib/playwright_api/web_storage.rb +48 -0
- data/lib/playwright_api/worker.rb +8 -8
- data/sig/playwright.rbs +51 -39
- metadata +11 -12
- data/documentation/docs/api/experimental/android.md +0 -42
- data/documentation/docs/api/experimental/android_device.md +0 -109
- data/documentation/docs/api/experimental/android_input.md +0 -43
- data/documentation/docs/api/experimental/android_socket.md +0 -7
- data/documentation/docs/api/experimental/android_web_view.md +0 -7
- data/lib/playwright_api/android.rb +0 -68
- data/lib/playwright_api/android_device.rb +0 -229
- data/lib/playwright_api/android_input.rb +0 -34
- data/lib/playwright_api/android_socket.rb +0 -18
- 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
|
|
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
|
-
@
|
|
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(
|
|
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
|
data/lib/playwright/version.rb
CHANGED
data/lib/playwright/waiter.rb
CHANGED
|
@@ -12,33 +12,14 @@ module Playwright
|
|
|
12
12
|
@registered_listeners = Set.new
|
|
13
13
|
@listeners_mutex = Mutex.new
|
|
14
14
|
@logs = []
|
|
15
|
-
|
|
15
|
+
send_wait_info(waitId: @wait_id, phase: 'before', event: @event)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
private def
|
|
19
|
-
@channel.async_send_message_to_server(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
|
13
|
-
#
|
|
14
|
-
#
|
|
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
|
|
19
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
178
|
-
wrap_impl(@impl.expose_binding(unwrap_impl(name), unwrap_impl(callback)
|
|
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
|
-
|
|
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
|