@rstest/browser 0.8.4 → 0.9.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.
- package/LICENSE-APACHE-2.0 +202 -0
- package/NOTICE +11 -0
- package/dist/361.js +8 -0
- package/dist/augmentExpect.d.ts +73 -0
- package/dist/browser-container/container-static/css/index.5c72297783.css +1 -0
- package/dist/browser-container/container-static/js/{392.28f9a733.js → 101.36a8ccdf84.js} +4068 -3904
- package/dist/browser-container/container-static/js/101.36a8ccdf84.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.129eaf9c.js → index.0687a8142a.js} +742 -692
- package/dist/browser-container/container-static/js/{lib-react.97ee79b0.js → lib-react.dcf2a5e57a.js} +10 -10
- package/dist/browser-container/container-static/js/lib-react.dcf2a5e57a.js.LICENSE.txt +1 -0
- package/dist/browser-container/index.html +1 -1
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +583 -0
- package/dist/browserRpcRegistry.d.ts +18 -0
- package/dist/client/api.d.ts +3 -0
- package/dist/client/browserRpc.d.ts +2 -0
- package/dist/client/dispatchTransport.d.ts +11 -0
- package/dist/client/entry.d.ts +1 -5
- package/dist/client/locator.d.ts +125 -0
- package/dist/client/snapshot.d.ts +0 -6
- package/dist/concurrency.d.ts +12 -0
- package/dist/dispatchCapabilities.d.ts +34 -0
- package/dist/dispatchRouter.d.ts +20 -0
- package/dist/headlessLatestRerunScheduler.d.ts +19 -0
- package/dist/headlessTransport.d.ts +12 -0
- package/dist/index.js +1608 -296
- package/dist/protocol.d.ts +44 -33
- package/dist/providers/index.d.ts +79 -0
- package/dist/providers/playwright/compileLocator.d.ts +3 -0
- package/dist/providers/playwright/dispatchBrowserRpc.d.ts +13 -0
- package/dist/providers/playwright/expectUtils.d.ts +24 -0
- package/dist/providers/playwright/implementation.d.ts +2 -0
- package/dist/providers/playwright/index.d.ts +1 -0
- package/dist/providers/playwright/runtime.d.ts +5 -0
- package/dist/providers/playwright/textMatcher.d.ts +8 -0
- package/dist/rpcProtocol.d.ts +145 -0
- package/dist/runSession.d.ts +33 -0
- package/dist/sessionRegistry.d.ts +34 -0
- package/dist/sourceMap/sourceMapLoader.d.ts +14 -0
- package/dist/watchRerunPlanner.d.ts +21 -0
- package/package.json +16 -11
- package/src/AGENTS.md +128 -0
- package/src/augmentExpect.ts +62 -0
- package/src/browser.ts +3 -0
- package/src/browserRpcRegistry.ts +57 -0
- package/src/client/AGENTS.md +82 -0
- package/src/client/api.ts +213 -0
- package/src/client/browserRpc.ts +86 -0
- package/src/client/dispatchTransport.ts +178 -0
- package/src/client/entry.ts +109 -39
- package/src/client/locator.ts +452 -0
- package/src/client/snapshot.ts +32 -97
- package/src/client/sourceMapSupport.ts +26 -37
- package/src/concurrency.ts +62 -0
- package/src/dispatchCapabilities.ts +162 -0
- package/src/dispatchRouter.ts +82 -0
- package/src/env.d.ts +8 -1
- package/src/headlessLatestRerunScheduler.ts +76 -0
- package/src/headlessTransport.ts +28 -0
- package/src/hostController.ts +1292 -367
- package/src/protocol.ts +66 -31
- package/src/providers/index.ts +103 -0
- package/src/providers/playwright/compileLocator.ts +130 -0
- package/src/providers/playwright/dispatchBrowserRpc.ts +372 -0
- package/src/providers/playwright/expectUtils.ts +57 -0
- package/src/providers/playwright/implementation.ts +33 -0
- package/src/providers/playwright/index.ts +1 -0
- package/src/providers/playwright/runtime.ts +32 -0
- package/src/providers/playwright/textMatcher.ts +10 -0
- package/src/rpcProtocol.ts +220 -0
- package/src/runSession.ts +110 -0
- package/src/sessionRegistry.ts +89 -0
- package/src/sourceMap/sourceMapLoader.ts +96 -0
- package/src/watchRerunPlanner.ts +77 -0
- package/dist/browser-container/container-static/css/index.5a71c757.css +0 -1
- package/dist/browser-container/container-static/js/392.28f9a733.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/lib-react.97ee79b0.js.LICENSE.txt +0 -1
- package/dist/browser-container/container-static/js/scheduler.6976de44.js +0 -411
- package/dist/browser-container/scheduler.html +0 -19
package/dist/browser-container/container-static/js/{lib-react.97ee79b0.js → lib-react.dcf2a5e57a.js}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
/*! For license information please see lib-react.
|
|
2
|
-
/*!
|
|
3
|
-
(self.
|
|
1
|
+
/*! For license information please see lib-react.dcf2a5e57a.js.LICENSE.txt */
|
|
2
|
+
/*! LICENSE: lib-react.dcf2a5e57a.js.LICENSE.txt */ "use strict";
|
|
3
|
+
(self.rspackChunk_rstest_browser_ui = self.rspackChunk_rstest_browser_ui || []).push([
|
|
4
4
|
[
|
|
5
5
|
"783"
|
|
6
6
|
],
|
|
7
7
|
{
|
|
8
8
|
7816 (e, t, n) {
|
|
9
|
-
var r, l = n(1099), a = n(162), o = n(
|
|
9
|
+
var r, l = n(1099), a = n(162), o = n(3085);
|
|
10
10
|
function i(e) {
|
|
11
11
|
var t = "https://react.dev/errors/" + e;
|
|
12
12
|
if (1 < arguments.length) {
|
|
@@ -1865,19 +1865,19 @@
|
|
|
1865
1865
|
}
|
|
1866
1866
|
var r7 = D.S;
|
|
1867
1867
|
D.S = function(e, t) {
|
|
1868
|
-
i3 = el(), "object" == typeof t && null !== t && "function" == typeof t.then && function(e
|
|
1868
|
+
i3 = el(), "object" == typeof t && null !== t && "function" == typeof t.then && function(e) {
|
|
1869
1869
|
if (null === r4) {
|
|
1870
|
-
var
|
|
1870
|
+
var t = r4 = [];
|
|
1871
1871
|
r8 = 0, r6 = uJ(), r5 = {
|
|
1872
1872
|
status: "pending",
|
|
1873
1873
|
value: void 0,
|
|
1874
1874
|
then: function(e) {
|
|
1875
|
-
|
|
1875
|
+
t.push(e);
|
|
1876
1876
|
}
|
|
1877
1877
|
};
|
|
1878
1878
|
}
|
|
1879
|
-
r8++,
|
|
1880
|
-
}(
|
|
1879
|
+
r8++, e.then(r9, r9);
|
|
1880
|
+
}(t), null !== r7 && r7(e, t);
|
|
1881
1881
|
};
|
|
1882
1882
|
var le = I(null);
|
|
1883
1883
|
function lt() {
|
|
@@ -7947,7 +7947,7 @@
|
|
|
7947
7947
|
}
|
|
7948
7948
|
})(), e.exports = n(7816);
|
|
7949
7949
|
},
|
|
7950
|
-
|
|
7950
|
+
3085 (e, t, n) {
|
|
7951
7951
|
(function e() {
|
|
7952
7952
|
if ("u" > typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" == typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE) try {
|
|
7953
7953
|
__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! LICENSE: lib-react.dcf2a5e57a.js.LICENSE.txt */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<script>
|
|
13
13
|
window.__RSTEST_BROWSER_OPTIONS__ = __RSTEST_OPTIONS_PLACEHOLDER__;
|
|
14
14
|
</script>
|
|
15
|
-
<script defer src="/container-static/js/lib-react.
|
|
15
|
+
<script defer src="/container-static/js/lib-react.dcf2a5e57a.js"></script><script defer src="/container-static/js/101.36a8ccdf84.js"></script><script defer src="/container-static/js/index.0687a8142a.js"></script><link href="/container-static/css/index.5c72297783.css" rel="stylesheet"></head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="root"></div>
|
|
18
18
|
</body>
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import { DISPATCH_RESPONSE_TYPE, DISPATCH_METHOD_RPC, DISPATCH_RPC_REQUEST_TYPE, DISPATCH_MESSAGE_TYPE, DISPATCH_NAMESPACE_BROWSER } from "./361.js";
|
|
2
|
+
const DEFAULT_RPC_TIMEOUT_MS = 30000;
|
|
3
|
+
const getRpcTimeout = ()=>window.__RSTEST_BROWSER_OPTIONS__?.rpcTimeout ?? DEFAULT_RPC_TIMEOUT_MS;
|
|
4
|
+
const pendingRequests = new Map();
|
|
5
|
+
let requestIdCounter = 0;
|
|
6
|
+
let messageListenerInitialized = false;
|
|
7
|
+
const createRequestId = (prefix)=>{
|
|
8
|
+
if ('function' == typeof globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
|
|
9
|
+
requestIdCounter += 1;
|
|
10
|
+
return `${prefix}-${Date.now().toString(36)}-${requestIdCounter.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
11
|
+
};
|
|
12
|
+
const isDispatchResponse = (value)=>'object' == typeof value && null !== value && 'requestId' in value && 'string' == typeof value.requestId;
|
|
13
|
+
const settlePendingRequest = (response)=>{
|
|
14
|
+
const pending = pendingRequests.get(response.requestId);
|
|
15
|
+
if (!pending) return;
|
|
16
|
+
pendingRequests.delete(response.requestId);
|
|
17
|
+
if (response.stale) return void pending.reject(new Error(pending.staleMessage));
|
|
18
|
+
if (response.error) return void pending.reject(new Error(response.error));
|
|
19
|
+
pending.resolve(response.result);
|
|
20
|
+
};
|
|
21
|
+
const initMessageListener = ()=>{
|
|
22
|
+
if (messageListenerInitialized) return;
|
|
23
|
+
messageListenerInitialized = true;
|
|
24
|
+
window.addEventListener('message', (event)=>{
|
|
25
|
+
if (event.data?.type === DISPATCH_RESPONSE_TYPE) settlePendingRequest(event.data.payload);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const unwrapDispatchBridgeResult = (requestId, result, staleMessage)=>{
|
|
29
|
+
if (!isDispatchResponse(result)) throw new Error('Invalid dispatch bridge response payload.');
|
|
30
|
+
if (result.requestId !== requestId) throw new Error(`Mismatched dispatch response id: expected ${requestId}, got ${result.requestId}`);
|
|
31
|
+
if (result.stale) throw new Error(staleMessage);
|
|
32
|
+
if (result.error) throw new Error(result.error);
|
|
33
|
+
return result.result;
|
|
34
|
+
};
|
|
35
|
+
const dispatchRpc = ({ requestId, request, timeoutMs, timeoutMessage, staleMessage })=>{
|
|
36
|
+
if (window.parent === window) {
|
|
37
|
+
const dispatchBridge = window.__rstest_dispatch_rpc__;
|
|
38
|
+
if (!dispatchBridge) throw new Error('Dispatch RPC bridge is not available in top-level runner.');
|
|
39
|
+
return new Promise((resolve, reject)=>{
|
|
40
|
+
const timeoutId = setTimeout(()=>{
|
|
41
|
+
reject(new Error(timeoutMessage));
|
|
42
|
+
}, timeoutMs);
|
|
43
|
+
const call = Promise.resolve(dispatchBridge(request)).then((result)=>unwrapDispatchBridgeResult(requestId, result, staleMessage));
|
|
44
|
+
call.then((result)=>{
|
|
45
|
+
clearTimeout(timeoutId);
|
|
46
|
+
resolve(result);
|
|
47
|
+
}).catch((error)=>{
|
|
48
|
+
clearTimeout(timeoutId);
|
|
49
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
initMessageListener();
|
|
54
|
+
return new Promise((resolve, reject)=>{
|
|
55
|
+
const timeoutId = setTimeout(()=>{
|
|
56
|
+
pendingRequests.delete(requestId);
|
|
57
|
+
reject(new Error(timeoutMessage));
|
|
58
|
+
}, timeoutMs);
|
|
59
|
+
pendingRequests.set(requestId, {
|
|
60
|
+
staleMessage,
|
|
61
|
+
resolve: (value)=>{
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
resolve(value);
|
|
64
|
+
},
|
|
65
|
+
reject: (error)=>{
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
reject(error);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
window.parent.postMessage({
|
|
71
|
+
type: DISPATCH_MESSAGE_TYPE,
|
|
72
|
+
payload: {
|
|
73
|
+
type: DISPATCH_RPC_REQUEST_TYPE,
|
|
74
|
+
payload: request
|
|
75
|
+
}
|
|
76
|
+
}, '*');
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const getUrlSearchParam = (name)=>{
|
|
80
|
+
try {
|
|
81
|
+
const value = new URL(window.location.href).searchParams.get(name);
|
|
82
|
+
return value ?? void 0;
|
|
83
|
+
} catch {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const getCurrentTestPath = ()=>{
|
|
88
|
+
const testPath = window.__RSTEST_BROWSER_OPTIONS__?.testFile ?? getUrlSearchParam('testFile');
|
|
89
|
+
if (!testPath) throw new Error("Browser RPC requires testFile in __RSTEST_BROWSER_OPTIONS__. This usually indicates the runner iframe was not configured by the container or URL.");
|
|
90
|
+
return testPath;
|
|
91
|
+
};
|
|
92
|
+
const getCurrentRunId = ()=>{
|
|
93
|
+
const runId = window.__RSTEST_BROWSER_OPTIONS__?.runId ?? getUrlSearchParam('runId');
|
|
94
|
+
if (!runId) throw new Error("Browser RPC requires runId in __RSTEST_BROWSER_OPTIONS__. This usually indicates the runner iframe URL/config is stale or incomplete.");
|
|
95
|
+
return runId;
|
|
96
|
+
};
|
|
97
|
+
const createBrowserDispatchRequest = (requestId, request)=>({
|
|
98
|
+
requestId,
|
|
99
|
+
namespace: DISPATCH_NAMESPACE_BROWSER,
|
|
100
|
+
method: DISPATCH_METHOD_RPC,
|
|
101
|
+
args: request,
|
|
102
|
+
target: {
|
|
103
|
+
testFile: request.testPath
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const callBrowserRpc = async (payload)=>{
|
|
107
|
+
if ('config' === payload.kind && window.__RSTEST_BROWSER_OPTIONS__?.mode === 'collect') return;
|
|
108
|
+
const id = createRequestId('browser-rpc');
|
|
109
|
+
const rpcTimeout = getRpcTimeout();
|
|
110
|
+
const request = {
|
|
111
|
+
id,
|
|
112
|
+
testPath: getCurrentTestPath(),
|
|
113
|
+
runId: getCurrentRunId(),
|
|
114
|
+
...payload
|
|
115
|
+
};
|
|
116
|
+
const dispatchRequest = createBrowserDispatchRequest(id, request);
|
|
117
|
+
return dispatchRpc({
|
|
118
|
+
requestId: id,
|
|
119
|
+
request: dispatchRequest,
|
|
120
|
+
timeoutMs: rpcTimeout,
|
|
121
|
+
staleMessage: 'Stale browser RPC request ignored.',
|
|
122
|
+
timeoutMessage: `Browser RPC timeout after ${rpcTimeout / 1000}s: ${request.kind}.${request.method}`
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const serializeText = (value)=>{
|
|
126
|
+
if ('string' == typeof value) return {
|
|
127
|
+
type: 'string',
|
|
128
|
+
value
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
type: 'regexp',
|
|
132
|
+
source: value.source,
|
|
133
|
+
flags: value.flags
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
class Locator {
|
|
137
|
+
ir;
|
|
138
|
+
constructor(ir){
|
|
139
|
+
this.ir = ir;
|
|
140
|
+
}
|
|
141
|
+
getByRole(role, options) {
|
|
142
|
+
const next = {
|
|
143
|
+
steps: [
|
|
144
|
+
...this.ir.steps,
|
|
145
|
+
{
|
|
146
|
+
type: 'getByRole',
|
|
147
|
+
role,
|
|
148
|
+
options: options ? {
|
|
149
|
+
...options,
|
|
150
|
+
name: void 0 === options.name ? void 0 : serializeText(options.name)
|
|
151
|
+
} : void 0
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
return new Locator(next);
|
|
156
|
+
}
|
|
157
|
+
locator(selector) {
|
|
158
|
+
return new Locator({
|
|
159
|
+
steps: [
|
|
160
|
+
...this.ir.steps,
|
|
161
|
+
{
|
|
162
|
+
type: 'locator',
|
|
163
|
+
selector
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
getByText(text, options) {
|
|
169
|
+
return new Locator({
|
|
170
|
+
steps: [
|
|
171
|
+
...this.ir.steps,
|
|
172
|
+
{
|
|
173
|
+
type: 'getByText',
|
|
174
|
+
text: serializeText(text),
|
|
175
|
+
options
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
getByLabel(text, options) {
|
|
181
|
+
return new Locator({
|
|
182
|
+
steps: [
|
|
183
|
+
...this.ir.steps,
|
|
184
|
+
{
|
|
185
|
+
type: 'getByLabel',
|
|
186
|
+
text: serializeText(text),
|
|
187
|
+
options
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
getByPlaceholder(text, options) {
|
|
193
|
+
return new Locator({
|
|
194
|
+
steps: [
|
|
195
|
+
...this.ir.steps,
|
|
196
|
+
{
|
|
197
|
+
type: 'getByPlaceholder',
|
|
198
|
+
text: serializeText(text),
|
|
199
|
+
options
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
getByAltText(text, options) {
|
|
205
|
+
return new Locator({
|
|
206
|
+
steps: [
|
|
207
|
+
...this.ir.steps,
|
|
208
|
+
{
|
|
209
|
+
type: 'getByAltText',
|
|
210
|
+
text: serializeText(text),
|
|
211
|
+
options
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
getByTitle(text, options) {
|
|
217
|
+
return new Locator({
|
|
218
|
+
steps: [
|
|
219
|
+
...this.ir.steps,
|
|
220
|
+
{
|
|
221
|
+
type: 'getByTitle',
|
|
222
|
+
text: serializeText(text),
|
|
223
|
+
options
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
getByTestId(text) {
|
|
229
|
+
return new Locator({
|
|
230
|
+
steps: [
|
|
231
|
+
...this.ir.steps,
|
|
232
|
+
{
|
|
233
|
+
type: 'getByTestId',
|
|
234
|
+
text: serializeText(text)
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
filter(options) {
|
|
240
|
+
return new Locator({
|
|
241
|
+
steps: [
|
|
242
|
+
...this.ir.steps,
|
|
243
|
+
{
|
|
244
|
+
type: 'filter',
|
|
245
|
+
options: {
|
|
246
|
+
hasText: options.hasText ? serializeText(options.hasText) : void 0,
|
|
247
|
+
hasNotText: options.hasNotText ? serializeText(options.hasNotText) : void 0,
|
|
248
|
+
has: void 0 === options.has ? void 0 : isLocator(options.has) ? options.has.ir : (()=>{
|
|
249
|
+
throw new TypeError('Locator.filter({ has }) expects a Locator returned from @rstest/browser page.getBy* APIs.');
|
|
250
|
+
})(),
|
|
251
|
+
hasNot: void 0 === options.hasNot ? void 0 : isLocator(options.hasNot) ? options.hasNot.ir : (()=>{
|
|
252
|
+
throw new TypeError('Locator.filter({ hasNot }) expects a Locator returned from @rstest/browser page.getBy* APIs.');
|
|
253
|
+
})()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
and(other) {
|
|
260
|
+
if (!isLocator(other)) throw new TypeError('Locator.and() expects a Locator returned from @rstest/browser page.getBy* APIs.');
|
|
261
|
+
return new Locator({
|
|
262
|
+
steps: [
|
|
263
|
+
...this.ir.steps,
|
|
264
|
+
{
|
|
265
|
+
type: 'and',
|
|
266
|
+
locator: other.ir
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
or(other) {
|
|
272
|
+
if (!isLocator(other)) throw new TypeError('Locator.or() expects a Locator returned from @rstest/browser page.getBy* APIs.');
|
|
273
|
+
return new Locator({
|
|
274
|
+
steps: [
|
|
275
|
+
...this.ir.steps,
|
|
276
|
+
{
|
|
277
|
+
type: 'or',
|
|
278
|
+
locator: other.ir
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
nth(index) {
|
|
284
|
+
return new Locator({
|
|
285
|
+
steps: [
|
|
286
|
+
...this.ir.steps,
|
|
287
|
+
{
|
|
288
|
+
type: 'nth',
|
|
289
|
+
index
|
|
290
|
+
}
|
|
291
|
+
]
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
first() {
|
|
295
|
+
return new Locator({
|
|
296
|
+
steps: [
|
|
297
|
+
...this.ir.steps,
|
|
298
|
+
{
|
|
299
|
+
type: 'first'
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
last() {
|
|
305
|
+
return new Locator({
|
|
306
|
+
steps: [
|
|
307
|
+
...this.ir.steps,
|
|
308
|
+
{
|
|
309
|
+
type: 'last'
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async click(options) {
|
|
315
|
+
await this.callLocator('click', void 0 === options ? [] : [
|
|
316
|
+
options
|
|
317
|
+
]);
|
|
318
|
+
}
|
|
319
|
+
async dblclick(options) {
|
|
320
|
+
await this.callLocator('dblclick', void 0 === options ? [] : [
|
|
321
|
+
options
|
|
322
|
+
]);
|
|
323
|
+
}
|
|
324
|
+
async fill(value, options) {
|
|
325
|
+
await this.callLocator('fill', void 0 === options ? [
|
|
326
|
+
value
|
|
327
|
+
] : [
|
|
328
|
+
value,
|
|
329
|
+
options
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
332
|
+
async hover(options) {
|
|
333
|
+
await this.callLocator('hover', void 0 === options ? [] : [
|
|
334
|
+
options
|
|
335
|
+
]);
|
|
336
|
+
}
|
|
337
|
+
async press(key, options) {
|
|
338
|
+
await this.callLocator('press', void 0 === options ? [
|
|
339
|
+
key
|
|
340
|
+
] : [
|
|
341
|
+
key,
|
|
342
|
+
options
|
|
343
|
+
]);
|
|
344
|
+
}
|
|
345
|
+
async clear() {
|
|
346
|
+
await this.callLocator('clear', []);
|
|
347
|
+
}
|
|
348
|
+
async check(options) {
|
|
349
|
+
await this.callLocator('check', void 0 === options ? [] : [
|
|
350
|
+
options
|
|
351
|
+
]);
|
|
352
|
+
}
|
|
353
|
+
async uncheck(options) {
|
|
354
|
+
await this.callLocator('uncheck', void 0 === options ? [] : [
|
|
355
|
+
options
|
|
356
|
+
]);
|
|
357
|
+
}
|
|
358
|
+
async focus(options) {
|
|
359
|
+
await this.callLocator('focus', void 0 === options ? [] : [
|
|
360
|
+
options
|
|
361
|
+
]);
|
|
362
|
+
}
|
|
363
|
+
async blur(options) {
|
|
364
|
+
await this.callLocator('blur', void 0 === options ? [] : [
|
|
365
|
+
options
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
async scrollIntoViewIfNeeded(options) {
|
|
369
|
+
await this.callLocator('scrollIntoViewIfNeeded', void 0 === options ? [] : [
|
|
370
|
+
options
|
|
371
|
+
]);
|
|
372
|
+
}
|
|
373
|
+
async waitFor(options) {
|
|
374
|
+
await this.callLocator('waitFor', void 0 === options ? [] : [
|
|
375
|
+
options
|
|
376
|
+
]);
|
|
377
|
+
}
|
|
378
|
+
async dispatchEvent(type, eventInit) {
|
|
379
|
+
if ('string' != typeof type || !type) throw new TypeError('Locator.dispatchEvent() expects a non-empty event type string.');
|
|
380
|
+
await this.callLocator('dispatchEvent', void 0 === eventInit ? [
|
|
381
|
+
type
|
|
382
|
+
] : [
|
|
383
|
+
type,
|
|
384
|
+
eventInit
|
|
385
|
+
]);
|
|
386
|
+
}
|
|
387
|
+
async selectOption(value, options) {
|
|
388
|
+
if ('string' != typeof value && !(Array.isArray(value) && value.every((v)=>'string' == typeof v))) throw new TypeError('Locator.selectOption() only supports string or string[] values in browser mode.');
|
|
389
|
+
await this.callLocator('selectOption', void 0 === options ? [
|
|
390
|
+
value
|
|
391
|
+
] : [
|
|
392
|
+
value,
|
|
393
|
+
options
|
|
394
|
+
]);
|
|
395
|
+
}
|
|
396
|
+
async setInputFiles(files, options) {
|
|
397
|
+
if ('string' != typeof files && !(Array.isArray(files) && files.every((v)=>'string' == typeof v))) throw new TypeError('Locator.setInputFiles() only supports file path string or string[] in browser mode.');
|
|
398
|
+
await this.callLocator('setInputFiles', void 0 === options ? [
|
|
399
|
+
files
|
|
400
|
+
] : [
|
|
401
|
+
files,
|
|
402
|
+
options
|
|
403
|
+
]);
|
|
404
|
+
}
|
|
405
|
+
async callLocator(method, args) {
|
|
406
|
+
await callBrowserRpc({
|
|
407
|
+
kind: 'locator',
|
|
408
|
+
locator: this.ir,
|
|
409
|
+
method,
|
|
410
|
+
args
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const browserPageQueryMethods = [
|
|
415
|
+
'locator',
|
|
416
|
+
'getByRole',
|
|
417
|
+
'getByText',
|
|
418
|
+
'getByLabel',
|
|
419
|
+
'getByPlaceholder',
|
|
420
|
+
'getByAltText',
|
|
421
|
+
'getByTitle',
|
|
422
|
+
'getByTestId'
|
|
423
|
+
];
|
|
424
|
+
const rootLocator = new Locator({
|
|
425
|
+
steps: []
|
|
426
|
+
});
|
|
427
|
+
const createBrowserPage = ()=>Object.fromEntries(browserPageQueryMethods.map((methodName)=>[
|
|
428
|
+
methodName,
|
|
429
|
+
rootLocator[methodName].bind(rootLocator)
|
|
430
|
+
]));
|
|
431
|
+
const page = createBrowserPage();
|
|
432
|
+
const isLocator = (value)=>value instanceof Locator;
|
|
433
|
+
const setTestIdAttribute = async (attribute)=>{
|
|
434
|
+
await callBrowserRpc({
|
|
435
|
+
kind: 'config',
|
|
436
|
+
locator: {
|
|
437
|
+
steps: []
|
|
438
|
+
},
|
|
439
|
+
method: 'setTestIdAttribute',
|
|
440
|
+
args: [
|
|
441
|
+
attribute
|
|
442
|
+
]
|
|
443
|
+
});
|
|
444
|
+
};
|
|
445
|
+
const serializeMatcherText = (value)=>serializeText(value);
|
|
446
|
+
const createElementExpect = (locator, isNot)=>{
|
|
447
|
+
const callExpect = async (method, args, timeout)=>{
|
|
448
|
+
await callBrowserRpc({
|
|
449
|
+
kind: 'expect',
|
|
450
|
+
locator: locator.ir,
|
|
451
|
+
method,
|
|
452
|
+
args,
|
|
453
|
+
isNot,
|
|
454
|
+
timeout
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
const api = {
|
|
458
|
+
async toBeVisible (options) {
|
|
459
|
+
await callExpect('toBeVisible', [], options?.timeout);
|
|
460
|
+
},
|
|
461
|
+
async toBeHidden (options) {
|
|
462
|
+
await callExpect('toBeHidden', [], options?.timeout);
|
|
463
|
+
},
|
|
464
|
+
async toBeEnabled (options) {
|
|
465
|
+
await callExpect('toBeEnabled', [], options?.timeout);
|
|
466
|
+
},
|
|
467
|
+
async toBeDisabled (options) {
|
|
468
|
+
await callExpect('toBeDisabled', [], options?.timeout);
|
|
469
|
+
},
|
|
470
|
+
async toBeChecked (options) {
|
|
471
|
+
await callExpect('toBeChecked', [], options?.timeout);
|
|
472
|
+
},
|
|
473
|
+
async toBeUnchecked (options) {
|
|
474
|
+
await callExpect('toBeUnchecked', [], options?.timeout);
|
|
475
|
+
},
|
|
476
|
+
async toBeAttached (options) {
|
|
477
|
+
await callExpect('toBeAttached', [], options?.timeout);
|
|
478
|
+
},
|
|
479
|
+
async toBeDetached (options) {
|
|
480
|
+
await callExpect('toBeDetached', [], options?.timeout);
|
|
481
|
+
},
|
|
482
|
+
async toBeEditable (options) {
|
|
483
|
+
await callExpect('toBeEditable', [], options?.timeout);
|
|
484
|
+
},
|
|
485
|
+
async toBeFocused (options) {
|
|
486
|
+
await callExpect('toBeFocused', [], options?.timeout);
|
|
487
|
+
},
|
|
488
|
+
async toBeEmpty (options) {
|
|
489
|
+
await callExpect('toBeEmpty', [], options?.timeout);
|
|
490
|
+
},
|
|
491
|
+
async toBeInViewport (options) {
|
|
492
|
+
const ratio = options?.ratio;
|
|
493
|
+
await callExpect('toBeInViewport', void 0 === ratio ? [] : [
|
|
494
|
+
ratio
|
|
495
|
+
], options?.timeout);
|
|
496
|
+
},
|
|
497
|
+
async toHaveText (text, options) {
|
|
498
|
+
await callExpect('toHaveText', [
|
|
499
|
+
serializeMatcherText(text)
|
|
500
|
+
], options?.timeout);
|
|
501
|
+
},
|
|
502
|
+
async toContainText (text, options) {
|
|
503
|
+
await callExpect('toContainText', [
|
|
504
|
+
serializeMatcherText(text)
|
|
505
|
+
], options?.timeout);
|
|
506
|
+
},
|
|
507
|
+
async toHaveValue (value, options) {
|
|
508
|
+
await callExpect('toHaveValue', [
|
|
509
|
+
serializeMatcherText(value)
|
|
510
|
+
], options?.timeout);
|
|
511
|
+
},
|
|
512
|
+
async toHaveId (value, options) {
|
|
513
|
+
await callExpect('toHaveId', [
|
|
514
|
+
serializeMatcherText(value)
|
|
515
|
+
], options?.timeout);
|
|
516
|
+
},
|
|
517
|
+
async toHaveAttribute (name, value, options) {
|
|
518
|
+
const args = void 0 === value ? [
|
|
519
|
+
name
|
|
520
|
+
] : [
|
|
521
|
+
name,
|
|
522
|
+
serializeMatcherText(value)
|
|
523
|
+
];
|
|
524
|
+
await callExpect('toHaveAttribute', args, options?.timeout);
|
|
525
|
+
},
|
|
526
|
+
async toHaveClass (value, options) {
|
|
527
|
+
await callExpect('toHaveClass', [
|
|
528
|
+
serializeMatcherText(value)
|
|
529
|
+
], options?.timeout);
|
|
530
|
+
},
|
|
531
|
+
async toHaveCount (count, options) {
|
|
532
|
+
await callExpect('toHaveCount', [
|
|
533
|
+
count
|
|
534
|
+
], options?.timeout);
|
|
535
|
+
},
|
|
536
|
+
async toHaveCSS (name, value, options) {
|
|
537
|
+
if ('string' != typeof name || !name) throw new TypeError('toHaveCSS expects a non-empty CSS property name');
|
|
538
|
+
await callExpect('toHaveCSS', [
|
|
539
|
+
name,
|
|
540
|
+
serializeMatcherText(value)
|
|
541
|
+
], options?.timeout);
|
|
542
|
+
},
|
|
543
|
+
async toHaveJSProperty (name, value, options) {
|
|
544
|
+
if ('string' != typeof name || !name) throw new TypeError('toHaveJSProperty expects a non-empty property name');
|
|
545
|
+
await callExpect('toHaveJSProperty', [
|
|
546
|
+
name,
|
|
547
|
+
value
|
|
548
|
+
], options?.timeout);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
const withNot = api;
|
|
552
|
+
Object.defineProperty(withNot, 'not', {
|
|
553
|
+
configurable: false,
|
|
554
|
+
enumerable: false,
|
|
555
|
+
get () {
|
|
556
|
+
return createElementExpect(locator, !isNot);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
return withNot;
|
|
560
|
+
};
|
|
561
|
+
const api_element = (locator)=>{
|
|
562
|
+
if (!isLocator(locator)) throw new TypeError('expect.element() expects a Locator returned from @rstest/browser page.getBy* APIs.');
|
|
563
|
+
return createElementExpect(locator, false);
|
|
564
|
+
};
|
|
565
|
+
const markBrowserElement = ()=>{
|
|
566
|
+
Object.defineProperty(api_element, '__rstestBrowser', {
|
|
567
|
+
value: true,
|
|
568
|
+
configurable: false,
|
|
569
|
+
enumerable: false,
|
|
570
|
+
writable: false
|
|
571
|
+
});
|
|
572
|
+
};
|
|
573
|
+
const installExpectElement = ()=>{
|
|
574
|
+
const api = globalThis.RSTEST_API;
|
|
575
|
+
const target = api?.expect;
|
|
576
|
+
if (!target) throw new Error('RSTEST_API.expect is not registered yet. This usually indicates @rstest/browser was imported too early.');
|
|
577
|
+
if ('function' != typeof target.element || !target.element.__rstestBrowser) {
|
|
578
|
+
markBrowserElement();
|
|
579
|
+
target.element = api_element;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
installExpectElement();
|
|
583
|
+
export { Locator, page, setTestIdAttribute };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime allowlists for Browser RPC methods.
|
|
3
|
+
*
|
|
4
|
+
* Planned capabilities are intentionally documented in comments (not runtime
|
|
5
|
+
* data) to keep this module focused on host-side validation.
|
|
6
|
+
*
|
|
7
|
+
* Planned gaps (non-exhaustive):
|
|
8
|
+
* - Locator query/interop: filter({ hasNot, hasNotText }), locator.selector/length,
|
|
9
|
+
* locator.query()/element()/elements()/all(), page.elementLocator(element),
|
|
10
|
+
* locators.extend(...)
|
|
11
|
+
* - Locator actions: tripleClick, hover out, drag/drop helpers
|
|
12
|
+
* - Assertions: a11y matchers (accessible name/description), toHaveRole,
|
|
13
|
+
* toHaveValues
|
|
14
|
+
* - Artifacts intentionally excluded for now: screenshot/toMatchScreenshot/
|
|
15
|
+
* trace/video
|
|
16
|
+
*/
|
|
17
|
+
export declare const supportedLocatorActions: Set<string>;
|
|
18
|
+
export declare const supportedExpectElementMatchers: Set<string>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Locator, page, setTestIdAttribute } from './locator';
|
|
2
|
+
export type { BrowserPage, BrowserSerializable, LocatorBlurOptions, LocatorCheckOptions, LocatorClickOptions, LocatorDblclickOptions, LocatorDispatchEventInit, LocatorFillOptions, LocatorFilterOptions, LocatorFocusOptions, LocatorGetByRoleOptions, LocatorHoverOptions, LocatorKeyboardModifier, LocatorMouseButton, LocatorPosition, LocatorPressOptions, LocatorScrollIntoViewIfNeededOptions, LocatorSelectOptionOptions, LocatorSetInputFilesOptions, LocatorTextOptions, LocatorWaitForOptions, } from './locator';
|
|
3
|
+
export { Locator, page, setTestIdAttribute };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BrowserDispatchRequest } from '../protocol';
|
|
2
|
+
export declare const DEFAULT_RPC_TIMEOUT_MS = 30000;
|
|
3
|
+
export declare const getRpcTimeout: () => number;
|
|
4
|
+
export declare const createRequestId: (prefix: string) => string;
|
|
5
|
+
export declare const dispatchRpc: <T>({ requestId, request, timeoutMs, timeoutMessage, staleMessage, }: {
|
|
6
|
+
requestId: string;
|
|
7
|
+
request: BrowserDispatchRequest;
|
|
8
|
+
timeoutMs: number;
|
|
9
|
+
timeoutMessage: string;
|
|
10
|
+
staleMessage: string;
|
|
11
|
+
}) => Promise<T>;
|