vitest 5.0.0-beta.4 → 5.0.0-beta.6

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 (58) hide show
  1. package/LICENSE.md +0 -17
  2. package/dist/browser.d.ts +8 -6
  3. package/dist/browser.js +5 -3
  4. package/dist/chunks/artifact.BNnEI1sJ.js +2770 -0
  5. package/dist/chunks/{base.BEGVMQrS.js → base.BKfXyFN7.js} +12 -12
  6. package/dist/chunks/browser.d.DoM6vBOs.d.ts +64 -0
  7. package/dist/chunks/{cac.CyXAEMkE.js → cac.DKrA6qEB.js} +15 -7
  8. package/dist/chunks/{cli-api.DJMXq34b.js → cli-api.cZcvaUFI.js} +491 -852
  9. package/dist/chunks/config.d.CKWK3nld.d.ts +2920 -0
  10. package/dist/chunks/{console.B3IRP8fX.js → console.DjVIMaXT.js} +4 -4
  11. package/dist/chunks/{coverage.d.g2xbl2sP.d.ts → coverage.d.CCNrKR65.d.ts} +2 -0
  12. package/dist/chunks/{creator.D66cVXYh.js → creator.C4nK7Krt.js} +19 -4
  13. package/dist/chunks/{defaults.CUUnbOrq.js → defaults.B_pFOTYy.js} +1 -1
  14. package/dist/chunks/{globals.BuY-yD0m.js → globals.CzUA1xn8.js} +12 -12
  15. package/dist/chunks/{index.CcluKS59.js → index.B4UTDegh.js} +6 -6
  16. package/dist/chunks/{nativeModuleRunner.BOeMnHl4.js → index.CesbTg1C.js} +3 -34
  17. package/dist/chunks/{index.CE58PZNH.js → index.D6NDM5O1.js} +515 -121
  18. package/dist/chunks/{index.nQFVd50u.js → index.DNRmy2jU.js} +32 -10
  19. package/dist/chunks/{index.og1WyBLx.js → index.Djaij2xr.js} +660 -58
  20. package/dist/chunks/{init-forks.Ce3vGWgL.js → init-forks.BG4bCDny.js} +1 -1
  21. package/dist/chunks/{init-threads.8e1OLv5v.js → init-threads.DLtIP5Qq.js} +1 -1
  22. package/dist/chunks/{init.6qx-LaHs.js → init.DTAQFCjl.js} +10 -6
  23. package/dist/chunks/{nativeModuleMocker.DDZfQXLs.js → nativeModuleMocker.BKtCThoO.js} +2 -3
  24. package/dist/chunks/nativeModuleRunner.WlMdOB52.js +36 -0
  25. package/dist/chunks/{node.CwFbQqI1.js → node.C9I1sbE9.js} +1 -2
  26. package/dist/chunks/{plugin.d.B7MTG_Fe.d.ts → plugin.d.Bpge8Jye.d.ts} +94 -37
  27. package/dist/chunks/{rpc.DFRWVnRh.js → rpc.DZEh5xnQ.js} +1 -1
  28. package/dist/chunks/{rpc.d.OQ_EZi1Z.d.ts → rpc.d.C6-dGZ2f.d.ts} +1 -2
  29. package/dist/chunks/{setup-common.DdEF_hkE.js → setup-common.ZNdeu5wa.js} +2 -2
  30. package/dist/chunks/{global.d.BtKPuz2X.d.ts → task-utils.d.mMDXJF_I.d.ts} +30 -89
  31. package/dist/chunks/{utils.BX5Fg8C4.js → utils.DYj33du9.js} +1 -17
  32. package/dist/chunks/{vm.Bu7mmcZq.js → vm.CeHWcIrU.js} +4 -4
  33. package/dist/chunks/{worker.d.yR22cs6X.d.ts → worker.d.JES7ffPK.d.ts} +3 -3
  34. package/dist/cli.js +1 -1
  35. package/dist/config.cjs +1 -1
  36. package/dist/config.d.ts +12 -13
  37. package/dist/config.js +1 -1
  38. package/dist/index.d.ts +416 -21
  39. package/dist/index.js +9 -9
  40. package/dist/module-evaluator.d.ts +7 -4
  41. package/dist/module-evaluator.js +7 -0
  42. package/dist/node.d.ts +31 -18
  43. package/dist/node.js +22 -16
  44. package/dist/runtime.d.ts +9 -3
  45. package/dist/runtime.js +4 -5
  46. package/dist/task-utils.js +296 -0
  47. package/dist/worker.d.ts +7 -6
  48. package/dist/worker.js +15 -14
  49. package/dist/workers/forks.js +16 -15
  50. package/dist/workers/runVmTests.js +10 -10
  51. package/dist/workers/threads.js +16 -15
  52. package/dist/workers/vmForks.js +8 -8
  53. package/dist/workers/vmThreads.js +8 -8
  54. package/package.json +18 -19
  55. package/dist/chunks/browser.d.BGxB4Xum.d.ts +0 -878
  56. package/dist/chunks/config.d.DXq1aBpy.d.ts +0 -244
  57. package/dist/chunks/environment.d-DOJxxZV9.d.DOJxxZV9.d.ts +0 -17
  58. package/dist/chunks/general.d.DFAHgpC2.d.ts +0 -247
@@ -1,33 +1,31 @@
1
1
  import * as fs from 'node:fs';
2
2
  import fs__default, { promises, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, statSync } from 'node:fs';
3
- import { join, dirname, basename, isAbsolute, resolve as resolve$1, relative, normalize, extname } from 'pathe';
3
+ import { join, dirname, basename, isAbsolute, resolve as resolve$1, relative, extname, normalize } from 'pathe';
4
4
  import { r as resolveCoverageProviderModule, C as CoverageProviderMap } from './coverage.CTzCuANN.js';
5
5
  import path, { resolve as resolve$2 } from 'node:path';
6
- import { unique, createDefer, slash, shuffle, toArray, cleanUrl, withTrailingSlash, wrapId, isExternalUrl, unwrapId, isPrimitive, noop as noop$1, deepMerge, nanoid, deepClone, sanitizeFilePath, notNullish } from '@vitest/utils/helpers';
7
- import { a as any, p as prompt } from './index.og1WyBLx.js';
8
- import { r as resolveModule, i as isPackageExists, N as NativeModuleRunner } from './nativeModuleRunner.BOeMnHl4.js';
6
+ import { unique, createDefer, slash, cleanUrl, withTrailingSlash, wrapId, isExternalUrl, unwrapId, isPrimitive, toArray, noop as noop$1, deepMerge, nanoid, deepClone, sanitizeFilePath, notNullish } from '@vitest/utils/helpers';
7
+ import { i as isPackageExists, r as resolveModule } from './index.CesbTg1C.js';
9
8
  import * as vite from 'vite';
10
- import { parseAst, searchForWorkspaceRoot, mergeConfig, fetchModule, version, createServer, isFileLoadingAllowed, normalizePath as normalizePath$1, isRunnableDevEnvironment } from 'vite';
11
- import { c as configFiles, d as defaultBrowserPort, b as defaultInspectPort, a as defaultPort, A as API_PATH } from './constants.-juJ8b_4.js';
9
+ import { parseAst, searchForWorkspaceRoot, fetchModule, version, createServer, isFileLoadingAllowed, normalizePath as normalizePath$1, isRunnableDevEnvironment, mergeConfig } from 'vite';
10
+ import { h as hash, b as resolveCoverageReporters, A as API_TOKEN_FILE, c as isBrowserEnabled, r as resolveConfig$1, R as RandomSequencer, a as resolveApiServerConfig, f as findConfigFile, p as prompt } from './index.Djaij2xr.js';
12
11
  import * as nodeos from 'node:os';
13
12
  import nodeos__default, { tmpdir, hostname } from 'node:os';
14
- import { getTests, createFileTask as createFileTask$1, createTaskName, validateTags, calculateSuiteHash, hasFailed, generateFileHash, limitConcurrency, getTestName, getSuites, getTasks, getFullName, createTagsFilter, someTasksAreOnly, interpretTaskModes, isTestCase } from '@vitest/runner/utils';
15
13
  import { serializeValue } from '@vitest/utils/serialize';
16
- import { v as version$1 } from './cac.CyXAEMkE.js';
14
+ import { v as version$1 } from './cac.DKrA6qEB.js';
17
15
  import { distDir, rootDir } from '../path.js';
16
+ import { w as validateTags, x as limitConcurrency, y as createTagsFilter } from './artifact.BNnEI1sJ.js';
17
+ import { N as NativeModuleRunner } from './nativeModuleRunner.WlMdOB52.js';
18
+ import { createFileTask as createFileTask$1, createTaskName, calculateSuiteHash, convertTasksToEvents, hasFailed, generateFileHash, getTestName, hasFailedSnapshot, getTests, getSuites, getTasks, getFullName, someTasksAreOnly, interpretTaskModes } from '../task-utils.js';
18
19
  import { Traces } from '../traces.js';
19
20
  import { createDebug } from 'obug';
20
- import crypto, { createHash } from 'node:crypto';
21
21
  import { rm, readFile, writeFile, rename, stat, unlink, mkdir, readdir, copyFile } from 'node:fs/promises';
22
22
  import c from 'tinyrainbow';
