@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.
@@ -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 = Promise.allSettled(
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 result = sourceMaps.get(file.filepath) || await rpc$2().getBrowserFileSourceMap(file.filepath);
1658
- if (!result) {
1705
+ const traceMap = await getTraceMap(file.filepath, sourceMaps);
1706
+ if (!traceMap) {
1659
1707
  return null;
1660
1708
  }
1661
- const traceMap = new TraceMap(result);
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
  }
@@ -20,6 +20,7 @@
20
20
  wrapModule,
21
21
  wrapDynamicImport: wrapModule,
22
22
  moduleCache,
23
+ cleanups: [],
23
24
  config: { __VITEST_CONFIG__ },
24
25
  viteConfig: { __VITEST_VITE_CONFIG__ },
25
26
  type: { __VITEST_TYPE__ },
@@ -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-CuTjqoE1.js"></script>
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-CrBz0KXk.js"></script>
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;