@vitest/browser 3.2.0-beta.2 → 3.2.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/context.d.ts +2 -2
- package/dist/client/.vite/manifest.json +2 -2
- package/dist/client/__vitest__/assets/{index-DJfrXR3P.css → index-BXfDBrdK.css} +1 -1
- package/dist/client/__vitest__/assets/index-Br0wpA4B.js +58 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/orchestrator-R-D9fwJI.js +2724 -0
- package/dist/client/__vitest_browser__/{tester-CrBz0KXk.js → tester-B_w3in1j.js} +59 -7
- package/dist/client/esm-client-injector.js +1 -0
- package/dist/client/orchestrator.html +1 -1
- package/dist/client/tester/tester.html +1 -1
- package/dist/client.js +6 -1
- package/dist/context.js +18 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +112 -10
- package/dist/locators/index.d.ts +1 -0
- package/dist/providers.js +2 -1
- package/dist/state.js +4 -0
- package/dist/{webdriver-D7k26Na7.js → webdriver-B1QbgqhC.js} +52 -13
- package/package.json +12 -12
- package/dist/client/__vitest__/assets/index-BZjudRZr.js +0 -52
- package/dist/client/__vitest_browser__/orchestrator-CuTjqoE1.js +0 -287
|
@@ -14,12 +14,22 @@ const seen = {};
|
|
|
14
14
|
const __vitePreload = function preload(baseModule, deps, importerUrl) {
|
|
15
15
|
let promise = Promise.resolve();
|
|
16
16
|
if (deps && deps.length > 0) {
|
|
17
|
+
let allSettled2 = function(promises2) {
|
|
18
|
+
return Promise.all(
|
|
19
|
+
promises2.map(
|
|
20
|
+
(p2) => Promise.resolve(p2).then(
|
|
21
|
+
(value) => ({ status: "fulfilled", value }),
|
|
22
|
+
(reason) => ({ status: "rejected", reason })
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
};
|
|
17
27
|
document.getElementsByTagName("link");
|
|
18
28
|
const cspNonceMeta = document.querySelector(
|
|
19
29
|
"meta[property=csp-nonce]"
|
|
20
30
|
);
|
|
21
31
|
const cspNonce = (cspNonceMeta == null ? void 0 : cspNonceMeta.nonce) || (cspNonceMeta == null ? void 0 : cspNonceMeta.getAttribute("nonce"));
|
|
22
|
-
promise =
|
|
32
|
+
promise = allSettled2(
|
|
23
33
|
deps.map((dep) => {
|
|
24
34
|
dep = assetsURL(dep);
|
|
25
35
|
if (dep in seen) return;
|
|
@@ -1419,6 +1429,9 @@ function parseStacktrace(stack, options = {}) {
|
|
|
1419
1429
|
const fileUrl = stack2.file.startsWith("file://") ? stack2.file : `file://${stack2.file}`;
|
|
1420
1430
|
const sourceRootUrl = map.sourceRoot ? new URL(map.sourceRoot, fileUrl) : fileUrl;
|
|
1421
1431
|
file = new URL(source, sourceRootUrl).pathname;
|
|
1432
|
+
if (file.match(/\/\w:\//)) {
|
|
1433
|
+
file = file.slice(1);
|
|
1434
|
+
}
|
|
1422
1435
|
}
|
|
1423
1436
|
if (shouldFilter(ignoreStackEntries, file)) {
|
|
1424
1437
|
return null;
|
|
@@ -1586,6 +1599,31 @@ function createBrowserRunner(runnerClass, mocker, state, coverageModule) {
|
|
|
1586
1599
|
}
|
|
1587
1600
|
return rpc$2().onCollected(this.method, files);
|
|
1588
1601
|
});
|
|
1602
|
+
__publicField(this, "onTestAnnotate", (test, annotation) => {
|
|
1603
|
+
if (annotation.location) {
|
|
1604
|
+
const map = this.sourceMapCache.get(annotation.location.file);
|
|
1605
|
+
if (!map) {
|
|
1606
|
+
return rpc$2().onTaskAnnotate(test.id, annotation);
|
|
1607
|
+
}
|
|
1608
|
+
const traceMap = new TraceMap(map);
|
|
1609
|
+
const { line, column, source } = originalPositionFor(traceMap, annotation.location);
|
|
1610
|
+
if (line != null && column != null && source != null) {
|
|
1611
|
+
let file = annotation.location.file;
|
|
1612
|
+
if (source) {
|
|
1613
|
+
const fileUrl = annotation.location.file.startsWith("file://") ? annotation.location.file : `file://${annotation.location.file}`;
|
|
1614
|
+
const sourceRootUrl = map.sourceRoot ? new URL(map.sourceRoot, fileUrl) : fileUrl;
|
|
1615
|
+
file = new URL(source, sourceRootUrl).pathname;
|
|
1616
|
+
}
|
|
1617
|
+
annotation.location = {
|
|
1618
|
+
line,
|
|
1619
|
+
column: column + 1,
|
|
1620
|
+
// if the file path is on windows, we need to remove the starting slash
|
|
1621
|
+
file: file.match(/\/\w:\//) ? file.slice(1) : file
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return rpc$2().onTaskAnnotate(test.id, annotation);
|
|
1626
|
+
});
|
|
1589
1627
|
__publicField(this, "onTaskUpdate", (task, events) => {
|
|
1590
1628
|
return rpc$2().onTaskUpdate(this.method, task, events);
|
|
1591
1629
|
});
|
|
@@ -1652,14 +1690,23 @@ async function initiateRunner(state, mocker, config) {
|
|
|
1652
1690
|
};
|
|
1653
1691
|
return runner;
|
|
1654
1692
|
}
|
|
1693
|
+
async function getTraceMap(file, sourceMaps) {
|
|
1694
|
+
const result = sourceMaps.get(file) || await rpc$2().getBrowserFileSourceMap(file).then((map) => {
|
|
1695
|
+
sourceMaps.set(file, map);
|
|
1696
|
+
return map;
|
|
1697
|
+
});
|
|
1698
|
+
if (!result) {
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
return new TraceMap(result);
|
|
1702
|
+
}
|
|
1655
1703
|
async function updateTestFilesLocations(files, sourceMaps) {
|
|
1656
1704
|
const promises2 = files.map(async (file) => {
|
|
1657
|
-
const
|
|
1658
|
-
if (!
|
|
1705
|
+
const traceMap = await getTraceMap(file.filepath, sourceMaps);
|
|
1706
|
+
if (!traceMap) {
|
|
1659
1707
|
return null;
|
|
1660
1708
|
}
|
|
1661
|
-
const
|
|
1662
|
-
function updateLocation(task) {
|
|
1709
|
+
const updateLocation = (task) => {
|
|
1663
1710
|
if (task.location) {
|
|
1664
1711
|
const { line, column } = originalPositionFor(traceMap, task.location);
|
|
1665
1712
|
if (line != null && column != null) {
|
|
@@ -1669,7 +1716,7 @@ async function updateTestFilesLocations(files, sourceMaps) {
|
|
|
1669
1716
|
if ("tasks" in task) {
|
|
1670
1717
|
task.tasks.forEach(updateLocation);
|
|
1671
1718
|
}
|
|
1672
|
-
}
|
|
1719
|
+
};
|
|
1673
1720
|
file.tasks.forEach(updateLocation);
|
|
1674
1721
|
return null;
|
|
1675
1722
|
});
|
|
@@ -3146,7 +3193,7 @@ class ModuleMocker {
|
|
|
3146
3193
|
return;
|
|
3147
3194
|
}
|
|
3148
3195
|
await this.rpc.invalidate(ids);
|
|
3149
|
-
this.interceptor.invalidate();
|
|
3196
|
+
await this.interceptor.invalidate();
|
|
3150
3197
|
this.registry.clear();
|
|
3151
3198
|
}
|
|
3152
3199
|
async importActual(id, importer) {
|
|
@@ -3254,6 +3301,8 @@ class ModuleMocker {
|
|
|
3254
3301
|
});
|
|
3255
3302
|
this.queue.add(promise);
|
|
3256
3303
|
}
|
|
3304
|
+
// We need to await mock registration before importing the actual module
|
|
3305
|
+
// In case there is a mocked module in the import chain
|
|
3257
3306
|
wrapDynamicImport(moduleFactory) {
|
|
3258
3307
|
if (typeof moduleFactory === "function") {
|
|
3259
3308
|
const promise = new Promise((resolve2, reject) => {
|
|
@@ -3497,6 +3546,9 @@ async function cleanup() {
|
|
|
3497
3546
|
}
|
|
3498
3547
|
}
|
|
3499
3548
|
await userEvent.cleanup().catch((error) => unhandledError(error, "Cleanup Error"));
|
|
3549
|
+
await Promise.all(
|
|
3550
|
+
getBrowserState().cleanups.map((fn) => fn())
|
|
3551
|
+
).catch((error) => unhandledError(error, "Cleanup Error"));
|
|
3500
3552
|
if (contextSwitched) {
|
|
3501
3553
|
await rpc2.wdioSwitchContext("parent").catch((error) => unhandledError(error, "Cleanup Error"));
|
|
3502
3554
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
{__VITEST_INJECTOR__}
|
|
27
27
|
{__VITEST_ERROR_CATCHER__}
|
|
28
28
|
{__VITEST_SCRIPTS__}
|
|
29
|
-
<script type="module" crossorigin src="/__vitest_browser__/orchestrator-
|
|
29
|
+
<script type="module" crossorigin src="/__vitest_browser__/orchestrator-R-D9fwJI.js"></script>
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-Owv5OOOf.js">
|
|
31
31
|
</head>
|
|
32
32
|
<body>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="icon" href="{__VITEST_FAVICON__}" type="image/svg+xml">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Vitest Browser Tester</title>
|
|
8
|
-
<script type="module" crossorigin src="/__vitest_browser__/tester-
|
|
8
|
+
<script type="module" crossorigin src="/__vitest_browser__/tester-B_w3in1j.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/__vitest_browser__/utils-Owv5OOOf.js">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/client.js
CHANGED
|
@@ -261,6 +261,7 @@ const stringify = (value, replacer, space) => {
|
|
|
261
261
|
|
|
262
262
|
/* @__NO_SIDE_EFFECTS__ */
|
|
263
263
|
function getBrowserState() {
|
|
264
|
+
// @ts-expect-error not typed global
|
|
264
265
|
return window.__vitest_browser_runner__;
|
|
265
266
|
}
|
|
266
267
|
|
|
@@ -272,11 +273,13 @@ const PORT = location.port;
|
|
|
272
273
|
const HOST = [location.hostname, PORT].filter(Boolean).join(":");
|
|
273
274
|
const RPC_ID = PAGE_TYPE === "orchestrator" ? getBrowserState().sessionId : getBrowserState().testerId;
|
|
274
275
|
const METHOD = getBrowserState().method;
|
|
275
|
-
const ENTRY_URL = `${location.protocol === "https:" ? "wss:" : "ws:"}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&rpcId=${RPC_ID}&sessionId=${getBrowserState().sessionId}&projectName=${getBrowserState().config.name || ""}&method=${METHOD}&token=${window.VITEST_API_TOKEN}`;
|
|
276
|
+
const ENTRY_URL = `${location.protocol === "https:" ? "wss:" : "ws:"}//${HOST}/__vitest_browser_api__?type=${PAGE_TYPE}&rpcId=${RPC_ID}&sessionId=${getBrowserState().sessionId}&projectName=${getBrowserState().config.name || ""}&method=${METHOD}&token=${window.VITEST_API_TOKEN || "0"}`;
|
|
276
277
|
let setCancel = (_) => {};
|
|
277
278
|
const onCancel = new Promise((resolve) => {
|
|
278
279
|
setCancel = resolve;
|
|
279
280
|
});
|
|
281
|
+
// ws connection can be established before the orchestrator is fully loaded
|
|
282
|
+
// in very rare cases in the preview provider
|
|
280
283
|
function waitForOrchestrator() {
|
|
281
284
|
return new Promise((resolve, reject) => {
|
|
282
285
|
const type = getBrowserState().type;
|
|
@@ -322,6 +325,7 @@ function createClient() {
|
|
|
322
325
|
cdp.emit(event, payload);
|
|
323
326
|
},
|
|
324
327
|
async resolveManualMock(url) {
|
|
328
|
+
// @ts-expect-error not typed global API
|
|
325
329
|
const mocker = globalThis.__vitest_mocker__;
|
|
326
330
|
const responseId = getBrowserState().sessionId;
|
|
327
331
|
if (!mocker) {
|
|
@@ -374,6 +378,7 @@ function createClient() {
|
|
|
374
378
|
if (ctx.ws.OPEN === ctx.ws.readyState) {
|
|
375
379
|
resolve();
|
|
376
380
|
}
|
|
381
|
+
// still have a listener even if it's already open to update tries
|
|
377
382
|
ctx.ws.addEventListener("open", () => {
|
|
378
383
|
tries = reconnectTries;
|
|
379
384
|
resolve();
|
package/dist/context.js
CHANGED
|
@@ -15,6 +15,7 @@ function ensureAwaited(promise) {
|
|
|
15
15
|
throw error;
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
|
+
// don't even start the promise if it's not awaited to not cause any unhanded promise rejections
|
|
18
19
|
let promiseResult;
|
|
19
20
|
return {
|
|
20
21
|
then(onFulfilled, onRejected) {
|
|
@@ -32,10 +33,12 @@ function ensureAwaited(promise) {
|
|
|
32
33
|
}
|
|
33
34
|
/* @__NO_SIDE_EFFECTS__ */
|
|
34
35
|
function getBrowserState() {
|
|
36
|
+
// @ts-expect-error not typed global
|
|
35
37
|
return window.__vitest_browser_runner__;
|
|
36
38
|
}
|
|
37
39
|
/* @__NO_SIDE_EFFECTS__ */
|
|
38
40
|
function getWorkerState() {
|
|
41
|
+
// @ts-expect-error not typed global
|
|
39
42
|
const state = window.__vitest_worker__;
|
|
40
43
|
if (!state) {
|
|
41
44
|
throw new Error("Worker state is not found. This is an issue with Vitest. Please, open an issue.");
|
|
@@ -55,14 +58,19 @@ function escapeIdForCSSSelector(id) {
|
|
|
55
58
|
return id.split("").map((char) => {
|
|
56
59
|
const code = char.charCodeAt(0);
|
|
57
60
|
if (char === " " || char === "#" || char === "." || char === ":" || char === "[" || char === "]" || char === ">" || char === "+" || char === "~" || char === "\\") {
|
|
61
|
+
// Escape common special characters with backslashes
|
|
58
62
|
return `\\${char}`;
|
|
59
63
|
} else if (code >= 65536) {
|
|
64
|
+
// Unicode escape for characters outside the BMP
|
|
60
65
|
return `\\${code.toString(16).toUpperCase().padStart(6, "0")} `;
|
|
61
66
|
} else if (code < 32 || code === 127) {
|
|
67
|
+
// Non-printable ASCII characters (0x00-0x1F and 0x7F) are escaped
|
|
62
68
|
return `\\${code.toString(16).toUpperCase().padStart(2, "0")} `;
|
|
63
69
|
} else if (code >= 128) {
|
|
70
|
+
// Non-ASCII characters (0x80 and above) are escaped
|
|
64
71
|
return `\\${code.toString(16).toUpperCase().padStart(2, "0")} `;
|
|
65
72
|
} else {
|
|
73
|
+
// Allowable characters are used directly
|
|
66
74
|
return char;
|
|
67
75
|
}
|
|
68
76
|
}).join("");
|
|
@@ -71,6 +79,7 @@ function getUniqueCssSelector(el) {
|
|
|
71
79
|
const path = [];
|
|
72
80
|
let parent;
|
|
73
81
|
let hasShadowRoot = false;
|
|
82
|
+
// eslint-disable-next-line no-cond-assign
|
|
74
83
|
while (parent = getParent(el)) {
|
|
75
84
|
if (parent.shadowRoot) {
|
|
76
85
|
hasShadowRoot = true;
|
|
@@ -115,11 +124,13 @@ function processTimeoutOptions(options_) {
|
|
|
115
124
|
if (options_ && options_.timeout != null || provider$1 !== "playwright") {
|
|
116
125
|
return options_;
|
|
117
126
|
}
|
|
127
|
+
// if there is a default action timeout, use it
|
|
118
128
|
if (getWorkerState().config.browser.providerOptions.actionTimeout != null) {
|
|
119
129
|
return options_;
|
|
120
130
|
}
|
|
121
131
|
const runner = getBrowserState().runner;
|
|
122
132
|
const startTime = runner._currentTaskStartTime;
|
|
133
|
+
// ignore timeout if this is called outside of a test
|
|
123
134
|
if (!startTime) {
|
|
124
135
|
return options_;
|
|
125
136
|
}
|
|
@@ -134,10 +145,13 @@ function processTimeoutOptions(options_) {
|
|
|
134
145
|
if (remainingTime <= 0) {
|
|
135
146
|
return options_;
|
|
136
147
|
}
|
|
148
|
+
// give us some time to process the timeout
|
|
137
149
|
options_.timeout = remainingTime - 100;
|
|
138
150
|
return options_;
|
|
139
151
|
}
|
|
140
152
|
|
|
153
|
+
// this file should not import anything directly, only types and utils
|
|
154
|
+
// @ts-expect-error not typed global
|
|
141
155
|
const provider = __vitest_browser_runner__.provider;
|
|
142
156
|
const sessionId = getBrowserState().sessionId;
|
|
143
157
|
const channel = new BroadcastChannel(`vitest:${sessionId}`);
|
|
@@ -149,12 +163,15 @@ function createUserEvent(__tl_user_event_base__, options) {
|
|
|
149
163
|
return createPreviewUserEvent(__tl_user_event_base__, options ?? {});
|
|
150
164
|
}
|
|
151
165
|
const keyboard = { unreleased: [] };
|
|
166
|
+
// https://playwright.dev/docs/api/class-keyboard
|
|
167
|
+
// https://webdriver.io/docs/api/browser/keys/
|
|
152
168
|
const modifier = provider === `playwright` ? "ControlOrMeta" : provider === "webdriverio" ? "Ctrl" : "Control";
|
|
153
169
|
const userEvent = {
|
|
154
170
|
setup() {
|
|
155
171
|
return createUserEvent();
|
|
156
172
|
},
|
|
157
173
|
async cleanup() {
|
|
174
|
+
// avoid cleanup rpc call if there is nothing to cleanup
|
|
158
175
|
if (!keyboard.unreleased.length) {
|
|
159
176
|
return;
|
|
160
177
|
}
|
|
@@ -423,6 +440,7 @@ const locators = {
|
|
|
423
440
|
const Locator = page._createLocator("css=body").constructor;
|
|
424
441
|
for (const method in methods) {
|
|
425
442
|
const cb = methods[method];
|
|
443
|
+
// @ts-expect-error types are hard to make work
|
|
426
444
|
Locator.prototype[method] = function(...args) {
|
|
427
445
|
const selectorOrLocator = cb.call(this, ...args);
|
|
428
446
|
if (typeof selectorOrLocator === "string") {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { ViteDevServer, HtmlTagDescriptor } from 'vite';
|
|
|
5
5
|
import { CancelReason, BrowserTesterOptions, TestExecutionMethod, RunnerTestFile, AfterSuiteRunMeta, UserConsoleLog, SnapshotResult, SerializedConfig, ErrorWithDiff, ParsedStack } from 'vitest';
|
|
6
6
|
import { MockedModuleSerialized } from '@vitest/mocker';
|
|
7
7
|
import { ServerIdResolution, ServerMockResolution } from '@vitest/mocker/node';
|
|
8
|
-
import { TaskResultPack, TaskEventPack } from '@vitest/runner';
|
|
8
|
+
import { TestAnnotation, TaskResultPack, TaskEventPack } from '@vitest/runner';
|
|
9
9
|
|
|
10
10
|
type ArgumentsType<T> = T extends (...args: infer A) => any ? A : never;
|
|
11
11
|
type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
|
|
@@ -29,6 +29,7 @@ interface WebSocketBrowserHandlers {
|
|
|
29
29
|
onUnhandledError: (error: unknown, type: string) => Promise<void>;
|
|
30
30
|
onQueued: (method: TestExecutionMethod, file: RunnerTestFile) => void;
|
|
31
31
|
onCollected: (method: TestExecutionMethod, files: RunnerTestFile[]) => Promise<void>;
|
|
32
|
+
onTaskAnnotate: (testId: string, annotation: TestAnnotation) => Promise<TestAnnotation>;
|
|
32
33
|
onTaskUpdate: (method: TestExecutionMethod, packs: TaskResultPack[], events: TaskEventPack[]) => void;
|
|
33
34
|
onAfterSuiteRun: (meta: AfterSuiteRunMeta) => void;
|
|
34
35
|
cancelCurrentRun: (reason: CancelReason) => void;
|
|
@@ -52,6 +53,7 @@ interface WebSocketBrowserHandlers {
|
|
|
52
53
|
registerMock: (sessionId: string, mock: MockedModuleSerialized) => void;
|
|
53
54
|
unregisterMock: (sessionId: string, id: string) => void;
|
|
54
55
|
clearMocks: (sessionId: string) => void;
|
|
56
|
+
// cdp
|
|
55
57
|
sendCdpEvent: (sessionId: string, event: string, payload?: Record<string, unknown>) => unknown;
|
|
56
58
|
trackCdpEvent: (sessionId: string, type: "on" | "once" | "off", event: string, listenerId: string) => void;
|
|
57
59
|
}
|
|
@@ -135,6 +137,7 @@ declare class ParentBrowserProject {
|
|
|
135
137
|
children: Set<ProjectBrowser>;
|
|
136
138
|
vitest: Vitest;
|
|
137
139
|
config: ResolvedConfig;
|
|
140
|
+
// cache for non-vite source maps
|
|
138
141
|
private sourceMapCache;
|
|
139
142
|
constructor(project: TestProject, base: string);
|
|
140
143
|
setServer(vite: Vite.ViteDevServer): void;
|