23
- import url, { pathToFileURL, fileURLToPath } from 'node:url';
24
- import { isDynamicPattern, glob } from 'tinyglobby';
25
- import { c as configDefaults, e as benchmarkConfigDefaults, a as coverageConfigDefaults } from './defaults.CUUnbOrq.js';
26
- import { a as isTTY, b as isWindows, i as isForceColor } from './env.BKKtU2WC.js';
27
- import { w as withLabel, t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatProjectName, a as formatTimeString, b as taskFail, s as separator, c as F_CHECK, g as F_DOWN_RIGHT, h as getStateSymbol, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.DKODp04v.js';
28
- import { isAgent, isCI, provider } from 'std-env';
23
+ import crypto, { createHash } from 'node:crypto';
29
24
  import module$1, { createRequire, builtinModules, isBuiltin as isBuiltin$1 } from 'node:module';
25
+ import url, { fileURLToPath, pathToFileURL } from 'node:url';
30
26
  import pm from 'picomatch';
27
+ import { glob, isDynamicPattern } from 'tinyglobby';
28
+ import { a as coverageConfigDefaults, c as configDefaults } from './defaults.B_pFOTYy.js';
31
29
  import { VitestModuleEvaluator } from '#module-evaluator';
32
30
  import { ModuleRunner } from 'vite/module-runner';
33
31
  import { Console } from 'node:console';
@@ -39,6 +37,8 @@ import { performance as performance$1 } from 'node:perf_hooks';
39
37
  import { x } from 'tinyexec';
40
38
  import { positionToOffset, lineSplitRE } from '@vitest/utils/offset';
41
39
  import { defaultStackIgnorePatterns, parseErrorStacktrace, parseStacktrace } from '@vitest/utils/source-map';
40
+ import { t as truncateString, e as errorBanner, F as F_POINTER, d as divider, f as formatProjectName, w as withLabel, a as formatTimeString, b as taskFail, s as separator, c as F_CHECK, g as F_DOWN_RIGHT, h as getStateSymbol, r as renderSnapshotSummary, p as padSummaryTitle, i as getStateString$1, j as formatTime, k as countTestErrors, l as F_TREE_NODE_END, m as F_TREE_NODE_MIDDLE, n as noun, o as F_RIGHT } from './utils.DKODp04v.js';
41
+ import { a as isTTY, b as isWindows, i as isForceColor } from './env.BKKtU2WC.js';
42
42
  import { isatty } from 'node:tty';
43
43
  import EventEmitter$1, { EventEmitter } from 'node:events';
44
44
  import { c as createBirpc } from './index.Chj8NDwU.js';
@@ -47,7 +47,7 @@ import { fork } from 'node:child_process';
47
47
  import { Worker } from 'node:worker_threads';
48
48
  import require$$0$3 from 'events';
49
49
  import require$$1$1 from 'https';
50
- import require$$2 from 'http';
50
+ import require$$2$1 from 'http';
51
51
  import require$$3 from 'net';
52
52
  import require$$4 from 'tls';
53
53
  import require$$1 from 'crypto';
@@ -55,6 +55,9 @@ import require$$0$2 from 'stream';
55
55
  import require$$7 from 'url';
56
56
  import require$$0 from 'zlib';
57
57
  import require$$0$1 from 'buffer';
58
+ import require$$2 from 'util';
59
+ import { A as API_PATH, c as configFiles, d as defaultBrowserPort, a as defaultPort } from './constants.-juJ8b_4.js';
60
+ import { isAgent } from 'std-env';
58
61
  import MagicString from 'magic-string';
59
62
  import { hoistMocksPlugin, automockPlugin } from '@vitest/mocker/node';
60
63
  import { KNOWN_ASSET_RE } from '@vitest/utils/constants';
@@ -177,76 +180,6 @@ function createLocationsIndexMap(source) {
177
180
  return map;
178
181
  }
179
182
 
180
- function hasFailedSnapshot(suite) {
181
- return getTests(suite).some((s) => {
182
- return s.result?.errors?.some((e) => typeof e?.message === "string" && e.message.match(/Snapshot .* mismatched/));
183
- });
184
- }
185
- function convertTasksToEvents(file, onTask) {
186
- const packs = [];
187
- const events = [];
188
- function visit(suite) {
189
- onTask?.(suite);
190
- packs.push([
191
- suite.id,
192
- suite.result,
193
- suite.meta
194
- ]);
195
- events.push([
196
- suite.id,
197
- "suite-prepare",
198
- void 0
199
- ]);
200
- suite.tasks.forEach((task) => {
201
- if (task.type === "suite") visit(task);
202
- else {
203
- onTask?.(task);
204
- if (suite.mode !== "skip" && suite.mode !== "todo") {
205
- packs.push([
206
- task.id,
207
- task.result,
208
- task.meta
209
- ]);
210
- events.push([
211
- task.id,
212
- "test-prepare",
213
- void 0
214
- ]);
215
- task.annotations.forEach((annotation) => {
216
- events.push([
217
- task.id,
218
- "test-annotation",
219
- { annotation }
220
- ]);
221
- });
222
- task.artifacts.forEach((artifact) => {
223
- events.push([
224
- task.id,
225
- "test-artifact",
226
- { artifact }
227
- ]);
228
- });
229
- events.push([
230
- task.id,
231
- "test-finished",
232
- void 0
233
- ]);
234
- }
235
- }
236
- });
237
- events.push([
238
- suite.id,
239
- "suite-finished",
240
- void 0
241
- ]);
242
- }
243
- visit(file);
244
- return {
245
- packs,
246
- events
247
- };
248
- }
249
-
250
183
  // src/vlq.ts
251
184
  var comma = ",".charCodeAt(0);
252
185
  var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -1567,17 +1500,29 @@ class BrowserSessions {
1567
1500
  this.sessions.delete(sessionId);
1568
1501
  }
1569
1502
  createSession(sessionId, project, pool, options) {
1570
- // this promise only waits for the WS connection with the orchestrator to be established
1503
+ // this promise waits until the orchestrator is ready to accept RPC calls
1571
1504
  const defer = createDefer();
1505
+ let isConnected = false;
1506
+ let isReady = false;
1572
1507
  const timeout = setTimeout(() => {
1573
1508
  defer.reject(/* @__PURE__ */ new Error(`Failed to connect to the browser session "${sessionId}" [${project.name}] within the timeout.`));
1574
1509
  }, project.vitest.config.browser.connectTimeout ?? 6e4).unref();
1510
+ const resolveIfReady = () => {
1511
+ if (!isConnected || !isReady) return;
1512
+ defer.resolve();
1513
+ clearTimeout(timeout);
1514
+ };
1575
1515
  this.sessions.set(sessionId, {
1576
1516
  project,
1577
1517
  otelCarrier: options?.otelCarrier,
1518
+ concurrencyId: 0,
1578
1519
  connected: () => {
1579
- defer.resolve();
1580
- clearTimeout(timeout);
1520
+ isConnected = true;
1521
+ resolveIfReady();
1522
+ },
1523
+ ready: () => {
1524
+ isReady = true;
1525
+ resolveIfReady();
1581
1526
  },
1582
1527
  fail: (error) => {
1583
1528
  defer.resolve();
@@ -1589,8 +1534,6 @@ class BrowserSessions {
1589
1534
  }
1590
1535
  }
1591
1536
 
1592
- const hash = crypto.hash ?? ((algorithm, data, outputEncoding) => crypto.createHash(algorithm).update(data).digest(outputEncoding));
1593
-
1594
1537
  class FilesStatsCache {
1595
1538
  cache = /* @__PURE__ */ new Map();
1596
1539
  getStats(key) {
@@ -2151,597 +2094,6 @@ function tryStatSync(file) {
2151
2094
  } catch {}
2152
2095
  }
2153
2096
 
2154
- function getWorkersCountByPercentage(percent) {
2155
- const maxWorkersCount = nodeos__default.availableParallelism?.() ?? nodeos__default.cpus().length;
2156
- const workersCountByPercentage = Math.round(Number.parseInt(percent) / 100 * maxWorkersCount);
2157
- return Math.max(1, Math.min(maxWorkersCount, workersCountByPercentage));
2158
- }
2159
-
2160
- class BaseSequencer {
2161
- ctx;
2162
- constructor(ctx) {
2163
- this.ctx = ctx;
2164
- }
2165
- // async so it can be extended by other sequencers
2166
- async shard(files) {
2167
- const { config } = this.ctx;
2168
- const { index, count } = config.shard;
2169
- const [shardStart, shardEnd] = this.calculateShardRange(files.length, index, count);
2170
- return [...files].map((spec) => {
2171
- const specPath = resolve$1(slash(config.root), slash(spec.moduleId))?.slice(config.root.length);
2172
- return {
2173
- spec,
2174
- hash: hash("sha1", specPath, "hex")
2175
- };
2176
- }).sort((a, b) => a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0).slice(shardStart, shardEnd).map(({ spec }) => spec);
2177
- }
2178
- // async so it can be extended by other sequencers
2179
- async sort(files) {
2180
- const cache = this.ctx.cache;
2181
- return [...files].sort((a, b) => {
2182
- // "sequence.groupOrder" is higher priority
2183
- const groupOrderDiff = a.project.config.sequence.groupOrder - b.project.config.sequence.groupOrder;
2184
- if (groupOrderDiff !== 0) return groupOrderDiff;
2185
- // Projects run sequential
2186
- if (a.project.name !== b.project.name) return a.project.name < b.project.name ? -1 : 1;
2187
- // Isolated run first
2188
- if (a.project.config.isolate && !b.project.config.isolate) return -1;
2189
- if (!a.project.config.isolate && b.project.config.isolate) return 1;
2190
- const keyA = `${a.project.name}:${relative(this.ctx.config.root, a.moduleId)}`;
2191
- const keyB = `${b.project.name}:${relative(this.ctx.config.root, b.moduleId)}`;
2192
- const aState = cache.getFileTestResults(keyA);
2193
- const bState = cache.getFileTestResults(keyB);
2194
- if (!aState || !bState) {
2195
- const statsA = cache.getFileStats(keyA);
2196
- const statsB = cache.getFileStats(keyB);
2197
- // run unknown first
2198
- if (!statsA || !statsB) return !statsA && statsB ? -1 : !statsB && statsA ? 1 : 0;
2199
- // run larger files first
2200
- return statsB.size - statsA.size;
2201
- }
2202
- // run failed first
2203
- if (aState.failed && !bState.failed) return -1;
2204
- if (!aState.failed && bState.failed) return 1;
2205
- // run longer first
2206
- return bState.duration - aState.duration;
2207
- });
2208
- }
2209
- // Calculate distributed shard range [start, end] distributed equally
2210
- calculateShardRange(filesCount, index, count) {
2211
- const baseShardSize = Math.floor(filesCount / count);
2212
- const remainderTestFilesCount = filesCount % count;
2213
- if (remainderTestFilesCount >= index) {
2214
- const shardSize = baseShardSize + 1;
2215
- return [shardSize * (index - 1), shardSize * index];
2216
- }
2217
- const shardStart = remainderTestFilesCount * (baseShardSize + 1) + (index - remainderTestFilesCount - 1) * baseShardSize;
2218
- return [shardStart, shardStart + baseShardSize];
2219
- }
2220
- }
2221
-
2222
- class RandomSequencer extends BaseSequencer {
2223
- async sort(files) {
2224
- const { sequence } = this.ctx.config;
2225
- return shuffle(files, sequence.seed);
2226
- }
2227
- }
2228
-
2229
- function resolvePath(path, root) {
2230
- // local-pkg (mlly)'s resolveModule("./file", { paths: ["/some/root"] }) tries
2231
- // /some/file
2232
- // /some/file.js
2233
- // /some/root/file
2234
- // /some/root/file.js
2235
- // etc.
2236
- // but we don't want to resolve files from parent directories,
2237
- // so we ensure passing "/" suffix such as "/some/root/"
2238
- // https://github.com/unjs/mlly/blob/401d42983f6f3a9112658d67b0a92ba4fb1d7efa/src/resolve.ts#L104-L110
2239
- return normalize(/* @__PURE__ */ resolveModule(path, { paths: [join(root, "/")] }) ?? resolve$1(root, path));
2240
- }
2241
- function parseInspector(inspect) {
2242
- if (typeof inspect === "boolean" || inspect === void 0) return {};
2243
- if (typeof inspect === "number") return { port: inspect };
2244
- if (inspect.match(/https?:\//)) throw new Error(`Inspector host cannot be a URL. Use "host:port" instead of "${inspect}"`);
2245
- const [host, port] = inspect.split(":");
2246
- if (!port) return { host };
2247
- return {
2248
- host,
2249
- port: Number(port) || defaultInspectPort
2250
- };
2251
- }
2252
- /**
2253
- * @deprecated Internal function
2254
- */
2255
- function resolveApiServerConfig(options, defaultPort, parentApi, logger) {
2256
- let api;
2257
- if (options.ui && !options.api) api = { port: defaultPort };
2258
- else if (options.api === true) api = { port: defaultPort };
2259
- else if (typeof options.api === "number") api = { port: options.api };
2260
- if (typeof options.api === "object") if (api) {
2261
- if (options.api.port) api.port = options.api.port;
2262
- if (options.api.strictPort) api.strictPort = options.api.strictPort;
2263
- if (options.api.host) api.host = options.api.host;
2264
- } else api = { ...options.api };
2265
- if (api) {
2266
- if (!api.port && !api.middlewareMode) api.port = defaultPort;
2267
- } else api = { middlewareMode: true };
2268
- // if the API server is exposed to network, disable write operations by default
2269
- if (!api.middlewareMode && api.host && api.host !== "localhost" && api.host !== "127.0.0.1") {
2270
- // assigned to browser
2271
- if (parentApi) {
2272
- if (api.allowWrite == null && api.allowExec == null) logger?.error(c.yellow(`${c.yellowBright(" WARNING ")} API server is exposed to network, disabling write and exec operations by default for security reasons. This can cause some APIs to not work as expected. Set \`browser.api.allowExec\` manually to hide this warning. See https://vitest.dev/config/browser/api for more details.`));
2273
- }
2274
- api.allowWrite ??= parentApi?.allowWrite ?? false;
2275
- api.allowExec ??= parentApi?.allowExec ?? false;
2276
- } else {
2277
- api.allowWrite ??= parentApi?.allowWrite ?? true;
2278
- api.allowExec ??= parentApi?.allowExec ?? true;
2279
- }
2280
- return api;
2281
- }
2282
- function resolveInlineWorkerOption(value) {
2283
- if (typeof value === "string" && value.trim().endsWith("%")) return getWorkersCountByPercentage(value);
2284
- else return Number(value);
2285
- }
2286
- // warn only once, check one PER PROCESS, not per instance,
2287
- // that's why it's on a module-level
2288
- let warnedTypeCheck = false;
2289
- function resolveConfig$1(vitest, options, viteConfig) {
2290
- const logger = vitest.logger;
2291
- if (options.dom) {
2292
- if (viteConfig.test?.environment != null && viteConfig.test.environment !== "happy-dom") logger.console.warn(withLabel("yellow", "Vitest", `Your config.test.environment ("${viteConfig.test.environment}") conflicts with --dom flag ("happy-dom"), ignoring "${viteConfig.test.environment}"`));
2293
- options.environment = "happy-dom";
2294
- }
2295
- const resolved = {
2296
- ...configDefaults,
2297
- ...options,
2298
- root: viteConfig.root
2299
- };
2300
- resolved.mode ??= viteConfig.mode ?? "test";
2301
- if (resolved.retry && typeof resolved.retry === "object" && typeof resolved.retry.condition === "function") {
2302
- logger.console.warn(c.yellow("Warning: retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file."));
2303
- resolved.retry = {
2304
- ...resolved.retry,
2305
- condition: void 0
2306
- };
2307
- }
2308
- if (options.pool && typeof options.pool !== "string") {
2309
- resolved.pool = options.pool.name;
2310
- resolved.poolRunner = options.pool;
2311
- }
2312
- if ("poolOptions" in resolved) logger.deprecate("`test.poolOptions` was removed in Vitest 4. All previous `poolOptions` are now top-level options. Please, refer to the migration guide: https://vitest.dev/guide/migration#pool-rework");
2313
- resolved.pool ??= "forks";
2314
- resolved.project = toArray(resolved.project);
2315
- resolved.provide ??= {};
2316
- // shallow copy tags array to avoid mutating user config
2317
- resolved.tags = [...resolved.tags || []];
2318
- const definedTags = /* @__PURE__ */ new Set();
2319
- resolved.tags.forEach((tag) => {
2320
- if (!tag.name || typeof tag.name !== "string") throw new Error(`Each tag defined in "test.tags" must have a "name" property, received: ${JSON.stringify(tag)}`);
2321
- if (definedTags.has(tag.name)) throw new Error(`Tag name "${tag.name}" is already defined in "test.tags". Tag names must be unique.`);
2322
- if (tag.name.match(/\s/)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain spaces.`);
2323
- if (tag.name.match(/([!()*|&])/)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot contain "!", "*", "&", "|", "(", or ")".`);
2324
- if (tag.name.match(/^\s*(and|or|not)\s*$/i)) throw new Error(`Tag name "${tag.name}" is invalid. Tag names cannot be a logical operator like "and", "or", "not".`);
2325
- if (typeof tag.retry === "object" && typeof tag.retry.condition === "function") throw new TypeError(`Tag "${tag.name}": retry.condition function cannot be used inside a config file. Use a RegExp pattern instead, or define the function in your test file.`);
2326
- if (tag.priority != null && (typeof tag.priority !== "number" || tag.priority < 0)) throw new TypeError(`Tag "${tag.name}": priority must be a non-negative number.`);
2327
- definedTags.add(tag.name);
2328
- });
2329
- resolved.name = typeof options.name === "string" ? options.name : options.name?.label || "";
2330
- resolved.color = typeof options.name !== "string" ? options.name?.color : void 0;
2331
- if (resolved.environment === "browser") throw new Error(`Looks like you set "test.environment" to "browser". To enable Browser Mode, use "test.browser.enabled" instead.`);
2332
- resolved.benchmark = {
2333
- ...benchmarkConfigDefaults,
2334
- ...resolved.benchmark
2335
- };
2336
- const inspector = resolved.inspect || resolved.inspectBrk;
2337
- resolved.inspector = {
2338
- ...resolved.inspector,
2339
- ...parseInspector(inspector),
2340
- enabled: !!inspector,
2341
- waitForDebugger: options.inspector?.waitForDebugger ?? !!resolved.inspectBrk
2342
- };
2343
- if (viteConfig.base !== "/") resolved.base = viteConfig.base;
2344
- resolved.clearScreen = resolved.clearScreen ?? viteConfig.clearScreen ?? true;
2345
- if (options.shard) {
2346
- if (resolved.watch) throw new Error("You cannot use --shard option with enabled watch");
2347
- const [indexString, countString] = options.shard.split("/");
2348
- const index = Math.abs(Number.parseInt(indexString, 10));
2349
- const count = Math.abs(Number.parseInt(countString, 10));
2350
- if (Number.isNaN(count) || count <= 0) throw new Error("--shard <count> must be a positive number");
2351
- if (Number.isNaN(index) || index <= 0 || index > count) throw new Error("--shard <index> must be a positive number less then <count>");
2352
- resolved.shard = {
2353
- index,
2354
- count
2355
- };
2356
- }
2357
- if (resolved.standalone && !resolved.watch) throw new Error(`Vitest standalone mode requires --watch`);
2358
- if (resolved.mergeReports && resolved.watch) throw new Error(`Cannot merge reports with --watch enabled`);
2359
- if (resolved.maxWorkers) resolved.maxWorkers = resolveInlineWorkerOption(resolved.maxWorkers);
2360
- if (!(options.fileParallelism ?? true))
2361
- // ignore user config, parallelism cannot be implemented without limiting workers
2362
- resolved.maxWorkers = 1;
2363
- if (resolved.maxConcurrency === 0) {
2364
- logger.console.warn(c.yellow(`The option "maxConcurrency" cannot be set to 0. Using default value ${configDefaults.maxConcurrency} instead.`));
2365
- resolved.maxConcurrency = configDefaults.maxConcurrency;
2366
- }
2367
- if (resolved.inspect || resolved.inspectBrk) {
2368
- if (resolved.maxWorkers !== 1) {
2369
- const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
2370
- throw new Error(`You cannot use ${inspectOption} without "--no-file-parallelism"`);
2371
- }
2372
- }
2373
- // apply browser CLI options only if the config already has the browser config and not disabled manually
2374
- if (vitest._cliOptions.browser && resolved.browser && (resolved.browser.enabled !== false || vitest._cliOptions.browser.enabled)) resolved.browser = mergeConfig(resolved.browser, vitest._cliOptions.browser);
2375
- resolved.browser ??= {};
2376
- const browser = resolved.browser;
2377
- if (browser.enabled) {
2378
- const instances = browser.instances;
2379
- if (!browser.instances) browser.instances = [];
2380
- // use `chromium` by default when the preview provider is specified
2381
- // for a smoother experience. if chromium is not available, it will
2382
- // open the default browser anyway
2383
- if (!browser.instances.length && browser.provider?.name === "preview") browser.instances = [{ browser: "chromium" }];
2384
- if (browser.name && instances?.length) {
2385
- // --browser=chromium filters configs to a single one
2386
- browser.instances = browser.instances.filter((instance) => instance.browser === browser.name);
2387
- // if `instances` were defined, but now they are empty,
2388
- // let's throw an error because the filter is invalid
2389
- if (!browser.instances.length) throw new Error([`"browser.instances" was set in the config, but the array is empty. Define at least one browser config.`, ` The "browser.name" was set to "${browser.name}" which filtered all configs (${instances.map((c) => c.browser).join(", ")}). Did you mean to use another name?`].join(""));
2390
- }
2391
- }
2392
- if (resolved.coverage.enabled && resolved.coverage.provider === "istanbul" && resolved.experimental?.viteModuleRunner === false) throw new Error(`"Istanbul" coverage provider is not compatible with "experimental.viteModuleRunner: false". Please, enable "viteModuleRunner" or switch to "v8" coverage provider.`);
2393
- if (browser.enabled && resolved.detectAsyncLeaks) logger.console.warn(c.yellow("The option \"detectAsyncLeaks\" is not supported in browser mode and will be ignored."));
2394
- const containsChromium = hasBrowserChromium(vitest, resolved);
2395
- const hasOnlyChromium = hasOnlyBrowserChromium(vitest, resolved);
2396
- // Browser-mode "Chromium" only features:
2397
- if (browser.enabled && (!containsChromium || !hasOnlyChromium)) {
2398
- const browserConfig = `
2399
- {
2400
- browser: {
2401
- provider: ${browser.provider?.name || "preview"}(),
2402
- instances: [
2403
- ${(browser.instances || []).map((i) => `{ browser: '${i.browser}' }`).join(",\n ")}
2404
- ],
2405
- },
2406
- }
2407
- `.trim();
2408
- const preferredProvider = !browser.provider?.name || browser.provider.name === "preview" ? "playwright" : browser.provider.name;
2409
- const correctExample = `
2410
- {
2411
- browser: {
2412
- provider: ${preferredProvider}(),
2413
- instances: [
2414
- { browser: '${preferredProvider === "playwright" ? "chromium" : "chrome"}' }
2415
- ],
2416
- },
2417
- }
2418
- `.trim();
2419
- // requires all projects to be chromium
2420
- if (!hasOnlyChromium && resolved.coverage.enabled && resolved.coverage.provider === "v8") {
2421
- const coverageExample = `
2422
- {
2423
- coverage: {
2424
- provider: 'istanbul',
2425
- },
2426
- }
2427
- `.trim();
2428
- throw new Error(`@vitest/coverage-v8 does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or change your coverage provider to:\n${coverageExample}\n`);
2429
- }
2430
- // ignores non-chromium browsers when there is at least one chromium project
2431
- if (!containsChromium && (resolved.inspect || resolved.inspectBrk)) {
2432
- const inspectOption = `--inspect${resolved.inspectBrk ? "-brk" : ""}`;
2433
- throw new Error(`${inspectOption} does not work with\n${browserConfig}\n\nUse either:\n${correctExample}\n\n...or disable ${inspectOption}\n`);
2434
- }
2435
- }
2436
- resolved.coverage.reporter = resolveCoverageReporters(resolved.coverage.reporter);
2437
- if (isAgent) {
2438
- // default to `skipFull` and add `text-summary` reporter when `text` reporter is used on agents
2439
- const text = resolved.coverage.reporter.find(([name]) => name === "text");
2440
- const textSummary = resolved.coverage.reporter.find(([name]) => name === "text-summary");
2441
- if (text) {
2442
- text[1] = {
2443
- skipFull: true,
2444
- ...text[1]
2445
- };
2446
- if (!textSummary) resolved.coverage.reporter.push(["text-summary", {}]);
2447
- }
2448
- }
2449
- if (resolved.coverage.changed === void 0 && resolved.changed !== void 0) resolved.coverage.changed = resolved.changed;
2450
- if (resolved.coverage.enabled && resolved.coverage.reportsDirectory) {
2451
- const reportsDirectory = resolve$1(resolved.root, resolved.coverage.reportsDirectory);
2452
- if (reportsDirectory === resolved.root || reportsDirectory === process.cwd()) throw new Error(`You cannot set "coverage.reportsDirectory" as ${reportsDirectory}. Vitest needs to be able to remove this directory before test run`);
2453
- if (resolved.coverage.htmlDir) resolved.coverage.htmlDir = resolve$1(resolved.root, resolved.coverage.htmlDir);
2454
- // infer default htmlDir based on builtin reporter's html output location
2455
- if (!resolved.coverage.htmlDir) {
2456
- const htmlReporter = resolved.coverage.reporter.find(([name]) => name === "html" || name === "html-spa");
2457
- if (htmlReporter) {
2458
- const [, options] = htmlReporter;
2459
- const subdir = options && typeof options === "object" && "subdir" in options && typeof options.subdir === "string" ? options.subdir : void 0;
2460
- resolved.coverage.htmlDir = resolve$1(reportsDirectory, subdir || ".");
2461
- } else if (resolved.coverage.reporter.find(([name]) => name === "lcov")) resolved.coverage.htmlDir = resolve$1(reportsDirectory, "lcov-report");
2462
- }
2463
- }
2464
- if (resolved.coverage.enabled && resolved.coverage.provider === "custom" && resolved.coverage.customProviderModule) resolved.coverage.customProviderModule = resolvePath(resolved.coverage.customProviderModule, resolved.root);
2465
- resolved.expect ??= {};
2466
- resolved.deps ??= {};
2467
- resolved.deps.moduleDirectories ??= [];
2468
- resolved.deps.optimizer ??= {};
2469
- resolved.deps.optimizer.ssr ??= {};
2470
- resolved.deps.optimizer.ssr.enabled ??= false;
2471
- resolved.deps.optimizer.client ??= {};
2472
- resolved.deps.optimizer.client.enabled ??= false;
2473
- resolved.deps.web ??= {};
2474
- resolved.deps.web.transformAssets ??= true;
2475
- resolved.deps.web.transformCss ??= true;
2476
- resolved.deps.web.transformGlobPattern ??= [];
2477
- resolved.setupFiles = toArray(resolved.setupFiles || []).map((file) => resolvePath(file, resolved.root));
2478
- resolved.globalSetup = toArray(resolved.globalSetup || []).map((file) => resolvePath(file, resolved.root));
2479
- if (resolved.coverage.include) resolved.coverage.include = resolved.coverage.include.map((pattern) => {
2480
- if (isDynamicPattern(pattern)) return pattern;
2481
- // Convert patterns like ["src", "packages/server"] to ["src/**", "packages/server/**"]
2482
- return pattern.endsWith("/") ? `${pattern}**` : `${pattern}/**`;
2483
- });
2484
- // Add hard-coded default coverage exclusions. These cannot be overridden by user config.
2485
- // Override original exclude array for cases where user re-uses same object in test.exclude.
2486
- resolved.coverage.exclude = [
2487
- ...resolved.coverage.exclude,
2488
- ...resolved.setupFiles.map((file) => `${resolved.coverage.allowExternal ? "**/" : ""}${relative(resolved.root, file)}`),
2489
- ...resolved.include.filter((pattern) => !pattern.startsWith("!")),
2490
- resolved.config && slash(resolved.config),
2491
- ...configFiles,
2492
- "**/virtual:*",
2493
- "**/__x00__*",
2494
- "**/node_modules/**"
2495
- ].filter((pattern) => typeof pattern === "string");
2496
- resolved.forceRerunTriggers = [...resolved.forceRerunTriggers, ...resolved.setupFiles];
2497
- if (resolved.cliExclude) resolved.exclude.push(...resolved.cliExclude);
2498
- if (resolved.runner) resolved.runner = resolvePath(resolved.runner, resolved.root);
2499
- resolved.attachmentsDir = resolve$1(resolved.root, resolved.attachmentsDir ?? ".vitest/attachments");
2500
- if (resolved.snapshotEnvironment) resolved.snapshotEnvironment = resolvePath(resolved.snapshotEnvironment, resolved.root);
2501
- resolved.testNamePattern = resolved.testNamePattern ? resolved.testNamePattern instanceof RegExp ? resolved.testNamePattern : new RegExp(resolved.testNamePattern) : void 0;
2502
- if (resolved.snapshotFormat && "plugins" in resolved.snapshotFormat) {
2503
- resolved.snapshotFormat.plugins = [];
2504
- // TODO: support it via separate config (like DiffOptions) or via `Function.toString()`
2505
- if (typeof resolved.snapshotFormat.compareKeys === "function") throw new TypeError(`"snapshotFormat.compareKeys" function is not supported.`);
2506
- }
2507
- const UPDATE_SNAPSHOT = resolved.update || process.env.UPDATE_SNAPSHOT;
2508
- resolved.snapshotOptions = {
2509
- expand: resolved.expandSnapshotDiff ?? false,
2510
- snapshotFormat: resolved.snapshotFormat || {},
2511
- updateSnapshot: UPDATE_SNAPSHOT === "all" || UPDATE_SNAPSHOT === "new" || UPDATE_SNAPSHOT === "none" ? UPDATE_SNAPSHOT : isCI && !UPDATE_SNAPSHOT ? "none" : UPDATE_SNAPSHOT ? "all" : "new",
2512
- resolveSnapshotPath: options.resolveSnapshotPath,
2513
- snapshotEnvironment: null
2514
- };
2515
- resolved.snapshotSerializers ??= [];
2516
- resolved.snapshotSerializers = resolved.snapshotSerializers.map((file) => resolvePath(file, resolved.root));
2517
- resolved.forceRerunTriggers.push(...resolved.snapshotSerializers);
2518
- if (options.resolveSnapshotPath) delete resolved.resolveSnapshotPath;
2519
- resolved.execArgv ??= [];
2520
- resolved.pool ??= "threads";
2521
- if (resolved.pool === "vmForks" || resolved.pool === "vmThreads" || resolved.pool === "typescript") resolved.isolate = false;
2522
- if (process.env.VITEST_MAX_WORKERS) resolved.maxWorkers = Number.parseInt(process.env.VITEST_MAX_WORKERS);
2523
- if (typeof resolved.diff === "string") {
2524
- resolved.diff = resolvePath(resolved.diff, resolved.root);
2525
- resolved.forceRerunTriggers.push(resolved.diff);
2526
- }
2527
- resolved.api = {
2528
- ...resolveApiServerConfig(options, defaultPort),
2529
- token: crypto.randomUUID()
2530
- };
2531
- if (options.related) resolved.related = toArray(options.related).map((file) => resolve$1(resolved.root, file));
2532
- /*
2533
- * Reporters can be defined in many different ways:
2534
- * { reporter: 'json' }
2535
- * { reporter: { onFinish() { method() } } }
2536
- * { reporter: ['json', { onFinish() { method() } }] }
2537
- * { reporter: [[ 'json' ]] }
2538
- * { reporter: [[ 'json' ], 'html'] }
2539
- * { reporter: [[ 'json', { outputFile: 'test.json' } ], 'html'] }
2540
- */
2541
- if (resolved.reporters) if (!Array.isArray(resolved.reporters))
2542
- // Reporter name, e.g. { reporters: 'json' }
2543
- if (typeof resolved.reporters === "string") resolved.reporters = [[resolved.reporters, {}]];
2544
- else resolved.reporters = [resolved.reporters];
2545
- else {
2546
- const reporters = resolved.reporters;
2547
- resolved.reporters = [];
2548
- for (const reporter of reporters) if (Array.isArray(reporter))
2549
- // Reporter with options, e.g. { reporters: [ [ 'json', { outputFile: 'test.json' } ] ] }
2550
- resolved.reporters.push([reporter[0], reporter[1] || {}]);
2551
- else if (typeof reporter === "string")
2552
- // Reporter name in array, e.g. { reporters: ["html", "json"]}
2553
- resolved.reporters.push([reporter, {}]);
2554
- else
2555
- // Inline reporter, e.g. { reporter: [{ onFinish() { method() } }] }
2556
- resolved.reporters.push(reporter);
2557
- }
2558
- // @ts-expect-error "reporter" is from CLI, should be absolute to the running directory
2559
- // it is passed down as "vitest --reporter ../reporter.js"
2560
- const reportersFromCLI = resolved.reporter;
2561
- const cliReporters = toArray(reportersFromCLI || []).map((reporter) => {
2562
- // ./reporter.js || ../reporter.js, but not .reporters/reporter.js
2563
- if (/^\.\.?\//.test(reporter)) return resolve$1(process.cwd(), reporter);
2564
- return reporter;
2565
- });
2566
- if (cliReporters.length) {
2567
- // When CLI reporters are specified, preserve options from config file
2568
- const configReportersMap = /* @__PURE__ */ new Map();
2569
- // Build a map of reporter names to their options from the config
2570
- for (const reporter of resolved.reporters) if (Array.isArray(reporter)) {
2571
- const [reporterName, reporterOptions] = reporter;
2572
- if (typeof reporterName === "string") configReportersMap.set(reporterName, reporterOptions);
2573
- }
2574
- resolved.reporters = Array.from(new Set(toArray(cliReporters))).filter(Boolean).map((reporter) => [reporter, configReportersMap.get(reporter) || {}]);
2575
- }
2576
- resolved.mergeReportsLabel = process.env.VITEST_BLOB_LABEL;
2577
- for (const reporter of resolved.reporters) if (Array.isArray(reporter) && reporter[0] === "blob") {
2578
- const options = reporter[1];
2579
- if (options && typeof options.label === "string") resolved.mergeReportsLabel = options.label;
2580
- }
2581
- if (resolved.changed) resolved.passWithNoTests ??= true;
2582
- resolved.css ??= {};
2583
- if (typeof resolved.css === "object") {
2584
- resolved.css.modules ??= {};
2585
- resolved.css.modules.classNameStrategy ??= "stable";
2586
- }
2587
- if (resolved.cache !== false) {
2588
- if (resolved.cache && typeof resolved.cache.dir === "string") vitest.logger.deprecate(`"cache.dir" is deprecated, use Vite's "cacheDir" instead if you want to change the cache director. Note caches will be written to "cacheDir\/vitest"`);
2589
- resolved.cache = { dir: viteConfig.cacheDir };
2590
- }
2591
- resolved.sequence ??= {};
2592
- if (resolved.sequence.shuffle && typeof resolved.sequence.shuffle === "object") {
2593
- const { files, tests } = resolved.sequence.shuffle;
2594
- resolved.sequence.sequencer ??= files ? RandomSequencer : BaseSequencer;
2595
- resolved.sequence.shuffle = tests;
2596
- }
2597
- if (!resolved.sequence?.sequencer)
2598
- // CLI flag has higher priority
2599
- resolved.sequence.sequencer = resolved.sequence.shuffle ? RandomSequencer : BaseSequencer;
2600
- resolved.sequence.groupOrder ??= 0;
2601
- resolved.sequence.hooks ??= "stack";
2602
- // Set seed if either files or tests are shuffled
2603
- if (resolved.sequence.sequencer === RandomSequencer || resolved.sequence.shuffle) resolved.sequence.seed ??= Date.now();
2604
- resolved.typecheck = {
2605
- ...configDefaults.typecheck,
2606
- ...resolved.typecheck
2607
- };
2608
- resolved.typecheck ??= {};
2609
- resolved.typecheck.enabled ??= false;
2610
- if (resolved.typecheck.enabled && !warnedTypeCheck) {
2611
- warnedTypeCheck = true;
2612
- logger.console.warn(c.yellow("Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest's version when using it."));
2613
- }
2614
- resolved.browser.enabled ??= false;
2615
- resolved.browser.headless ??= isCI;
2616
- if (resolved.browser.isolate) logger.console.warn(c.yellow("`browser.isolate` is deprecated. Use top-level `isolate` instead."));
2617
- resolved.browser.isolate ??= resolved.isolate ?? true;
2618
- resolved.browser.fileParallelism ??= options.fileParallelism ?? true;
2619
- // disable in headless mode by default, and if CI is detected
2620
- resolved.browser.ui ??= resolved.browser.headless === true ? false : !isCI;
2621
- resolved.browser.commands ??= {};
2622
- resolved.browser.detailsPanelPosition ??= "right";
2623
- if (resolved.browser.screenshotDirectory) resolved.browser.screenshotDirectory = resolve$1(resolved.root, resolved.browser.screenshotDirectory);
2624
- if (resolved.inspector.enabled) resolved.browser.trackUnhandledErrors ??= false;
2625
- resolved.browser.viewport ??= {};
2626
- resolved.browser.viewport.width ??= 414;
2627
- resolved.browser.viewport.height ??= 896;
2628
- resolved.browser.locators ??= {};
2629
- resolved.browser.locators.testIdAttribute ??= "data-testid";
2630
- resolved.browser.locators.exact ??= true;
2631
- resolved.browser.locators.errorFormat ??= "all";
2632
- if (typeof resolved.browser.provider === "string") {
2633
- const source = `@vitest/browser-${resolved.browser.provider}`;
2634
- throw new TypeError(`The \`browser.provider\` configuration was changed to accept a factory instead of a string. Add an import of "${resolved.browser.provider}" from "${source}" instead. See: https://vitest.dev/config/browser/provider`);
2635
- }
2636
- const isPreview = resolved.browser.provider?.name === "preview";
2637
- if (!isPreview && resolved.browser.enabled && provider === "stackblitz") throw new Error(`stackblitz environment does not support the ${resolved.browser.provider?.name} provider. Please, use "@vitest/browser-preview" instead.`);
2638
- if (isPreview && resolved.browser.screenshotFailures === true) {
2639
- console.warn(c.yellow([
2640
- `Browser provider "preview" doesn't support screenshots, `,
2641
- `so "browser.screenshotFailures" option is forcefully disabled. `,
2642
- `Set "browser.screenshotFailures" to false or remove it from the config to suppress this warning.`
2643
- ].join("")));
2644
- resolved.browser.screenshotFailures = false;
2645
- } else resolved.browser.screenshotFailures ??= !isPreview && !resolved.browser.ui;
2646
- if (resolved.browser.provider && resolved.browser.provider.options == null) resolved.browser.provider.options = {};
2647
- resolved.browser.api = resolveApiServerConfig(resolved.browser, defaultBrowserPort, resolved.api, logger) || { port: defaultBrowserPort };
2648
- // enable includeTaskLocation by default in UI mode
2649
- if (resolved.browser.enabled) {
2650
- if (resolved.browser.ui) resolved.includeTaskLocation ??= true;
2651
- } else if (resolved.ui) resolved.includeTaskLocation ??= true;
2652
- if (typeof resolved.browser.trace === "string" || !resolved.browser.trace) resolved.browser.trace = { mode: resolved.browser.trace || "off" };
2653
- const traceView = resolved.browser.traceView;
2654
- resolved.browser.traceView = typeof traceView === "object" ? {
2655
- enabled: traceView.enabled ?? false,
2656
- recordCanvas: traceView.recordCanvas ?? false,
2657
- inlineImages: traceView.inlineImages ?? false
2658
- } : {
2659
- enabled: traceView ?? false,
2660
- recordCanvas: false,
2661
- inlineImages: false
2662
- };
2663
- if (resolved.browser.enabled && resolved.browser.traceView.enabled) resolved.browser.detailsPanelPosition = "bottom";
2664
- if (resolved.browser.trace.tracesDir != null) resolved.browser.trace.tracesDir = resolvePath(resolved.browser.trace.tracesDir, resolved.root);
2665
- if (toArray(resolved.reporters).some((reporter) => {
2666
- if (Array.isArray(reporter)) return reporter[0] === "html";
2667
- return false;
2668
- })) resolved.includeTaskLocation ??= true;
2669
- else if (resolved.browser.enabled && resolved.browser.traceView.enabled && !resolved.watch) logger.console.warn(c.yellow(withLabel("yellow", "Vitest", "--browser.traceView is enabled without the HTML reporter.")));
2670
- resolved.server ??= {};
2671
- resolved.server.deps ??= {};
2672
- if (resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP) {
2673
- const userFolder = resolved.server.debug?.dump || process.env.VITEST_DEBUG_DUMP;
2674
- resolved.dumpDir = resolve$1(resolved.root, typeof userFolder === "string" && userFolder !== "true" ? userFolder : ".vitest-dump", resolved.name || "root");
2675
- }
2676
- resolved.testTimeout ??= resolved.browser.enabled ? 15e3 : 5e3;
2677
- resolved.hookTimeout ??= resolved.browser.enabled ? 3e4 : 1e4;
2678
- resolved.experimental ??= {};
2679
- if (resolved.experimental.openTelemetry?.sdkPath) {
2680
- const sdkPath = resolve$1(resolved.root, resolved.experimental.openTelemetry.sdkPath);
2681
- resolved.experimental.openTelemetry.sdkPath = pathToFileURL(sdkPath).toString();
2682
- }
2683
- if (resolved.experimental.openTelemetry?.browserSdkPath) {
2684
- const browserSdkPath = resolve$1(resolved.root, resolved.experimental.openTelemetry.browserSdkPath);
2685
- resolved.experimental.openTelemetry.browserSdkPath = browserSdkPath;
2686
- }
2687
- if (resolved.experimental.fsModuleCachePath) resolved.experimental.fsModuleCachePath = resolve$1(resolved.root, resolved.experimental.fsModuleCachePath);
2688
- resolved.experimental.importDurations ??= {};
2689
- resolved.experimental.importDurations.print ??= false;
2690
- resolved.experimental.importDurations.failOnDanger ??= false;
2691
- if (resolved.experimental.importDurations.limit == null) {
2692
- const shouldCollect = resolved.experimental.importDurations.print || resolved.experimental.importDurations.failOnDanger || resolved.ui;
2693
- resolved.experimental.importDurations.limit = shouldCollect ? 10 : 0;
2694
- }
2695
- resolved.experimental.importDurations.thresholds ??= {};
2696
- resolved.experimental.importDurations.thresholds.warn ??= 100;
2697
- resolved.experimental.importDurations.thresholds.danger ??= 500;
2698
- if (typeof resolved.experimental.vcsProvider === "string" && resolved.experimental.vcsProvider !== "git") resolved.experimental.vcsProvider = resolvePath(resolved.experimental.vcsProvider, resolved.root);
2699
- return resolved;
2700
- }
2701
- function isBrowserEnabled(config) {
2702
- return Boolean(config.browser?.enabled);
2703
- }
2704
- function resolveCoverageReporters(configReporters) {
2705
- // E.g. { reporter: "html" }
2706
- if (!Array.isArray(configReporters)) return [[configReporters, {}]];
2707
- const resolvedReporters = [];
2708
- for (const reporter of configReporters) if (Array.isArray(reporter))
2709
- // E.g. { reporter: [ ["html", { skipEmpty: true }], ["lcov"], ["json", { file: "map.json" }] ]}
2710
- resolvedReporters.push([reporter[0], reporter[1] || {}]);
2711
- else
2712
- // E.g. { reporter: ["html", "json"]}
2713
- resolvedReporters.push([reporter, {}]);
2714
- return resolvedReporters;
2715
- }
2716
- function isChromiumName(provider, name) {
2717
- if (provider === "playwright") return name === "chromium";
2718
- return name === "chrome" || name === "edge";
2719
- }
2720
- function hasBrowserChromium(vitest, config) {
2721
- const browser = config.browser;
2722
- if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
2723
- if (browser.name) return isChromiumName(browser.provider.name, browser.name);
2724
- if (!browser.instances) return false;
2725
- return browser.instances.some((instance) => {
2726
- const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
2727
- // browser config is filtered out
2728
- if (!vitest.matchesProjectFilter(name)) return false;
2729
- return isChromiumName(browser.provider.name, instance.browser);
2730
- });
2731
- }
2732
- function hasOnlyBrowserChromium(vitest, config) {
2733
- const browser = config.browser;
2734
- if (!browser || !browser.provider || browser.provider.name === "preview" || !browser.enabled) return false;
2735
- if (browser.name) return isChromiumName(browser.provider.name, browser.name);
2736
- if (!browser.instances) return false;
2737
- return browser.instances.every((instance) => {
2738
- const name = instance.name || (config.name ? `${config.name} (${instance.browser})` : instance.browser);
2739
- // browser config is filtered out
2740
- if (!vitest.matchesProjectFilter(name)) return true;
2741
- return isChromiumName(browser.provider.name, instance.browser);
2742
- });
2743
- }
2744
-
2745
2097
  const THRESHOLD_KEYS = [
2746
2098
  "lines",
2747
2099
  "functions",
@@ -2766,6 +2118,7 @@ class BaseCoverageProvider {
2766
2118
  coverageFiles = /* @__PURE__ */ new Map();
2767
2119
  pendingPromises = [];
2768
2120
  coverageFilesDirectory;
2121
+ reportsDirectoryLock;
2769
2122
  roots = [];
2770
2123
  changedFiles;
2771
2124
  _initialize(ctx) {
@@ -2791,6 +2144,7 @@ Update your dependencies and make sure the versions match.`));
2791
2144
  const shard = this.ctx.config.shard;
2792
2145
  const tempDirectory = `.tmp${shard ? `-${shard.index}-${shard.count}` : ""}`;
2793
2146
  this.coverageFilesDirectory = resolve$1(this.options.reportsDirectory, tempDirectory);
2147
+ this.reportsDirectoryLock = new ReportsDirectoryLock(resolve$1(this.options.reportsDirectory));
2794
2148
  // If --project filter is set pick only roots of resolved projects
2795
2149
  this.roots = ctx.config.project?.length ? [...new Set(ctx.projects.map((project) => project.config.root))] : [ctx.config.root];
2796
2150
  }
@@ -2802,12 +2156,12 @@ Update your dependencies and make sure the versions match.`));
2802
2156
  const filename = slash(cleanUrl(_filename));
2803
2157
  const cacheHit = this.globCache.get(filename);
2804
2158
  if (cacheHit !== void 0) return cacheHit;
2159
+ const matchingRoot = roots.find((root) => filename.startsWith(`${slash(root)}/`) || filename === slash(root));
2805
2160
  // File outside project root with default allowExternal
2806
- if (this.options.allowExternal === false && roots.every((root) => !filename.startsWith(root))) {
2161
+ if (this.options.allowExternal === false && !matchingRoot) {
2807
2162
  this.globCache.set(filename, false);
2808
2163
  return false;
2809
2164
  }
2810
- const matchingRoot = roots.find((root) => filename.startsWith(`${slash(root)}/`) || filename === slash(root));
2811
2165
  const relativeFilename = matchingRoot ? relative(matchingRoot, filename) : filename;
2812
2166
  if (pm.isMatch(relativeFilename, this.options.exclude, { dot: true })) {
2813
2167
  this.globCache.set(filename, false);
@@ -2854,6 +2208,7 @@ Update your dependencies and make sure the versions match.`));
2854
2208
  return this.options;
2855
2209
  }
2856
2210
  async clean(clean = true) {
2211
+ await this.reportsDirectoryLock.acquire();
2857
2212
  if (clean && existsSync(this.options.reportsDirectory)) await promises.rm(this.options.reportsDirectory, {
2858
2213
  recursive: true,
2859
2214
  force: true,
@@ -2913,10 +2268,14 @@ Update your dependencies and make sure the versions match.`));
2913
2268
  }
2914
2269
  }
2915
2270
  async cleanAfterRun() {
2916
- this.coverageFiles = /* @__PURE__ */ new Map();
2917
- await promises.rm(this.coverageFilesDirectory, { recursive: true });
2918
- // Remove empty reports directory, e.g. when only text-reporter is used
2919
- if (readdirSync(this.options.reportsDirectory).length === 0) await promises.rm(this.options.reportsDirectory, { recursive: true });
2271
+ try {
2272
+ this.coverageFiles = /* @__PURE__ */ new Map();
2273
+ await promises.rm(this.coverageFilesDirectory, { recursive: true });
2274
+ // Remove empty reports directory, e.g. when only text-reporter is used
2275
+ if (readdirSync(this.options.reportsDirectory).length === 0) await promises.rm(this.options.reportsDirectory, { recursive: true });
2276
+ } finally {
2277
+ await this.reportsDirectoryLock.release();
2278
+ }
2920
2279
  }
2921
2280
  async onTestRunStart() {
2922
2281
  if (this.options.changed) try {
@@ -2963,7 +2322,8 @@ Update your dependencies and make sure the versions match.`));
2963
2322
  for (const key of Object.keys(this.options.thresholds)) {
2964
2323
  if (key === "perFile" || key === "autoUpdate" || key === "100" || THRESHOLD_KEYS.includes(key)) continue;
2965
2324
  const glob = key;
2966
- const globThresholds = resolveGlobThresholds(this.options.thresholds[glob]);
2325
+ const globEntry = this.options.thresholds[glob];
2326
+ const globThresholds = resolveGlobThresholds(globEntry);
2967
2327
  const globCoverageMap = this.createCoverageMap();
2968
2328
  const matcher = pm(glob);
2969
2329
  const matchingFiles = files.filter((file) => matcher(relative(this.ctx.config.root, file)));
@@ -2974,7 +2334,8 @@ Update your dependencies and make sure the versions match.`));
2974
2334
  resolvedThresholds.push({
2975
2335
  name: glob,
2976
2336
  coverageMap: globCoverageMap,
2977
- thresholds: globThresholds
2337
+ thresholds: globThresholds,
2338
+ ...resolvePerFile(globEntry)
2978
2339
  });
2979
2340
  }
2980
2341
  // Global threshold is for all files, even if they are included by glob patterns
@@ -2990,7 +2351,8 @@ Update your dependencies and make sure the versions match.`));
2990
2351
  functions: this.options.thresholds?.functions,
2991
2352
  lines: this.options.thresholds?.lines,
2992
2353
  statements: this.options.thresholds?.statements
2993
- }
2354
+ },
2355
+ ...resolvePerFile(this.options.thresholds)
2994
2356
  });
2995
2357
  return resolvedThresholds;
2996
2358
  }
@@ -2998,51 +2360,56 @@ Update your dependencies and make sure the versions match.`));
2998
2360
  * Check collected coverage against configured thresholds. Sets exit code to 1 when thresholds not reached.
2999
2361
  */
3000
2362
  checkThresholds(allThresholds) {
3001
- for (const { coverageMap, thresholds, name } of allThresholds) {
3002
- if (thresholds.branches === void 0 && thresholds.functions === void 0 && thresholds.lines === void 0 && thresholds.statements === void 0) continue;
3003
- // Construct list of coverage summaries where thresholds are compared against
3004
- const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => ({
3005
- file,
3006
- summary: coverageMap.fileCoverageFor(file).toSummary()
3007
- })) : [{
2363
+ for (const { coverageMap, thresholds, perFile, perFileThresholds, name } of allThresholds) {
2364
+ const groups = [];
2365
+ if (!perFile) groups.push({
3008
2366
  file: null,
3009
- summary: coverageMap.getCoverageSummary()
3010
- }];
3011
- // Check thresholds of each summary
3012
- for (const { summary, file } of summaries) for (const thresholdKey of THRESHOLD_KEYS) {
3013
- const threshold = thresholds[thresholdKey];
3014
- if (threshold === void 0) continue;
3015
- /**
3016
- * Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
3017
- * while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
3018
- */
3019
- if (threshold >= 0) {
3020
- const coverage = summary.data[thresholdKey].pct;
3021
- if (coverage < threshold) {
3022
- process.exitCode = 1;
3023
- /**
3024
- * Generate error message based on perFile flag:
3025
- * - ERROR: Coverage for statements (33.33%) does not meet threshold (85%) for src/math.ts
3026
- * - ERROR: Coverage for statements (50%) does not meet global threshold (85%)
3027
- */
3028
- let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet ${name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`} threshold (${threshold}%)`;
3029
- if (this.options.thresholds?.perFile && file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
3030
- this.ctx.logger.error(errorMessage);
3031
- }
3032
- } else {
3033
- const uncovered = summary.data[thresholdKey].total - summary.data[thresholdKey].covered;
3034
- const absoluteThreshold = threshold * -1;
3035
- if (uncovered > absoluteThreshold) {
3036
- process.exitCode = 1;
3037
- /**
3038
- * Generate error message based on perFile flag:
3039
- * - ERROR: Uncovered statements (33) exceed threshold (30) for src/math.ts
3040
- * - ERROR: Uncovered statements (33) exceed global threshold (30)
3041
- */
3042
- let errorMessage = `ERROR: Uncovered ${thresholdKey} (${uncovered}) exceed ${name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`} threshold (${absoluteThreshold})`;
3043
- if (this.options.thresholds?.perFile && file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
3044
- this.ctx.logger.error(errorMessage);
3045
- }
2367
+ thresholds,
2368
+ summary: coverageMap.getCoverageSummary(),
2369
+ name: name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`
2370
+ });
2371
+ if (perFile) for (const file of coverageMap.files().sort()) groups.push({
2372
+ file,
2373
+ thresholds,
2374
+ summary: coverageMap.fileCoverageFor(file).toSummary(),
2375
+ name: name === GLOBAL_THRESHOLDS_KEY ? name : `"${name}"`
2376
+ });
2377
+ if (perFileThresholds) for (const file of coverageMap.files().sort()) groups.push({
2378
+ file,
2379
+ thresholds: perFileThresholds,
2380
+ summary: coverageMap.fileCoverageFor(file).toSummary(),
2381
+ name: "per-file"
2382
+ });
2383
+ for (const group of groups) {
2384
+ if (group.thresholds.branches === void 0 && group.thresholds.functions === void 0 && group.thresholds.lines === void 0 && group.thresholds.statements === void 0) continue;
2385
+ this.reportThresholdViolations(group.thresholds, group.summary, group.file, group.name);
2386
+ }
2387
+ }
2388
+ }
2389
+ reportThresholdViolations(thresholds, summary, file, label) {
2390
+ for (const thresholdKey of THRESHOLD_KEYS) {
2391
+ const threshold = thresholds[thresholdKey];
2392
+ if (threshold === void 0) continue;
2393
+ /**
2394
+ * Positive thresholds are treated as minimum coverage percentages (X means: X% of lines must be covered),
2395
+ * while negative thresholds are treated as maximum uncovered counts (-X means: X lines may be uncovered).
2396
+ */
2397
+ if (threshold >= 0) {
2398
+ const coverage = summary.data[thresholdKey].pct;
2399
+ if (coverage < threshold) {
2400
+ process.exitCode = 1;
2401
+ let errorMessage = `ERROR: Coverage for ${thresholdKey} (${coverage}%) does not meet ${label} threshold (${threshold}%)`;
2402
+ if (file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
2403
+ this.ctx.logger.error(errorMessage);
2404
+ }
2405
+ } else {
2406
+ const uncovered = summary.data[thresholdKey].total - summary.data[thresholdKey].covered;
2407
+ const absoluteThreshold = threshold * -1;
2408
+ if (uncovered > absoluteThreshold) {
2409
+ process.exitCode = 1;
2410
+ let errorMessage = `ERROR: Uncovered ${thresholdKey} (${uncovered}) exceed ${label} threshold (${absoluteThreshold})`;
2411
+ if (file) errorMessage += ` for ${relative("./", file).replace(/\\/g, "/")}`;
2412
+ this.ctx.logger.error(errorMessage);
3046
2413
  }
3047
2414
  }
3048
2415
  }
@@ -3054,8 +2421,11 @@ Update your dependencies and make sure the versions match.`));
3054
2421
  let updatedThresholds = false;
3055
2422
  const config = resolveConfig(configurationFile);
3056
2423
  assertConfigurationModule(config);
3057
- for (const { coverageMap, thresholds, name } of allThresholds) {
3058
- const summaries = this.options.thresholds?.perFile ? coverageMap.files().map((file) => coverageMap.fileCoverageFor(file).toSummary()) : [coverageMap.getCoverageSummary()];
2424
+ for (const { coverageMap, thresholds, name, perFile } of allThresholds) {
2425
+ const summaries = perFile ? coverageMap.files().map((file) => coverageMap.fileCoverageFor(file).toSummary()) : [coverageMap.getCoverageSummary()];
2426
+ // A `perFile` glob may match no files; skip it instead of writing
2427
+ // Infinity thresholds from `Math.min(...[])`.
2428
+ if (summaries.length === 0) continue;
3059
2429
  const thresholdsToUpdate = [];
3060
2430
  for (const key of THRESHOLD_KEYS) {
3061
2431
  const threshold = thresholds[key] ?? 100;
@@ -3065,22 +2435,30 @@ Update your dependencies and make sure the versions match.`));
3065
2435
  */
3066
2436
  if (threshold >= 0) {
3067
2437
  const actual = Math.min(...summaries.map((summary) => summary[key].pct));
3068
- if (actual > threshold) thresholdsToUpdate.push([key, actual]);
2438
+ if (actual > threshold) thresholdsToUpdate.push([
2439
+ key,
2440
+ actual,
2441
+ threshold
2442
+ ]);
3069
2443
  } else {
3070
2444
  const absoluteThreshold = threshold * -1;
3071
2445
  const actual = Math.max(...summaries.map((summary) => summary[key].total - summary[key].covered));
3072
2446
  if (actual < absoluteThreshold) {
3073
2447
  // If everything was covered, set new threshold to 100% (since a threshold of 0 would be considered as 0%)
3074
2448
  const updatedThreshold = actual === 0 ? 100 : actual * -1;
3075
- thresholdsToUpdate.push([key, updatedThreshold]);
2449
+ thresholdsToUpdate.push([
2450
+ key,
2451
+ updatedThreshold,
2452
+ threshold
2453
+ ]);
3076
2454
  }
3077
2455
  }
3078
2456
  }
3079
2457
  if (thresholdsToUpdate.length === 0) continue;
3080
2458
  updatedThresholds = true;
3081
2459
  const thresholdFormatter = typeof this.options.thresholds?.autoUpdate === "function" ? this.options.thresholds?.autoUpdate : (value) => value;
3082
- for (const [threshold, newValue] of thresholdsToUpdate) {
3083
- const formattedValue = thresholdFormatter(newValue);
2460
+ for (const [threshold, newValue, previousValue] of thresholdsToUpdate) {
2461
+ const formattedValue = thresholdFormatter(newValue, previousValue);
3084
2462
  if (name === GLOBAL_THRESHOLDS_KEY) config.test.coverage.thresholds[threshold] = formattedValue;
3085
2463
  else {
3086
2464
  const glob = config.test.coverage.thresholds[name];
@@ -3160,6 +2538,25 @@ Update your dependencies and make sure the versions match.`));
3160
2538
  };
3161
2539
  }
3162
2540
  }
2541
+ function resolvePerFile(thresholds) {
2542
+ if (!thresholds || typeof thresholds !== "object" || !("perFile" in thresholds)) return {
2543
+ perFile: false,
2544
+ perFileThresholds: null
2545
+ };
2546
+ const { perFile } = thresholds;
2547
+ if (perFile === true) return {
2548
+ perFile: true,
2549
+ perFileThresholds: null
2550
+ };
2551
+ if (perFile && typeof perFile === "object") return {
2552
+ perFile: false,
2553
+ perFileThresholds: resolveGlobThresholds(perFile)
2554
+ };
2555
+ return {
2556
+ perFile: false,
2557
+ perFileThresholds: null
2558
+ };
2559
+ }
3163
2560
  /**
3164
2561
  * Narrow down `unknown` glob thresholds to resolved ones
3165
2562
  */
@@ -3226,6 +2623,65 @@ function resolveMergeConfig(mod) {
3226
2623
  if (config) return config;
3227
2624
  }
3228
2625
  }
2626
+ class ReportsDirectoryLock {
2627
+ lockFile;
2628
+ constructor(reportsDirectory) {
2629
+ this.reportsDirectory = reportsDirectory;
2630
+ const hash = createHash("sha256").update(reportsDirectory).digest("hex").slice(0, 16);
2631
+ this.lockFile = resolve$1(tmpdir(), `vitest-coverage-${hash}.lock`);
2632
+ }
2633
+ async acquire() {
2634
+ if (await this.tryWrite()) return;
2635
+ const owner = await this.readOwner();
2636
+ // We already hold the lock for this directory (e.g. watch-mode reruns).
2637
+ if (owner?.pid === process.pid) return;
2638
+ // Another running Vitest owns this directory.
2639
+ if (owner && isProcessAlive(owner.pid)) throw this.inUseError(owner);
2640
+ // The lock was left behind by a process that no longer exists. Reclaim it.
2641
+ await promises.rm(this.lockFile, { force: true });
2642
+ if (!await this.tryWrite()) throw this.inUseError(await this.readOwner());
2643
+ }
2644
+ async release() {
2645
+ if ((await this.readOwner())?.pid === process.pid) await promises.rm(this.lockFile, { force: true });
2646
+ }
2647
+ async tryWrite() {
2648
+ const payload = JSON.stringify({
2649
+ pid: process.pid,
2650
+ reportsDirectory: this.reportsDirectory
2651
+ });
2652
+ try {
2653
+ // `wx` fails with EEXIST if the file already exists, so only one process wins.
2654
+ await promises.writeFile(this.lockFile, payload, { flag: "wx" });
2655
+ return true;
2656
+ } catch (error) {
2657
+ if (error.code === "EEXIST") return false;
2658
+ throw error;
2659
+ }
2660
+ }
2661
+ async readOwner() {
2662
+ try {
2663
+ const owner = JSON.parse(await promises.readFile(this.lockFile, "utf-8"));
2664
+ return typeof owner?.pid === "number" ? owner : null;
2665
+ } catch {
2666
+ return null;
2667
+ }
2668
+ }
2669
+ inUseError(owner) {
2670
+ return /* @__PURE__ */ new Error(`The coverage report directory "${this.reportsDirectory}" is already in use by another Vitest process${owner ? ` (pid ${owner.pid})` : ""}. Running coverage for multiple Vitest processes in the same directory at the same time is not supported, because they would delete each other's reports.\nGive each run its own "coverage.reportsDirectory" (e.g. --coverage.reportsDirectory=coverage-${process.pid}) or run them sequentially.`);
2671
+ }
2672
+ }
2673
+ function isProcessAlive(pid) {
2674
+ try {
2675
+ // Sending signal 0 checks if the process exists without actually killing it:
2676
+ // https://nodejs.org/api/process.html#processkillpid-signal
2677
+ process.kill(pid, 0);
2678
+ return true;
2679
+ } catch (error) {
2680
+ // ESRCH means the process is gone. Treat anything else (e.g. EPERM) as alive
2681
+ // so we never reclaim a lock from a process that is still running.
2682
+ return error.code !== "ESRCH";
2683
+ }
2684
+ }
3229
2685
 
3230
2686
  // this is copy pasted from vite
3231
2687
  function normalizeResolvedIdToUrl(environment, resolvedId) {
@@ -4908,13 +4364,14 @@ class Logger {
4908
4364
  const color = this.ctx.config.watch ? "blue" : "cyan";
4909
4365
  const mode = this.ctx.config.watch ? "DEV" : "RUN";
4910
4366
  this.log(withLabel(color, mode, `v${this.ctx.version} `) + c.gray(this.ctx.config.root));
4911
- // Log seed if either files (RandomSequencer) or tests are shuffled
4912
- if (this.ctx.config.sequence.sequencer === RandomSequencer || this.ctx.config.sequence.shuffle) this.log(PAD + c.gray(`Running tests with seed "${this.ctx.config.sequence.seed}"`));
4367
+ const seed = this.ctx.getSeed();
4368
+ if (seed != null) this.log(PAD + c.gray(`Running tests with seed "${seed}"`));
4913
4369
  if (this.ctx.config.ui) {
4914
4370
  const host = this.ctx.config.api?.host || "localhost";
4915
4371
  const port = this.ctx.vite.config.server.port;
4916
- const base = this.ctx.config.uiBase;
4917
- this.log(PAD + c.dim(c.green(`UI started at http://${host}:${c.bold(port)}${base}`)));
4372
+ const url = new URL(this.ctx.config.uiBase, `http://${host}:${port}`);
4373
+ url.searchParams.set("token", this.ctx.config.api.token);
4374
+ this.log(PAD + c.dim(c.green(`UI started at ${url}`)));
4918
4375
  } else if (this.ctx.config.api?.port) {
4919
4376
  const resolvedUrls = this.ctx.vite.resolvedUrls;
4920
4377
  // workaround for https://github.com/vitejs/vite/issues/15438, it was fixed in vite 5.1
@@ -4926,16 +4383,6 @@ class Logger {
4926
4383
  if (this.ctx.config.standalone) this.log(c.yellow(`\nVitest is running in standalone mode. Edit a test file to rerun tests.\n`));
4927
4384
  else this.log();
4928
4385
  }
4929
- printBrowserBanner(project) {
4930
- if (!project.browser) return;
4931
- const resolvedUrls = project.browser.vite.resolvedUrls;
4932
- const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
4933
- if (!origin) return;
4934
- const output = project.isRootProject() ? "" : formatProjectName(project);
4935
- const provider = project.browser.provider?.name;
4936
- const providerString = provider === "preview" ? "" : ` by ${c.reset(c.bold(provider))}`;
4937
- this.log(c.dim(`${output}Browser runner started${providerString} ${c.dim("at")} ${c.blue(new URL("/__vitest_test__/", origin))}\n`));
4938
- }
4939
4386
  printUnhandledErrors(errors) {
4940
4387
  const errorMessage = c.red(c.bold(`\nVitest caught ${errors.length} unhandled error${errors.length > 1 ? "s" : ""} during the test run.
4941
4388
  This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.`));
@@ -5253,7 +4700,7 @@ class VitestPackageInstaller {
5253
4700
  if (/* @__PURE__ */ isPackageExists(dependency, { paths: [root, __dirname$1] })) return true;
5254
4701
  process.stderr.write(withLabel("red", "MISSING DEPENDENCY", `Cannot find dependency '${dependency}'\n\n`));
5255
4702
  if (!isTTY) return false;
5256
- const { install } = await (await import('./index.og1WyBLx.js').then(function (n) { return n.i; })).default({
4703
+ const { install } = await (await import('./index.Djaij2xr.js').then(function (n) { return n.i; })).default({
5257
4704
  type: "confirm",
5258
4705
  name: "install",
5259
4706
  message: c.reset(`Do you want to install ${c.green(dependency)}?`)
@@ -5365,7 +4812,7 @@ function createBrowserPool(vitest) {
5365
4812
  vitest.onCancel(() => {
5366
4813
  isCancelled = true;
5367
4814
  });
5368
- const initialisedPools = await Promise.all([...groupedFiles.entries()].map(async ([project, files]) => {
4815
+ const initialisedPools = await Promise.all(Array.from(groupedFiles.entries(), async ([project, files]) => {
5369
4816
  await project._initBrowserProvider();
5370
4817
  if (!project.browser) throw new TypeError(`The browser server was not initialized${project.name ? ` for the "${project.name}" project` : ""}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
5371
4818
  if (isCancelled) return;
@@ -5404,7 +4851,7 @@ function createBrowserPool(vitest) {
5404
4851
  return {
5405
4852
  name: "browser",
5406
4853
  async close() {
5407
- await Promise.all([...providers].map((provider) => provider.close()));
4854
+ await Promise.all(Array.from(providers, (provider) => provider.close()));
5408
4855
  vitest._browserSessions.sessionIds.clear();
5409
4856
  providers.clear();
5410
4857
  vitest.projects.forEach((project) => {
@@ -5479,11 +4926,10 @@ class BrowserPool {
5479
4926
  this.project.vitest._browserSessions.sessionIds.add(sessionId);
5480
4927
  const project = this.project.name;
5481
4928
  debug?.("[%s] creating session for %s", sessionId, project);
5482
- let page = this._traces.$(`vitest.browser.open`, {
4929
+ const page = this._traces.$(`vitest.browser.open`, {
5483
4930
  context: this._otel.context,
5484
4931
  attributes: { "vitest.browser.session_id": sessionId }
5485
- }, () => this.openPage(sessionId, { parallel: workerCount > 1 }));
5486
- page = page.then(() => {
4932
+ }, () => this.openPage(sessionId, { parallel: workerCount > 1 })).then(() => {
5487
4933
  // start running tests on the page when it's ready
5488
4934
  this.runNextTest(method, sessionId);
5489
4935
  });
@@ -5499,6 +4945,24 @@ class BrowserPool {
5499
4945
  parallel: options.parallel
5500
4946
  });
5501
4947
  }
4948
+ // stable slot id (1..maxWorkers) assigned to each session/orchestrator on its
4949
+ // first run, exposed to the test runner as both `concurrencyId` and `workerId`.
4950
+ // the id lives on the session, so it is freed when the session disconnects, and
4951
+ // the used set is derived from the live orchestrators, so it stays within maxWorkers
4952
+ getConcurrencyId(sessionId) {
4953
+ const sessions = this.project.vitest._browserSessions;
4954
+ const session = sessions.getSession(sessionId);
4955
+ if (session?.concurrencyId) return session.concurrencyId;
4956
+ const used = /* @__PURE__ */ new Set();
4957
+ for (const id of this.orchestrators.keys()) {
4958
+ const concurrencyId = sessions.getSession(id)?.concurrencyId;
4959
+ if (concurrencyId) used.add(concurrencyId);
4960
+ }
4961
+ let concurrencyId = 1;
4962
+ while (used.has(concurrencyId)) concurrencyId++;
4963
+ if (session) session.concurrencyId = concurrencyId;
4964
+ return concurrencyId;
4965
+ }
5502
4966
  getOrchestrator(sessionId) {
5503
4967
  const orchestrator = this.orchestrators.get(sessionId);
5504
4968
  if (!orchestrator) throw new Error(`Orchestrator not found for session ${sessionId}. This is a bug in Vitest. Please, open a new issue with reproduction.`);
@@ -5535,11 +4999,14 @@ class BrowserPool {
5535
4999
  context: this._otel.context,
5536
5000
  attributes: { "code.file.path": file.filepath }
5537
5001
  }, async () => {
5002
+ const concurrencyId = this.getConcurrencyId(sessionId);
5538
5003
  return orchestrator.createTesters({
5539
5004
  method,
5540
5005
  files: [file],
5541
5006
  providedContext: this._providedContext || "[{}]",
5542
- otelCarrier: this._traces.getContextCarrier()
5007
+ otelCarrier: this._traces.getContextCarrier(),
5008
+ concurrencyId,
5009
+ workerId: concurrencyId
5543
5010
  });
5544
5011
  }).then(() => {
5545
5012
  debug?.("[%s] test %s finished running", sessionId, file);
@@ -5727,6 +5194,7 @@ class PoolRunner {
5727
5194
  poolId = void 0;
5728
5195
  project;
5729
5196
  environment;
5197
+ _lastTestFiles;
5730
5198
  _state = RunnerState.IDLE;
5731
5199
  _operationLock = null;
5732
5200
  _terminatePromise = createDefer();
@@ -5749,6 +5217,7 @@ class PoolRunner {
5749
5217
  this.project = options.project;
5750
5218
  this.environment = options.environment;
5751
5219
  const vitest = this.project.vitest;
5220
+ this._lastTestFiles = [];
5752
5221
  this._traces = vitest._traces;
5753
5222
  if (this._traces.isEnabled()) {
5754
5223
  const { span: workerSpan, context } = this._traces.startContextSpan("vitest.worker");
@@ -5802,7 +5271,8 @@ class PoolRunner {
5802
5271
  return span;
5803
5272
  }
5804
5273
  request(method, context) {
5805
- this._otel?.files.push(...context.files.map((f) => f.filepath));
5274
+ this._lastTestFiles = context.files.map((f) => f.filepath);
5275
+ this._otel?.files.push(...this._lastTestFiles);
5806
5276
  return this.postMessage({
5807
5277
  __vitest_worker_request__: true,
5808
5278
  type: method,
@@ -5922,6 +5392,7 @@ class PoolRunner {
5922
5392
  this._state = RunnerState.STOPPED;
5923
5393
  throw error;
5924
5394
  } finally {
5395
+ this._lastTestFiles = [];
5925
5396
  this._operationLock.resolve();
5926
5397
  this._operationLock = null;
5927
5398
  this._otel?.span.end();
@@ -5947,20 +5418,36 @@ class PoolRunner {
5947
5418
  this._eventEmitter.emit("error", error);
5948
5419
  }
5949
5420
  };
5950
- emitUnexpectedExit = () => {
5951
- const error = /* @__PURE__ */ new Error(`Worker exited unexpectedly during ${this._state} state`);
5421
+ emitUnexpectedExit = (code, signal) => {
5422
+ const hasCode = typeof code === "number";
5423
+ const errorDetails = hasCode || signal ? `with ${hasCode ? `exit code ${code} ` : ""}${signal ? `signal ${signal} ` : ""}` : "";
5424
+ const testFileDetails = this._lastTestFiles.length ? ` while running test file${this._lastTestFiles.length === 1 ? "" : "s"} ${this._lastTestFiles.join(", ")}` : "";
5425
+ const error = /* @__PURE__ */ new Error(`Worker exited unexpectedly ${errorDetails}during ${this._state} state${testFileDetails}`);
5426
+ this._state = RunnerState.STOPPED;
5952
5427
  this._eventEmitter.emit("error", error);
5953
5428
  };
5954
5429
  waitForStart() {
5955
5430
  return new Promise((resolve, reject) => {
5956
- const onStart = (message) => {
5431
+ const cleanup = () => {
5432
+ this.off("message", onStart);
5433
+ this.off("error", onError);
5434
+ };
5435
+ function onStart(message) {
5957
5436
  if (message.type === "started") {
5958
- this.off("message", onStart);
5437
+ cleanup();
5959
5438
  if (message.error) reject(message.error);
5960
5439
  else resolve();
5961
5440
  }
5962
- };
5441
+ }
5442
+ // The worker can die before it ever reports back (e.g. an invalid
5443
+ // `execArgv` makes Node exit immediately). Reject as soon as that
5444
+ // happens instead of waiting for the start timeout to elapse.
5445
+ function onError(error) {
5446
+ cleanup();
5447
+ reject(error);
5448
+ }
5963
5449
  this.on("message", onStart);
5450
+ this.on("error", onError);
5964
5451
  });
5965
5452
  }
5966
5453
  withTimeout(promise, timeout) {
@@ -6356,7 +5843,7 @@ class Pool {
6356
5843
  try {
6357
5844
  let isMemoryLimitReached = false;
6358
5845
  const runner = this.getPoolRunner(task, method);
6359
- const poolId = runner.poolId ?? this.getWorkerId();
5846
+ const poolId = runner.poolId ?? this.getConcurrencyId();
6360
5847
  runner.poolId = poolId;
6361
5848
  const activeTask = {
6362
5849
  task,
@@ -6376,14 +5863,18 @@ class Pool {
6376
5863
  if (task.memoryLimit && message.usedMemory) isMemoryLimitReached = message.usedMemory >= task.memoryLimit;
6377
5864
  if (message.error) this.options.state.catchError(message.error, "Test Run Error");
6378
5865
  runner.off("message", onFinished);
5866
+ runner.off("error", onTaskError);
6379
5867
  resolver.resolve();
6380
5868
  }
6381
5869
  };
5870
+ function onTaskError(error) {
5871
+ runner.off("message", onFinished);
5872
+ runner.off("error", onTaskError);
5873
+ resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
5874
+ }
6382
5875
  runner.on("message", onFinished);
5876
+ runner.on("error", onTaskError);
6383
5877
  if (!runner.isStarted) {
6384
- runner.on("error", (error) => {
6385
- resolver.reject(new Error(`[vitest-pool]: Worker ${task.worker} emitted error.`, { cause: error }));
6386
- });
6387
5878
  const id = setTimeout(() => resolver.reject(/* @__PURE__ */ new Error(`[vitest-pool]: Timeout starting ${task.worker} runner.`)), WORKER_START_TIMEOUT);
6388
5879
  await runner.start({ workerId: task.context.workerId }).catch((error) => resolver.reject(new Error(`[vitest-pool]: Failed to start ${task.worker} worker for test files ${formatFiles(task)}.`, { cause: error }))).finally(() => clearTimeout(id));
6389
5880
  }
@@ -6396,7 +5887,7 @@ class Pool {
6396
5887
  await resolver.promise.catch((error) => span?.recordException(error)).finally(() => span?.end());
6397
5888
  const index = this.activeTasks.indexOf(activeTask);
6398
5889
  if (index !== -1) this.activeTasks.splice(index, 1);
6399
- if (!task.isolate && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) {
5890
+ if (!task.isolate && !runner.isTerminated && !isMemoryLimitReached && this.queue[0]?.task.isolate === false && isEqualRunner(runner, this.queue[0].task)) {
6400
5891
  this.sharedRunners.push(runner);
6401
5892
  return this.schedule();
6402
5893
  }
@@ -6466,15 +5957,16 @@ catch (error) {
6466
5957
  if (customPool != null && customPool.name === task.worker) return new PoolRunner(options, customPool.createPoolWorker(options));
6467
5958
  throw new Error(`Runner ${task.worker} is not supported. Test files: ${formatFiles(task)}.`);
6468
5959
  }
6469
- getWorkerId() {
6470
- let workerId = 0;
5960
+ getConcurrencyId() {
5961
+ let concurrencyId;
6471
5962
  this.workerIds.forEach((state, id) => {
6472
- if (state && !workerId) {
6473
- workerId = id;
5963
+ if (state && concurrencyId == null) {
5964
+ concurrencyId = id;
6474
5965
  this.workerIds.set(id, false);
6475
5966
  }
6476
5967
  });
6477
- return workerId;
5968
+ if (concurrencyId == null) throw new Error("Cannot set concurrency id because there are no valid free ids.");
5969
+ return concurrencyId;
6478
5970
  }
6479
5971
  freeWorkerId(id) {
6480
5972
  this.workerIds.set(id, true);
@@ -6519,7 +6011,7 @@ function deepEqual$1(obj1, obj2) {
6519
6011
  const keys1 = Object.keys(obj1);
6520
6012
  const keys2 = Object.keys(obj2);
6521
6013
  if (keys1.length !== keys2.length) return false;
6522
- for (const key of keys1) if (!Object.prototype.hasOwnProperty.call(obj2, key) || !deepEqual$1(obj1[key], obj2[key])) return false;
6014
+ for (const key of keys1) if (!Object.hasOwn(obj2, key) || !deepEqual$1(obj1[key], obj2[key])) return false;
6523
6015
  return true;
6524
6016
  }
6525
6017
 
@@ -6545,7 +6037,7 @@ function createPool(ctx) {
6545
6037
  specs = await sequencer.shard(Array.from(specs));
6546
6038
  }
6547
6039
  const taskGroups = [];
6548
- let workerId = 0;
6040
+ let workerId = 1;
6549
6041
  const sorted = await sequencer.sort(specs);
6550
6042
  const { environments, tags } = await getSpecificationsOptions(specs);
6551
6043
  const groups = groupSpecs(sorted, environments);
@@ -7045,6 +6537,9 @@ function requirePermessageDeflate () {
7045
6537
  * acknowledge disabling of client context takeover
7046
6538
  * @param {Number} [options.concurrencyLimit=10] The number of concurrent
7047
6539
  * calls to zlib
6540
+ * @param {Boolean} [options.isServer=false] Create the instance in either
6541
+ * server or client mode
6542
+ * @param {Number} [options.maxPayload=0] The maximum allowed message length
7048
6543
  * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
7049
6544
  * use of a custom server window size
7050
6545
  * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
@@ -7055,16 +6550,13 @@ function requirePermessageDeflate () {
7055
6550
  * deflate
7056
6551
  * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
7057
6552
  * inflate
7058
- * @param {Boolean} [isServer=false] Create the instance in either server or
7059
- * client mode
7060
- * @param {Number} [maxPayload=0] The maximum allowed message length
7061
6553
  */
7062
- constructor(options, isServer, maxPayload) {
7063
- this._maxPayload = maxPayload | 0;
6554
+ constructor(options) {
7064
6555
  this._options = options || {};
7065
6556
  this._threshold =
7066
6557
  this._options.threshold !== undefined ? this._options.threshold : 1024;
7067
- this._isServer = !!isServer;
6558
+ this._maxPayload = this._options.maxPayload | 0;
6559
+ this._isServer = !!this._options.isServer;
7068
6560
  this._deflate = null;
7069
6561
  this._inflate = null;
7070
6562
 
@@ -7745,6 +7237,10 @@ function requireReceiver () {
7745
7237
  * extensions
7746
7238
  * @param {Boolean} [options.isServer=false] Specifies whether to operate in
7747
7239
  * client or server mode
7240
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
7241
+ * buffered data chunks
7242
+ * @param {Number} [options.maxFragments=0] The maximum number of message
7243
+ * fragments
7748
7244
  * @param {Number} [options.maxPayload=0] The maximum allowed message length
7749
7245
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
7750
7246
  * not to skip UTF-8 validation for text and close messages
@@ -7759,6 +7255,8 @@ function requireReceiver () {
7759
7255
  this._binaryType = options.binaryType || BINARY_TYPES[0];
7760
7256
  this._extensions = options.extensions || {};
7761
7257
  this._isServer = !!options.isServer;
7258
+ this._maxBufferedChunks = options.maxBufferedChunks | 0;
7259
+ this._maxFragments = options.maxFragments | 0;
7762
7260
  this._maxPayload = options.maxPayload | 0;
7763
7261
  this._skipUTF8Validation = !!options.skipUTF8Validation;
7764
7262
  this[kWebSocket] = undefined;
@@ -7794,6 +7292,22 @@ function requireReceiver () {
7794
7292
  _write(chunk, encoding, cb) {
7795
7293
  if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
7796
7294
 
7295
+ if (
7296
+ this._maxBufferedChunks > 0 &&
7297
+ this._buffers.length >= this._maxBufferedChunks
7298
+ ) {
7299
+ cb(
7300
+ this.createError(
7301
+ RangeError,
7302
+ 'Too many buffered chunks',
7303
+ false,
7304
+ 1008,
7305
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
7306
+ )
7307
+ );
7308
+ return;
7309
+ }
7310
+
7797
7311
  this._bufferedBytes += chunk.length;
7798
7312
  this._buffers.push(chunk);
7799
7313
  this.startLoop(cb);
@@ -8190,6 +7704,22 @@ function requireReceiver () {
8190
7704
  }
8191
7705
 
8192
7706
  if (data.length) {
7707
+ if (
7708
+ this._maxFragments > 0 &&
7709
+ this._fragments.length >= this._maxFragments
7710
+ ) {
7711
+ const error = this.createError(
7712
+ RangeError,
7713
+ 'Too many message fragments',
7714
+ false,
7715
+ 1008,
7716
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
7717
+ );
7718
+
7719
+ cb(error);
7720
+ return;
7721
+ }
7722
+
8193
7723
  //
8194
7724
  // This message is not compressed so its length is the sum of the payload
8195
7725
  // length of all fragments.
@@ -8229,6 +7759,22 @@ function requireReceiver () {
8229
7759
  return;
8230
7760
  }
8231
7761
 
7762
+ if (
7763
+ this._maxFragments > 0 &&
7764
+ this._fragments.length >= this._maxFragments
7765
+ ) {
7766
+ const error = this.createError(
7767
+ RangeError,
7768
+ 'Too many message fragments',
7769
+ false,
7770
+ 1008,
7771
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
7772
+ );
7773
+
7774
+ cb(error);
7775
+ return;
7776
+ }
7777
+
8232
7778
  this._fragments.push(buf);
8233
7779
  }
8234
7780
 
@@ -8423,6 +7969,9 @@ function requireSender () {
8423
7969
 
8424
7970
  const { Duplex } = require$$0$2;
8425
7971
  const { randomFillSync } = require$$1;
7972
+ const {
7973
+ types: { isUint8Array }
7974
+ } = require$$2;
8426
7975
 
8427
7976
  const PerMessageDeflate = requirePermessageDeflate();
8428
7977
  const { EMPTY_BUFFER, kWebSocket, NOOP } = requireConstants();
@@ -8619,8 +8168,10 @@ function requireSender () {
8619
8168
 
8620
8169
  if (typeof data === 'string') {
8621
8170
  buf.write(data, 2);
8622
- } else {
8171
+ } else if (isUint8Array(data)) {
8623
8172
  buf.set(data, 2);
8173
+ } else {
8174
+ throw new TypeError('Second argument must be a string or a Uint8Array');
8624
8175
  }
8625
8176
  }
8626
8177
 
@@ -9544,7 +9095,7 @@ function requireWebsocket () {
9544
9095
 
9545
9096
  const EventEmitter = require$$0$3;
9546
9097
  const https = require$$1$1;
9547
- const http = require$$2;
9098
+ const http = require$$2$1;
9548
9099
  const net = require$$3;
9549
9100
  const tls = require$$4;
9550
9101
  const { randomBytes, createHash } = require$$1;
@@ -9741,6 +9292,10 @@ function requireWebsocket () {
9741
9292
  * multiple times in the same tick
9742
9293
  * @param {Function} [options.generateMask] The function used to generate the
9743
9294
  * masking key
9295
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
9296
+ * buffered data chunks
9297
+ * @param {Number} [options.maxFragments=0] The maximum number of message
9298
+ * fragments
9744
9299
  * @param {Number} [options.maxPayload=0] The maximum allowed message size
9745
9300
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
9746
9301
  * not to skip UTF-8 validation for text and close messages
@@ -9752,6 +9307,8 @@ function requireWebsocket () {
9752
9307
  binaryType: this.binaryType,
9753
9308
  extensions: this._extensions,
9754
9309
  isServer: this._isServer,
9310
+ maxBufferedChunks: options.maxBufferedChunks,
9311
+ maxFragments: options.maxFragments,
9755
9312
  maxPayload: options.maxPayload,
9756
9313
  skipUTF8Validation: options.skipUTF8Validation
9757
9314
  });
@@ -10180,6 +9737,10 @@ function requireWebsocket () {
10180
9737
  * masking key
10181
9738
  * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
10182
9739
  * handshake request
9740
+ * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
9741
+ * buffered data chunks
9742
+ * @param {Number} [options.maxFragments=131072] The maximum number of message
9743
+ * fragments
10183
9744
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
10184
9745
  * size
10185
9746
  * @param {Number} [options.maxRedirects=10] The maximum number of redirects
@@ -10200,6 +9761,8 @@ function requireWebsocket () {
10200
9761
  autoPong: true,
10201
9762
  closeTimeout: CLOSE_TIMEOUT,
10202
9763
  protocolVersion: protocolVersions[1],
9764
+ maxBufferedChunks: 1024 * 1024,
9765
+ maxFragments: 128 * 1024,
10203
9766
  maxPayload: 100 * 1024 * 1024,
10204
9767
  skipUTF8Validation: false,
10205
9768
  perMessageDeflate: true,
@@ -10233,7 +9796,7 @@ function requireWebsocket () {
10233
9796
  } else {
10234
9797
  try {
10235
9798
  parsedUrl = new URL(address);
10236
- } catch (e) {
9799
+ } catch {
10237
9800
  throw new SyntaxError(`Invalid URL: ${address}`);
10238
9801
  }
10239
9802
  }
@@ -10295,11 +9858,11 @@ function requireWebsocket () {
10295
9858
  opts.timeout = opts.handshakeTimeout;
10296
9859
 
10297
9860
  if (opts.perMessageDeflate) {
10298
- perMessageDeflate = new PerMessageDeflate(
10299
- opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
10300
- false,
10301
- opts.maxPayload
10302
- );
9861
+ perMessageDeflate = new PerMessageDeflate({
9862
+ ...opts.perMessageDeflate,
9863
+ isServer: false,
9864
+ maxPayload: opts.maxPayload
9865
+ });
10303
9866
  opts.headers['Sec-WebSocket-Extensions'] = format({
10304
9867
  [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
10305
9868
  });
@@ -10557,6 +10120,8 @@ function requireWebsocket () {
10557
10120
  websocket.setSocket(socket, head, {
10558
10121
  allowSynchronousEvents: opts.allowSynchronousEvents,
10559
10122
  generateMask: opts.generateMask,
10123
+ maxBufferedChunks: opts.maxBufferedChunks,
10124
+ maxFragments: opts.maxFragments,
10560
10125
  maxPayload: opts.maxPayload,
10561
10126
  skipUTF8Validation: opts.skipUTF8Validation
10562
10127
  });
@@ -11106,12 +10671,14 @@ function requireStream () {
11106
10671
 
11107
10672
  requireStream();
11108
10673
 
10674
+ requireExtension();
10675
+
10676
+ requirePermessageDeflate();
10677
+
11109
10678
  requireReceiver();
11110
10679
 
11111
10680
  requireSender();
11112
10681
 
11113
- requireWebsocket();
11114
-
11115
10682
  var subprotocol;
11116
10683
  var hasRequiredSubprotocol;
11117
10684
 
@@ -11182,6 +10749,10 @@ function requireSubprotocol () {
11182
10749
  return subprotocol;
11183
10750
  }
11184
10751
 
10752
+ requireSubprotocol();
10753
+
10754
+ requireWebsocket();
10755
+
11185
10756
  /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */
11186
10757
 
11187
10758
  var websocketServer;
@@ -11192,7 +10763,7 @@ function requireWebsocketServer () {
11192
10763
  hasRequiredWebsocketServer = 1;
11193
10764
 
11194
10765
  const EventEmitter = require$$0$3;
11195
- const http = require$$2;
10766
+ const http = require$$2$1;
11196
10767
  const { Duplex } = require$$0$2;
11197
10768
  const { createHash } = require$$1;
11198
10769
 
@@ -11232,6 +10803,10 @@ function requireWebsocketServer () {
11232
10803
  * called
11233
10804
  * @param {Function} [options.handleProtocols] A hook to handle protocols
11234
10805
  * @param {String} [options.host] The hostname where to bind the server
10806
+ * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
10807
+ * buffered data chunks
10808
+ * @param {Number} [options.maxFragments=131072] The maximum number of message
10809
+ * fragments
11235
10810
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
11236
10811
  * size
11237
10812
  * @param {Boolean} [options.noServer=false] Enable no server mode
@@ -11254,6 +10829,8 @@ function requireWebsocketServer () {
11254
10829
  options = {
11255
10830
  allowSynchronousEvents: true,
11256
10831
  autoPong: true,
10832
+ maxBufferedChunks: 1024 * 1024,
10833
+ maxFragments: 128 * 1024,
11257
10834
  maxPayload: 100 * 1024 * 1024,
11258
10835
  skipUTF8Validation: false,
11259
10836
  perMessageDeflate: false,
@@ -11482,11 +11059,11 @@ function requireWebsocketServer () {
11482
11059
  this.options.perMessageDeflate &&
11483
11060
  secWebSocketExtensions !== undefined
11484
11061
  ) {
11485
- const perMessageDeflate = new PerMessageDeflate(
11486
- this.options.perMessageDeflate,
11487
- true,
11488
- this.options.maxPayload
11489
- );
11062
+ const perMessageDeflate = new PerMessageDeflate({
11063
+ ...this.options.perMessageDeflate,
11064
+ isServer: true,
11065
+ maxPayload: this.options.maxPayload
11066
+ });
11490
11067
 
11491
11068
  try {
11492
11069
  const offers = extension.parse(secWebSocketExtensions);
@@ -11613,6 +11190,8 @@ function requireWebsocketServer () {
11613
11190
 
11614
11191
  ws.setSocket(socket, head, {
11615
11192
  allowSynchronousEvents: this.options.allowSynchronousEvents,
11193
+ maxBufferedChunks: this.options.maxBufferedChunks,
11194
+ maxFragments: this.options.maxFragments,
11616
11195
  maxPayload: this.options.maxPayload,
11617
11196
  skipUTF8Validation: this.options.skipUTF8Validation
11618
11197
  });
@@ -12187,8 +11766,9 @@ function serializeConfig(project) {
12187
11766
  allowWrite: api?.allowWrite
12188
11767
  };
12189
11768
  })(project.isBrowserEnabled() ? config.browser.api : config.api),
12190
- diff: config.diff,
11769
+ diff: serializeDiffOptions(config.diff),
12191
11770
  retry: config.retry,
11771
+ repeats: config.repeats,
12192
11772
  disableConsoleIntercept: config.disableConsoleIntercept,
12193
11773
  root: config.root,
12194
11774
  name: config.name,
@@ -12223,11 +11803,11 @@ function serializeConfig(project) {
12223
11803
  expand: config.snapshotOptions.expand ?? globalConfig.snapshotOptions.expand
12224
11804
  },
12225
11805
  sequence: {
12226
- shuffle: globalConfig.sequence.shuffle,
12227
- concurrent: globalConfig.sequence.concurrent,
11806
+ shuffle: config.sequence.shuffle,
11807
+ concurrent: config.sequence.concurrent,
12228
11808
  seed: globalConfig.sequence.seed,
12229
- hooks: globalConfig.sequence.hooks,
12230
- setupFiles: globalConfig.sequence.setupFiles
11809
+ hooks: config.sequence.hooks,
11810
+ setupFiles: config.sequence.setupFiles
12231
11811
  },
12232
11812
  inspect: globalConfig.inspect,
12233
11813
  inspectBrk: globalConfig.inspectBrk,
@@ -12285,6 +11865,36 @@ function serializeConfig(project) {
12285
11865
  disableColors: isAgent && !isForceColor()
12286
11866
  };
12287
11867
  }
11868
+ const serializableDiffKeys = [
11869
+ "aAnnotation",
11870
+ "aIndicator",
11871
+ "bAnnotation",
11872
+ "bIndicator",
11873
+ "commonIndicator",
11874
+ "contextLines",
11875
+ "emptyFirstOrLastLinePlaceholder",
11876
+ "expand",
11877
+ "includeChangeCounts",
11878
+ "omitAnnotationLines",
11879
+ "printBasicPrototype",
11880
+ "maxDepth",
11881
+ "truncateThreshold",
11882
+ "truncateAnnotation"
11883
+ ];
11884
+ // `diff` can be an inline object containing color/compareKeys functions
11885
+ // (`DiffOptions`). Those functions are not structured-cloneable (threads pool)
11886
+ // and are silently dropped over `child_process` IPC (forks pool), so passing
11887
+ // the raw object to workers throws `DataCloneError`. Forward only the
11888
+ // serializable fields declared by `SerializedDiffOptions`, and only the ones
11889
+ // actually set — explicit `undefined` values would override the diff defaults
11890
+ // when the worker merges the options. The function-based options still
11891
+ // require the file-path form, which workers import locally.
11892
+ function serializeDiffOptions(diff) {
11893
+ if (diff == null || typeof diff === "string") return diff;
11894
+ const result = {};
11895
+ for (const key of serializableDiffKeys) if (diff[key] !== void 0) result[key] = diff[key];
11896
+ return result;
11897
+ }
12288
11898
 
12289
11899
  async function loadGlobalSetupFiles(runner, globalSetup) {
12290
11900
  const globalSetupFiles = toArray(globalSetup);
@@ -13361,7 +12971,10 @@ function WorkspaceVitestPlugin(project, options) {
13361
12971
  ws: false,
13362
12972
  preTransformRequests: false,
13363
12973
  middlewareMode: true,
13364
- fs: { allow: resolveFsAllow(project.vitest.config.root, project.vitest.vite.config.configFile) }
12974
+ fs: {
12975
+ allow: resolveFsAllow(project.vitest.config.root, project.vitest.vite.config.configFile),
12976
+ deny: [API_TOKEN_FILE]
12977
+ }
13365
12978
  },
13366
12979
  environments: { ssr: { resolve: resolveOptions } },
13367
12980
  test: {}
@@ -13475,7 +13088,7 @@ const defaultInline = [
13475
13088
  ];
13476
13089
  const depsExternal = [/\/node_modules\/.*\.cjs\.js$/, /\/node_modules\/.*\.mjs$/];
13477
13090
  function guessCJSversion(id) {
13478
- if (id.match(ESM_EXT_RE)) {
13091
+ if (ESM_EXT_RE.test(id)) {
13479
13092
  for (const i of [
13480
13093
  id.replace(ESM_EXT_RE, ".mjs"),
13481
13094
  id.replace(ESM_EXT_RE, ".umd.js"),
@@ -13483,7 +13096,7 @@ function guessCJSversion(id) {
13483
13096
  id.replace(ESM_EXT_RE, ".js")
13484
13097
  ]) if (existsSync(i)) return i;
13485
13098
  }
13486
- if (id.match(ESM_FOLDER_RE)) {
13099
+ if (ESM_FOLDER_RE.test(id)) {
13487
13100
  for (const i of [
13488
13101
  id.replace(ESM_FOLDER_RE, "/umd/$1"),
13489
13102
  id.replace(ESM_FOLDER_RE, "/cjs/$1"),
@@ -14116,6 +13729,7 @@ async function resolveProjects(vitest, cliOptions, workspaceConfigPath, projects
14116
13729
  "expandSnapshotDiff",
14117
13730
  "disableConsoleIntercept",
14118
13731
  "retry",
13732
+ "repeats",
14119
13733
  "testNamePattern",
14120
13734
  "passWithNoTests",
14121
13735
  "bail",
@@ -14474,7 +14088,8 @@ class BlobReporter {
14474
14088
  errors,
14475
14089
  coverage,
14476
14090
  executionTime,
14477
- environmentModules
14091
+ environmentModules,
14092
+ this.ctx.state.transformTime
14478
14093
  ]);
14479
14094
  let outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "blob");
14480
14095
  if (outputFile) {
@@ -14503,7 +14118,7 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
14503
14118
  const promises = (await readdir(resolvedDir)).map(async (filename) => {
14504
14119
  const fullPath = resolve$1(resolvedDir, filename);
14505
14120
  if (!(await stat(fullPath)).isFile()) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a file`);
14506
- const [version, files, errors, coverage, executionTime, environmentModules] = parse(await readFile(fullPath, "utf-8"));
14121
+ const [version, files, errors, coverage, executionTime, environmentModules, transformTime] = parse(await readFile(fullPath, "utf-8"));
14507
14122
  if (!version) throw new TypeError(`vitest.mergeReports() expects all paths in "${blobsDirectory}" to be files generated by the blob reporter, but "${filename}" is not a valid blob file`);
14508
14123
  return {
14509
14124
  version,
@@ -14512,7 +14127,8 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
14512
14127
  coverage,
14513
14128
  file: filename,
14514
14129
  executionTime,
14515
- environmentModules
14130
+ environmentModules,
14131
+ transformTime
14516
14132
  };
14517
14133
  });
14518
14134
  const blobs = await Promise.all(promises);
@@ -14547,7 +14163,8 @@ async function readBlobs(currentVersion, blobsDirectory, projectsArray) {
14547
14163
  }),
14548
14164
  errors: blobs.flatMap((blob) => blob.errors),
14549
14165
  coverages: blobs.map((blob) => blob.coverage),
14550
- executionTimes: blobs.map((blob) => blob.executionTime)
14166
+ executionTimes: blobs.map((blob) => blob.executionTime),
14167
+ transformTimes: blobs.map((blob) => blob.transformTime)
14551
14168
  };
14552
14169
  }
14553
14170
  function serializeEnvironmentModuleGraph(environment) {
@@ -14873,7 +14490,7 @@ class BaseReporter {
14873
14490
  getFullName(test, separator) {
14874
14491
  if (test === test.file) return test.name;
14875
14492
  let name = test.file.name;
14876
- if (test.location) name += c.dim(`:${test.location.line}:${test.location.column}`);
14493
+ if (test.location) name += c.dim(`:${test.location.line}`);
14877
14494
  name += separator;
14878
14495
  name += getTestName(test, separator);
14879
14496
  return name;
@@ -15304,7 +14921,7 @@ function deepEqual(a, b) {
15304
14921
  const keysA = Object.keys(a);
15305
14922
  const keysB = Object.keys(b);
15306
14923
  if (keysA.length !== keysB.length) return false;
15307
- for (const key of keysA) if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) return false;
14924
+ for (const key of keysA) if (!Object.hasOwn(b, key) || !deepEqual(a[key], b[key])) return false;
15308
14925
  return true;
15309
14926
  }
15310
14927
  function sum(items, cb) {
@@ -16233,22 +15850,20 @@ class JsonReporter {
16233
15850
  testResults,
16234
15851
  coverageMap: this.coverageMap
16235
15852
  };
16236
- await this.writeReport(JSON.stringify(result));
16237
- }
16238
- /**
16239
- * Writes the report to an output file if specified in the config,
16240
- * or logs it to the console otherwise.
16241
- * @param report
16242
- */
16243
- async writeReport(report) {
15853
+ const resultString = JSON.stringify(result);
16244
15854
  const outputFile = this.options.outputFile ?? getOutputFile(this.ctx.config, "json");
16245
15855
  if (outputFile) {
16246
15856
  const reportFile = resolve$1(this.ctx.config.root, outputFile);
16247
15857
  const outputDirectory = dirname(reportFile);
16248
15858
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
16249
- await promises.writeFile(reportFile, report, "utf-8");
15859
+ await promises.writeFile(reportFile, resultString, "utf-8");
16250
15860
  this.ctx.logger.log(`JSON report written to ${reportFile}`);
16251
- } else this.ctx.logger.log(report);
15861
+ } else if (this.options.stdout) this.ctx.logger.log(resultString);
15862
+ else {
15863
+ const report = this.ctx.createReport("json");
15864
+ await report.writeFile("output.json", resultString);
15865
+ this.ctx.logger.log(`JSON report written to ${resolve$1(report.root, "output.json")}`);
15866
+ }
16252
15867
  }
16253
15868
  }
16254
15869
 
@@ -16332,6 +15947,8 @@ class JUnitReporter {
16332
15947
  this.reportFile = resolve$1(this.ctx.config.root, outputFile);
16333
15948
  const outputDirectory = dirname(this.reportFile);
16334
15949
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
15950
+ } else if (!this.options.stdout) this.reportFile = resolve$1(this.ctx.createReport("junit").root, "output.xml");
15951
+ if (this.reportFile) {
16335
15952
  this.fileFd = await promises.open(this.reportFile, "w+");
16336
15953
  this.baseLog = async (text) => {
16337
15954
  if (!this.fileFd) this.fileFd = await promises.open(this.reportFile, "w+");
@@ -16711,7 +16328,7 @@ class VerboseReporter extends DefaultReporter {
16711
16328
  if (this.ctx.config.hideSkippedTests && testResult.state === "skipped" && test.options.mode !== "todo") return;
16712
16329
  let title = ` ${this.getEntityPrefix(test)} `;
16713
16330
  title += test.module.task.name;
16714
- if (test.location) title += c.dim(`:${test.location.line}:${test.location.column}`);
16331
+ if (test.location) title += c.dim(`:${test.location.line}`);
16715
16332
  title += separator;
16716
16333
  title += getTestName(test.task, separator);
16717
16334
  title += this.getTestCaseSuffix(test);
@@ -16775,11 +16392,11 @@ function parseFilter(filter) {
16775
16392
  const colonIndex = filter.lastIndexOf(":");
16776
16393
  if (colonIndex === -1) return { filename: filter };
16777
16394
  const [parsedFilename, lineNumber] = [filter.substring(0, colonIndex), filter.substring(colonIndex + 1)];
16778
- if (lineNumber.match(/^\d+$/)) return {
16395
+ if (/^\d+$/.test(lineNumber)) return {
16779
16396
  filename: parsedFilename,
16780
16397
  lineNumber: Number.parseInt(lineNumber)
16781
16398
  };
16782
- else if (lineNumber.match(/^\d+-\d+$/)) throw new RangeLocationFilterProvidedError(filter);
16399
+ else if (/^\d+-\d+$/.test(lineNumber)) throw new RangeLocationFilterProvidedError(filter);
16783
16400
  else return { filename: filter };
16784
16401
  }
16785
16402
  function groupFilters(filters) {
@@ -17207,7 +16824,7 @@ class TestSuite extends SuiteImplementation {
17207
16824
  */
17208
16825
  toTestSpecification() {
17209
16826
  const isTypecheck = this.task.meta.typecheck === true;
17210
- const testIds = [...this.children.allTests()].map((test) => test.id);
16827
+ const testIds = Array.from(this.children.allTests(), (test) => test.id);
17211
16828
  return this.project.createSpecification(this.module.moduleId, { testIds }, isTypecheck ? "typecheck" : void 0);
17212
16829
  }
17213
16830
  /**
@@ -17274,7 +16891,9 @@ class TestModule extends SuiteImplementation {
17274
16891
  setupDuration,
17275
16892
  duration: this.task.result?.duration || 0,
17276
16893
  heap: this.task.result?.heap,
17277
- importDurations: this.task.importDurations ?? {}
16894
+ importDurations: this.task.importDurations ?? {},
16895
+ concurrencyId: this.task.concurrencyId,
16896
+ workerId: this.task.workerId
17278
16897
  };
17279
16898
  }
17280
16899
  }
@@ -18743,13 +18362,15 @@ class Vitest {
18743
18362
  async mergeReports(directory) {
18744
18363
  return this._traces.$("vitest.merge_reports", async () => {
18745
18364
  if (this.reporters.some((r) => r instanceof BlobReporter)) throw new Error("Cannot merge reports when `--reporter=blob` is used. Remove blob reporter from the config first.");
18746
- const { files, errors, coverages, executionTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
18365
+ const { files, errors, coverages, executionTimes, transformTimes } = await readBlobs(this.version, directory || this.config.mergeReports, this.projects);
18747
18366
  this.state.blobs = {
18748
18367
  files,
18749
18368
  errors,
18750
18369
  coverages,
18751
- executionTimes
18370
+ executionTimes,
18371
+ transformTimes
18752
18372
  };
18373
+ this.state.transformTime = transformTimes.reduce((a, b) => a + b, 0);
18753
18374
  await this.report("onInit", this);
18754
18375
  const specifications = [];
18755
18376
  for (const file of files) {
@@ -18775,7 +18396,7 @@ class Vitest {
18775
18396
  * Returns the seed, if tests are running in a random order.
18776
18397
  */
18777
18398
  getSeed() {
18778
- return this.config.sequence.seed ?? null;
18399
+ return this.config.sequence.sequencer === RandomSequencer || !!this.config.sequence.shuffle || this.projects.some((p) => !!p.config.sequence.shuffle) ? this.config.sequence.seed : null;
18779
18400
  }
18780
18401
  /** @internal */
18781
18402
  async _reportFileTask(file) {
@@ -19138,7 +18759,7 @@ class Vitest {
19138
18759
  */
19139
18760
  async cancelCurrentRun(reason) {
19140
18761
  this.isCancelling = true;
19141
- this.cancelPromise = Promise.all([...this._onCancelListeners].map((listener) => listener(reason)));
18762
+ this.cancelPromise = Promise.all(Array.from(this._onCancelListeners, (listener) => listener(reason)));
19142
18763
  await this.cancelPromise.finally(() => this.cancelPromise = void 0);
19143
18764
  await this.runningPromise;
19144
18765
  }
@@ -19506,8 +19127,6 @@ async function VitestPlugin(options = {}, vitest = new Vitest(deepClone(options)
19506
19127
  const defines = deleteDefineConfig(viteConfig);
19507
19128
  options.defines = defines;
19508
19129
  options.viteDefine = originalDefine;
19509
- let open = false;
19510
- if (testConfig.ui && testConfig.open) open = testConfig.uiBase ?? "/__vitest__/";
19511
19130
  const resolveOptions = getDefaultResolveOptions();
19512
19131
  let config = {
19513
19132
  base: "/",
@@ -19519,11 +19138,14 @@ async function VitestPlugin(options = {}, vitest = new Vitest(deepClone(options)
19519
19138
  },
19520
19139
  server: {
19521
19140
  ...testConfig.api,
19522
- open,
19141
+ open: false,
19523
19142
  hmr: false,
19524
19143
  ws: testConfig.api?.middlewareMode ? false : void 0,
19525
19144
  preTransformRequests: false,
19526
- fs: { allow: resolveFsAllow(options.root || process.cwd(), testConfig.config) }
19145
+ fs: {
19146
+ allow: resolveFsAllow(options.root || process.cwd(), testConfig.config),
19147
+ deny: [API_TOKEN_FILE]
19148
+ }
19527
19149
  },
19528
19150
  build: {
19529
19151
  outDir: "dummy-non-existing-folder",
@@ -19638,7 +19260,7 @@ async function createVitest(modeOrOptions, optionsOrViteOverrides = {}, viteOver
19638
19260
  }
19639
19261
  const ctx = new Vitest(deepClone(options), vitestOptions);
19640
19262
  const root = slash(resolve$2(options.root || process.cwd()));
19641
- const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$2(root, options.config) : any(configFiles, { cwd: root });
19263
+ const configPath = options.config === false ? false : options.config ? resolveModule(options.config, { paths: [root] }) ?? resolve$2(root, options.config) : findConfigFile(root);
19642
19264
  options.config = configPath;
19643
19265
  const { browser: _removeBrowser, ...restOptions } = options;
19644
19266
  const config = {
@@ -19649,7 +19271,33 @@ async function createVitest(modeOrOptions, optionsOrViteOverrides = {}, viteOver
19649
19271
  };
19650
19272
  try {
19651
19273
  const server = await createViteServer(mergeConfig(config, mergeConfig(viteOverrides, { root: options.root })));
19652
- if (ctx.config.api?.port) await server.listen();
19274
+ if (ctx.config.api?.port) {
19275
+ await server.listen();
19276
+ if (ctx.config.ui && ctx.config.open) {
19277
+ // Note: `tokenCreated` is only an approximation of "the browser is not
19278
+ // authenticated yet". If the user clears cookies while the token file
19279
+ // persists, the clean URL will block until they re-open the `?token=`
19280
+ // URL printed in the terminal.
19281
+ if (ctx.config.api.tokenCreated) {
19282
+ // First run that generated the token: no browser holds the auth
19283
+ // cookie yet, so open the authenticated URL to set it. A new tab
19284
+ // here is fine since no clean-URL tab exists to reuse.
19285
+ const url = new URL(ctx.config.uiBase, "http://localhost");
19286
+ url.searchParams.set("token", ctx.config.api.token);
19287
+ server.config.server.open = `${url.pathname}${url.search}`;
19288
+ } else
19289
+ // Subsequent runs: open the clean UI base URL (without `?token=`)
19290
+ // rather than the authenticated URL printed by the logger. On macOS,
19291
+ // `openBrowser` reuses an existing tab whose URL matches via substring
19292
+ // and reloads it (Vite's `bin/openChrome.js`). Since the 302 redirect
19293
+ // strips the token, an already-authenticated tab lives at the clean
19294
+ // URL, so opening the clean URL matches and reloads it; opening the
19295
+ // token URL would never match and would spawn a new tab on every
19296
+ // restart.
19297
+ server.config.server.open = ctx.config.uiBase;
19298
+ server.openBrowser();
19299
+ }
19300
+ }
19653
19301
  return ctx;
19654
19302
  }
19655
19303
  // Vitest can fail at any point inside "setServer" or inside a custom plugin
@@ -19796,7 +19444,6 @@ const keys = [
19796
19444
  ["p", "filter by a filename"],
19797
19445
  ["t", "filter by a test name regex pattern"],
19798
19446
  ["w", "filter by a project name"],
19799
- ["b", "start the browser server if not started yet"],
19800
19447
  ["q", "quit"]
19801
19448
  ];
19802
19449
  const cancelKeys = [
@@ -19812,7 +19459,7 @@ ${keys.map((i) => c.dim(" press ") + c.reset([i[0]].flat().map(c.bold).join(",
19812
19459
  `);
19813
19460
  }
19814
19461
  function* traverseFilteredTestNames(parentName, filter, t) {
19815
- if (isTestCase(t)) {
19462
+ if (t.type === "test") {
19816
19463
  if (t.name.match(filter)) {
19817
19464
  const displayName = `${parentName} > ${t.name}`;
19818
19465
  yield {
@@ -19881,14 +19528,6 @@ function registerConsoleShortcuts(ctx, stdin = process.stdin, stdout) {
19881
19528
  if (name === "t") return inputNamePattern();
19882
19529
  // change fileNamePattern
19883
19530
  if (name === "p") return inputFilePattern();
19884
- if (name === "b") {
19885
- await ctx._initBrowserServers();
19886
- ctx.projects.forEach((project) => {
19887
- ctx.logger.log();
19888
- ctx.logger.printBrowserBanner(project);
19889
- });
19890
- return null;
19891
- }
19892
19531
  }
19893
19532
  async function keypressHandler(str, key) {
19894
19533
  await _keypressHandler(str, key);
@@ -20149,4 +19788,4 @@ var cliApi = /*#__PURE__*/Object.freeze({
20149
19788
  startVitest: startVitest
20150
19789
  });
20151
19790
 
20152
- export { startVitest as A, BaseCoverageProvider as B, cliApi as C, DefaultReporter as D, ForksPoolWorker as F, GitNotFoundError as G, HangingProcessReporter as H, JUnitReporter as J, MinimalReporter as M, ReportersMap as R, TapFlatReporter as T, Vitest as V, VitestPlugin as a, BaseSequencer as b, DotReporter as c, GithubActionsReporter as d, JsonReporter as e, TapReporter as f, FilesNotFoundError as g, ThreadsPoolWorker as h, TypecheckPoolWorker as i, VerboseReporter as j, VitestPackageInstaller as k, VmForksPoolWorker as l, VmThreadsPoolWorker as m, createDebugger as n, createMethodsRPC as o, createViteLogger as p, createVitest as q, resolveConfig$1 as r, escapeTestName as s, experimental_getRunnerTask as t, getFilePoolName as u, isFileServingAllowed as v, isValidApiRequest as w, registerConsoleShortcuts as x, resolveApiServerConfig as y, resolveFsAllow as z };
19791
+ export { BaseCoverageProvider as B, DefaultReporter as D, ForksPoolWorker as F, GitNotFoundError as G, HangingProcessReporter as H, JUnitReporter as J, MinimalReporter as M, ReportersMap as R, TapFlatReporter as T, Vitest as V, VitestPlugin as a, DotReporter as b, GithubActionsReporter as c, JsonReporter as d, TapReporter as e, FilesNotFoundError as f, ThreadsPoolWorker as g, TypecheckPoolWorker as h, VerboseReporter as i, VitestPackageInstaller as j, VmForksPoolWorker as k, VmThreadsPoolWorker as l, createDebugger as m, createMethodsRPC as n, createViteLogger as o, createVitest as p, escapeTestName as q, experimental_getRunnerTask as r, getFilePoolName as s, isFileServingAllowed as t, isValidApiRequest as u, registerConsoleShortcuts as v, resolveFsAllow as w, startVitest as x, cliApi as y };