@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.
Files changed (31) hide show
  1. package/context.d.ts +8 -0
  2. package/dist/client/.vite/manifest.json +8 -8
  3. package/dist/client/__vitest__/assets/index-B2jbgabi.js +139 -0
  4. package/dist/client/__vitest__/assets/index-CEyXAB_I.css +1 -0
  5. package/dist/client/__vitest__/index.html +2 -2
  6. package/dist/client/__vitest_browser__/defineProperty-C3k2g8Sk.js +267 -0
  7. package/dist/client/__vitest_browser__/orchestrator-B44yH1M4.js +343 -0
  8. package/dist/client/__vitest_browser__/rrweb-snapshot-iZCFA2to.js +4388 -0
  9. package/dist/client/__vitest_browser__/tester-kAU8uxhk.js +5086 -0
  10. package/dist/client/orchestrator.html +2 -2
  11. package/dist/client/tester/tester.html +2 -2
  12. package/dist/client/tester/trace.d.ts +9 -6
  13. package/dist/client.js +10 -1
  14. package/dist/context.js +53 -23
  15. package/dist/expect-element.js +30 -30
  16. package/dist/index.js +153 -33
  17. package/dist/locators-CPBpJv8y.js +5 -0
  18. package/dist/locators.js +1 -1
  19. package/dist/shared/screenshotMatcher/types.d.ts +2 -1
  20. package/dist/state.js +64 -14
  21. package/dist/types.d.ts +7 -2
  22. package/jest-dom.d.ts +26 -6
  23. package/matchers.d.ts +2 -1
  24. package/package.json +7 -7
  25. package/dist/client/__vitest__/assets/index-Cd6On2Pm.js +0 -136
  26. package/dist/client/__vitest__/assets/index-Dw9P28Qt.css +0 -1
  27. package/dist/client/__vitest_browser__/orchestrator-BfoS0x4w.js +0 -411
  28. package/dist/client/__vitest_browser__/rrweb-snapshot-xhvrgOHx.js +0 -5476
  29. package/dist/client/__vitest_browser__/tester-BJtW9QqZ.js +0 -5588
  30. package/dist/client/__vitest_browser__/utils-Nd8hqrhP.js +0 -189
  31. 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