@vitest/browser 5.0.0-beta.2 → 5.0.0-beta.4
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 +8 -0
- package/dist/client/.vite/manifest.json +8 -8
- package/dist/client/__vitest__/assets/index-B2jbgabi.js +139 -0
- package/dist/client/__vitest__/assets/index-CEyXAB_I.css +1 -0
- package/dist/client/__vitest__/index.html +2 -2
- package/dist/client/__vitest_browser__/defineProperty-C3k2g8Sk.js +267 -0
- package/dist/client/__vitest_browser__/orchestrator-B44yH1M4.js +343 -0
- package/dist/client/__vitest_browser__/rrweb-snapshot-iZCFA2to.js +4388 -0
- package/dist/client/__vitest_browser__/tester-kAU8uxhk.js +5086 -0
- package/dist/client/orchestrator.html +2 -2
- package/dist/client/tester/tester.html +2 -2
- package/dist/client/tester/trace.d.ts +9 -6
- package/dist/client.js +10 -1
- package/dist/context.js +53 -23
- package/dist/expect-element.js +30 -30
- package/dist/index.js +153 -33
- package/dist/locators-CPBpJv8y.js +5 -0
- package/dist/locators.js +1 -1
- package/dist/shared/screenshotMatcher/types.d.ts +2 -1
- package/dist/state.js +64 -14
- package/dist/types.d.ts +7 -2
- package/jest-dom.d.ts +26 -6
- package/matchers.d.ts +2 -1
- package/package.json +7 -7
- package/dist/client/__vitest__/assets/index-Cd6On2Pm.js +0 -136
- package/dist/client/__vitest__/assets/index-Dw9P28Qt.css +0 -1
- package/dist/client/__vitest_browser__/orchestrator-BfoS0x4w.js +0 -411
- package/dist/client/__vitest_browser__/rrweb-snapshot-xhvrgOHx.js +0 -5476
- package/dist/client/__vitest_browser__/tester-BJtW9QqZ.js +0 -5588
- package/dist/client/__vitest_browser__/utils-Nd8hqrhP.js +0 -189
- package/dist/locators-CesZ2RSY.js +0 -5
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { c as relative, n as getBrowserState, r as getConfig, t as _defineProperty } from "./defineProperty-C3k2g8Sk.js";
|
|
2
|
+
import { channel, client, globalChannel } from "@vitest/browser/client";
|
|
3
|
+
import { Traces } from "vitest/internal/traces";
|
|
4
|
+
|
|
5
|
+
//#region src/client/ui.ts
|
|
6
|
+
/* @__NO_SIDE_EFFECTS__ */
|
|
7
|
+
function getUiAPI() {
|
|
8
|
+
return window.__vitest_ui_api__;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/client/orchestrator.ts
|
|
13
|
+
var ID_ALL = "__vitest_all__";
|
|
14
|
+
var IframeOrchestrator = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
_defineProperty(this, "cancelled", false);
|
|
17
|
+
_defineProperty(this, "recreateNonIsolatedIframe", false);
|
|
18
|
+
_defineProperty(this, "iframes", /* @__PURE__ */ new Map());
|
|
19
|
+
_defineProperty(this, "eventTarget", new EventTarget());
|
|
20
|
+
_defineProperty(this, "traces", void 0);
|
|
21
|
+
_defineProperty(this, "loggedIframe", /* @__PURE__ */ new WeakSet());
|
|
22
|
+
_defineProperty(this, "iframeEvents", /* @__PURE__ */ new WeakMap());
|
|
23
|
+
debug("init orchestrator", (/* @__PURE__ */ getBrowserState()).sessionId);
|
|
24
|
+
const otelConfig = (/* @__PURE__ */ getBrowserState()).config.experimental.openTelemetry;
|
|
25
|
+
this.traces = new Traces({
|
|
26
|
+
enabled: !!((otelConfig === null || otelConfig === void 0 ? void 0 : otelConfig.enabled) && otelConfig.browserSdkPath),
|
|
27
|
+
sdkPath: `/@fs/${otelConfig === null || otelConfig === void 0 ? void 0 : otelConfig.browserSdkPath}`
|
|
28
|
+
});
|
|
29
|
+
channel.addEventListener("message", (e) => this.onIframeEvent(e));
|
|
30
|
+
globalChannel.addEventListener("message", (e) => this.onGlobalChannelEvent(e));
|
|
31
|
+
}
|
|
32
|
+
async createTesters(options) {
|
|
33
|
+
await this.traces.waitInit();
|
|
34
|
+
this.traces.recordInitSpan(this.traces.getContextFromCarrier((/* @__PURE__ */ getBrowserState()).otelCarrier));
|
|
35
|
+
const orchestratorSpan = this.traces.startContextSpan("vitest.browser.orchestrator.run", this.traces.getContextFromCarrier(options.otelCarrier));
|
|
36
|
+
orchestratorSpan.span.setAttributes({ "vitest.browser.files": options.files.map((f) => f.filepath) });
|
|
37
|
+
const endSpan = async () => {
|
|
38
|
+
orchestratorSpan.span.end();
|
|
39
|
+
await this.traces.flush();
|
|
40
|
+
};
|
|
41
|
+
const startTime = performance.now();
|
|
42
|
+
this.cancelled = false;
|
|
43
|
+
const config = getConfig();
|
|
44
|
+
debug("create testers", ...options.files.join(", "));
|
|
45
|
+
const container = await getContainer(config);
|
|
46
|
+
if (config.browser.ui) {
|
|
47
|
+
container.setAttribute("data-ready", "true");
|
|
48
|
+
if (container.textContent) container.textContent = "";
|
|
49
|
+
}
|
|
50
|
+
if (config.browser.isolate === false) {
|
|
51
|
+
await this.runNonIsolatedTests(container, options, startTime, orchestratorSpan.context);
|
|
52
|
+
await endSpan();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.iframes.forEach((iframe) => iframe.remove());
|
|
56
|
+
this.iframes.clear();
|
|
57
|
+
for (let i = 0; i < options.files.length; i++) {
|
|
58
|
+
if (this.cancelled) {
|
|
59
|
+
await endSpan();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const file = options.files[i];
|
|
63
|
+
debug("create iframe", file.filepath);
|
|
64
|
+
await this.runIsolatedTestInIframe(container, file, options, startTime, orchestratorSpan.context);
|
|
65
|
+
}
|
|
66
|
+
await endSpan();
|
|
67
|
+
}
|
|
68
|
+
async cleanupTesters() {
|
|
69
|
+
if (getConfig().browser.isolate) {
|
|
70
|
+
const files = Array.from(this.iframes.keys());
|
|
71
|
+
const ui = /* @__PURE__ */ getUiAPI();
|
|
72
|
+
if (ui && files[0]) {
|
|
73
|
+
const id = generateFileId(files[0]);
|
|
74
|
+
ui.setCurrentFileId(id);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!this.iframes.get(ID_ALL)) return;
|
|
79
|
+
await this.sendEventToIframe({
|
|
80
|
+
event: "cleanup",
|
|
81
|
+
iframeId: ID_ALL
|
|
82
|
+
});
|
|
83
|
+
this.recreateNonIsolatedIframe = true;
|
|
84
|
+
}
|
|
85
|
+
async runNonIsolatedTests(container, options, startTime, otelContext) {
|
|
86
|
+
if (this.recreateNonIsolatedIframe) {
|
|
87
|
+
this.recreateNonIsolatedIframe = false;
|
|
88
|
+
this.iframes.get(ID_ALL).remove();
|
|
89
|
+
this.iframes.delete(ID_ALL);
|
|
90
|
+
debug("recreate non-isolated iframe");
|
|
91
|
+
}
|
|
92
|
+
if (!this.iframes.has(ID_ALL)) {
|
|
93
|
+
debug("preparing non-isolated iframe");
|
|
94
|
+
await this.prepareIframe(container, ID_ALL, startTime, otelContext);
|
|
95
|
+
}
|
|
96
|
+
const { width, height } = getConfig().browser.viewport;
|
|
97
|
+
await setIframeViewport(width, height);
|
|
98
|
+
debug("run non-isolated tests", options.files.join(", "));
|
|
99
|
+
await this.sendEventToIframe({
|
|
100
|
+
event: "execute",
|
|
101
|
+
iframeId: ID_ALL,
|
|
102
|
+
files: options.files,
|
|
103
|
+
method: options.method,
|
|
104
|
+
context: options.providedContext
|
|
105
|
+
});
|
|
106
|
+
debug("finished running tests", options.files.join(", "));
|
|
107
|
+
}
|
|
108
|
+
async runIsolatedTestInIframe(container, spec, options, startTime, otelContext) {
|
|
109
|
+
const { width, height } = getConfig().browser.viewport;
|
|
110
|
+
const file = spec.filepath;
|
|
111
|
+
if (this.iframes.has(file)) {
|
|
112
|
+
this.iframes.get(file).remove();
|
|
113
|
+
this.iframes.delete(file);
|
|
114
|
+
}
|
|
115
|
+
await this.prepareIframe(container, file, startTime, otelContext);
|
|
116
|
+
await setIframeViewport(width, height);
|
|
117
|
+
await this.sendEventToIframe({
|
|
118
|
+
event: "execute",
|
|
119
|
+
files: [spec],
|
|
120
|
+
method: options.method,
|
|
121
|
+
iframeId: file,
|
|
122
|
+
context: options.providedContext
|
|
123
|
+
});
|
|
124
|
+
await this.sendEventToIframe({
|
|
125
|
+
event: "cleanup",
|
|
126
|
+
iframeId: file
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
dispatchIframeError(error) {
|
|
130
|
+
const event = new CustomEvent("iframeerror", { detail: error });
|
|
131
|
+
this.eventTarget.dispatchEvent(event);
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
async prepareIframe(container, iframeId, startTime, otelContext) {
|
|
135
|
+
const iframe = this.createTestIframe(iframeId);
|
|
136
|
+
container.appendChild(iframe);
|
|
137
|
+
await new Promise((resolve, reject) => {
|
|
138
|
+
iframe.onload = () => {
|
|
139
|
+
const href = this.getIframeHref(iframe);
|
|
140
|
+
debug("iframe loaded with href", href);
|
|
141
|
+
if (href !== iframe.src) reject(this.dispatchIframeError(/* @__PURE__ */ new Error(`Cannot connect to the iframe. Did you change the location or submitted a form? If so, don't forget to call \`event.preventDefault()\` to avoid reloading the page.
|
|
142
|
+
|
|
143
|
+
Received URL: ${href || "unknown due to CORS"}\nExpected: ${iframe.src}`)));
|
|
144
|
+
else if (this.iframes.has(iframeId)) {
|
|
145
|
+
const events = this.iframeEvents.get(iframe);
|
|
146
|
+
if (events === null || events === void 0 ? void 0 : events.size) this.dispatchIframeError(new Error(this.createWarningMessage(iframeId, "during a test")));
|
|
147
|
+
else this.warnReload(iframe, iframeId);
|
|
148
|
+
} else {
|
|
149
|
+
this.iframes.set(iframeId, iframe);
|
|
150
|
+
this.sendEventToIframe({
|
|
151
|
+
event: "prepare",
|
|
152
|
+
iframeId,
|
|
153
|
+
startTime,
|
|
154
|
+
otelCarrier: this.traces.getContextCarrier(otelContext)
|
|
155
|
+
}).then(resolve, (error) => reject(this.dispatchIframeError(error)));
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
iframe.onerror = (e) => {
|
|
159
|
+
if (typeof e === "string") reject(this.dispatchIframeError(new Error(e)));
|
|
160
|
+
else if (e instanceof ErrorEvent) reject(this.dispatchIframeError(e.error));
|
|
161
|
+
else reject(this.dispatchIframeError(/* @__PURE__ */ new Error(`Cannot load the iframe ${iframeId}.`)));
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
return iframe;
|
|
165
|
+
}
|
|
166
|
+
createWarningMessage(iframeId, location) {
|
|
167
|
+
return `The iframe${iframeId === ID_ALL ? "" : ` for "${iframeId}"`} was reloaded ${location}. This can lead to unexpected behavior during tests, duplicated test results or tests hanging.\n\nMake sure that your test code does not change window's location, submit forms without preventing default behavior, or imports unoptimized dependencies.\nIf you are using a framework that manipulates browser history (like React Router), consider using memory-based routing for tests. If you think this is a false positive, open an issue with a reproduction: https://github.com/vitest-dev/vitest/issues/new`;
|
|
168
|
+
}
|
|
169
|
+
warnReload(iframe, iframeId) {
|
|
170
|
+
if (this.loggedIframe.has(iframe)) return;
|
|
171
|
+
this.loggedIframe.add(iframe);
|
|
172
|
+
const message = `\x1B[41m WARNING \x1B[49m ${this.createWarningMessage(iframeId, "multiple times")}`;
|
|
173
|
+
client.rpc.sendLog("run", {
|
|
174
|
+
type: "stderr",
|
|
175
|
+
time: Date.now(),
|
|
176
|
+
content: message,
|
|
177
|
+
size: message.length,
|
|
178
|
+
taskId: iframeId === ID_ALL ? void 0 : generateFileId(iframeId)
|
|
179
|
+
}).catch(() => {});
|
|
180
|
+
}
|
|
181
|
+
getIframeHref(iframe) {
|
|
182
|
+
try {
|
|
183
|
+
var _iframe$contentWindow;
|
|
184
|
+
return (_iframe$contentWindow = iframe.contentWindow) === null || _iframe$contentWindow === void 0 ? void 0 : _iframe$contentWindow.location.href;
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
createTestIframe(iframeId) {
|
|
190
|
+
const iframe = document.createElement("iframe");
|
|
191
|
+
const src = `/?sessionId=${(/* @__PURE__ */ getBrowserState()).sessionId}&iframeId=${iframeId}`;
|
|
192
|
+
const config = getConfig();
|
|
193
|
+
iframe.setAttribute("loading", "eager");
|
|
194
|
+
iframe.setAttribute("src", src);
|
|
195
|
+
iframe.setAttribute("data-vitest", "true");
|
|
196
|
+
iframe.setAttribute("allowfullscreen", "true");
|
|
197
|
+
iframe.setAttribute("allow", "clipboard-write;");
|
|
198
|
+
iframe.setAttribute("name", "vitest-iframe");
|
|
199
|
+
iframe.style.setProperty("border", "none");
|
|
200
|
+
iframe.style.setProperty("background-color", "#fff");
|
|
201
|
+
iframe.style.setProperty("width", "var(--viewport-width)");
|
|
202
|
+
iframe.style.setProperty("height", "var(--viewport-height)");
|
|
203
|
+
if (config.browser.ui) {
|
|
204
|
+
if (config.browser.name !== "firefox") iframe.style.setProperty("transform", "scale(min(1, calc(100cqw / var(--viewport-width)), calc(100cqh / var(--viewport-height))))");
|
|
205
|
+
else {
|
|
206
|
+
iframe.style.setProperty("--container-width", "100cqw");
|
|
207
|
+
iframe.style.setProperty("--container-height", "100cqh");
|
|
208
|
+
iframe.style.setProperty("transform", "scale(min(1, tan(atan2(var(--container-width), var(--viewport-width))), tan(atan2(var(--container-height), var(--viewport-height)))))");
|
|
209
|
+
}
|
|
210
|
+
iframe.style.setProperty("transform-origin", "top left");
|
|
211
|
+
}
|
|
212
|
+
return iframe;
|
|
213
|
+
}
|
|
214
|
+
async onGlobalChannelEvent(e) {
|
|
215
|
+
debug("global channel event", JSON.stringify(e.data));
|
|
216
|
+
switch (e.data.type) {
|
|
217
|
+
case "cancel":
|
|
218
|
+
this.cancelled = true;
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async onIframeEvent(e) {
|
|
223
|
+
debug("iframe event", JSON.stringify(e.data));
|
|
224
|
+
switch (e.data.event) {
|
|
225
|
+
case "viewport": {
|
|
226
|
+
const { width, height, iframeId: id } = e.data;
|
|
227
|
+
if (!this.iframes.get(id)) {
|
|
228
|
+
const error = `Cannot find iframe with id ${id}`;
|
|
229
|
+
channel.postMessage({
|
|
230
|
+
event: "viewport:fail",
|
|
231
|
+
iframeId: id,
|
|
232
|
+
error
|
|
233
|
+
});
|
|
234
|
+
await client.rpc.onUnhandledError({
|
|
235
|
+
name: "Teardown Error",
|
|
236
|
+
message: error
|
|
237
|
+
}, "Teardown Error");
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
await setIframeViewport(width, height);
|
|
241
|
+
channel.postMessage({
|
|
242
|
+
event: "viewport:done",
|
|
243
|
+
iframeId: id
|
|
244
|
+
});
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
default:
|
|
248
|
+
if (typeof e.data.event === "string" && e.data.event.startsWith("response:")) break;
|
|
249
|
+
await client.rpc.onUnhandledError({
|
|
250
|
+
name: "Unexpected Event",
|
|
251
|
+
message: `Unexpected event: ${e.data.event}`
|
|
252
|
+
}, "Unexpected Event");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async sendEventToIframe(event) {
|
|
256
|
+
const iframe = this.iframes.get(event.iframeId);
|
|
257
|
+
if (!iframe) throw new Error(`Cannot find iframe with id ${event.iframeId}`);
|
|
258
|
+
let events = this.iframeEvents.get(iframe);
|
|
259
|
+
if (!events) {
|
|
260
|
+
events = /* @__PURE__ */ new Set();
|
|
261
|
+
this.iframeEvents.set(iframe, events);
|
|
262
|
+
}
|
|
263
|
+
events.add(event.event);
|
|
264
|
+
channel.postMessage(event);
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
const cleanupEvents = () => {
|
|
267
|
+
channel.removeEventListener("message", onReceived);
|
|
268
|
+
this.eventTarget.removeEventListener("iframeerror", onError);
|
|
269
|
+
};
|
|
270
|
+
function onReceived(e) {
|
|
271
|
+
if (e.data.iframeId === event.iframeId && e.data.event === `response:${event.event}`) {
|
|
272
|
+
resolve();
|
|
273
|
+
cleanupEvents();
|
|
274
|
+
events.delete(event.event);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function onError(e) {
|
|
278
|
+
reject(e.detail);
|
|
279
|
+
cleanupEvents();
|
|
280
|
+
events.delete(event.event);
|
|
281
|
+
}
|
|
282
|
+
this.eventTarget.addEventListener("iframeerror", onError);
|
|
283
|
+
channel.addEventListener("message", onReceived);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
var orchestrator = new IframeOrchestrator();
|
|
288
|
+
(/* @__PURE__ */ getBrowserState()).orchestrator = orchestrator;
|
|
289
|
+
async function getContainer(config) {
|
|
290
|
+
if (config.browser.ui) {
|
|
291
|
+
const element = document.querySelector("#tester-ui");
|
|
292
|
+
if (!element) return new Promise((resolve) => {
|
|
293
|
+
queueMicrotask(() => {
|
|
294
|
+
resolve(getContainer(config));
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
return element;
|
|
298
|
+
}
|
|
299
|
+
return document.querySelector("#vitest-tester");
|
|
300
|
+
}
|
|
301
|
+
function generateFileId(file) {
|
|
302
|
+
const config = getConfig();
|
|
303
|
+
return generateFileHash(relative(config.root, file), config.name, {
|
|
304
|
+
typecheck: config.pool === "typescript",
|
|
305
|
+
__vitest_label__: config.mergeReportsLabel
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function generateFileHash(file, projectName, meta) {
|
|
309
|
+
return generateHash([
|
|
310
|
+
file,
|
|
311
|
+
projectName || "",
|
|
312
|
+
(meta === null || meta === void 0 ? void 0 : meta.typecheck) ? "__typecheck__" : "",
|
|
313
|
+
(meta === null || meta === void 0 ? void 0 : meta.__vitest_label__) || ""
|
|
314
|
+
].join("\0"));
|
|
315
|
+
}
|
|
316
|
+
function generateHash(str) {
|
|
317
|
+
let hash = 0;
|
|
318
|
+
if (str.length === 0) return `${hash}`;
|
|
319
|
+
for (let i = 0; i < str.length; i++) {
|
|
320
|
+
const char = str.charCodeAt(i);
|
|
321
|
+
hash = (hash << 5) - hash + char;
|
|
322
|
+
hash = hash & hash;
|
|
323
|
+
}
|
|
324
|
+
return `${hash}`;
|
|
325
|
+
}
|
|
326
|
+
async function setIframeViewport(width, height) {
|
|
327
|
+
const ui = /* @__PURE__ */ getUiAPI();
|
|
328
|
+
if (ui) await ui.setIframeViewport(width, height);
|
|
329
|
+
else {
|
|
330
|
+
document.body.style.setProperty("--viewport-width", `${width}px`);
|
|
331
|
+
document.body.style.setProperty("--viewport-height", `${height}px`);
|
|
332
|
+
await client.rpc.triggerCommand((/* @__PURE__ */ getBrowserState()).sessionId, "__vitest_viewport", void 0, [{
|
|
333
|
+
width,
|
|
334
|
+
height
|
|
335
|
+
}]);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function debug(...args) {
|
|
339
|
+
const debug = getConfig().env.VITEST_BROWSER_DEBUG;
|
|
340
|
+
if (debug && debug !== "false") client.rpc.debug(...args.map(String));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
//#endregion
|