@vitest/runner 4.0.9 → 4.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { isObject, createDefer, toArray, isNegativeNaN, objectAttr, shuffle, assertTypes } from '@vitest/utils/helpers';
2
- import { getSafeTimers } from '@vitest/utils/timers';
3
1
  import { processError } from '@vitest/utils/error';
2
+ import { isObject, createDefer, assertTypes, toArray, isNegativeNaN, objectAttr, shuffle } from '@vitest/utils/helpers';
3
+ import { getSafeTimers } from '@vitest/utils/timers';
4
4
  import { format, formatRegExp, objDisplay } from '@vitest/utils/display';
5
5
  import { c as createChainable, f as findTestFileStackTrace, b as createFileTask, a as calculateSuiteHash, s as someTasksAreOnly, i as interpretTaskModes, l as limitConcurrency, p as partitionSuiteChildren, q as hasTests, o as hasFailed } from './chunk-tasks.js';
6
6
  import '@vitest/utils/source-map';
@@ -48,18 +48,6 @@ function getHooks(key) {
48
48
  return hooksMap.get(key);
49
49
  }
50
50
 
51
- async function runSetupFiles(config, files, runner) {
52
- if (config.sequence.setupFiles === "parallel") {
53
- await Promise.all(files.map(async (fsPath) => {
54
- await runner.importFile(fsPath, "setup");
55
- }));
56
- } else {
57
- for (const fsPath of files) {
58
- await runner.importFile(fsPath, "setup");
59
- }
60
- }
61
- }
62
-
63
51
  function mergeScopedFixtures(testFixtures, scopedFixtures) {
64
52
  const scopedFixturesMap = scopedFixtures.reduce((map, fixture) => {
65
53
  map[fixture.prop] = fixture;
@@ -390,6 +378,176 @@ function getRunningTests() {
390
378
  return tests;
391
379
  }
392
380
 
381
+ function getDefaultHookTimeout() {
382
+ return getRunner().config.hookTimeout;
383
+ }
384
+ const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
385
+ const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
386
+ function getBeforeHookCleanupCallback(hook, result, context) {
387
+ if (typeof result === "function") {
388
+ const timeout = CLEANUP_TIMEOUT_KEY in hook && typeof hook[CLEANUP_TIMEOUT_KEY] === "number" ? hook[CLEANUP_TIMEOUT_KEY] : getDefaultHookTimeout();
389
+ const stackTraceError = CLEANUP_STACK_TRACE_KEY in hook && hook[CLEANUP_STACK_TRACE_KEY] instanceof Error ? hook[CLEANUP_STACK_TRACE_KEY] : undefined;
390
+ return withTimeout(result, timeout, true, stackTraceError, (_, error) => {
391
+ if (context) {
392
+ abortContextSignal(context, error);
393
+ }
394
+ });
395
+ }
396
+ }
397
+ /**
398
+ * Registers a callback function to be executed once before all tests within the current suite.
399
+ * This hook is useful for scenarios where you need to perform setup operations that are common to all tests in a suite, such as initializing a database connection or setting up a test environment.
400
+ *
401
+ * **Note:** The `beforeAll` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
402
+ *
403
+ * @param {Function} fn - The callback function to be executed before all tests.
404
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
405
+ * @returns {void}
406
+ * @example
407
+ * ```ts
408
+ * // Example of using beforeAll to set up a database connection
409
+ * beforeAll(async () => {
410
+ * await database.connect();
411
+ * });
412
+ * ```
413
+ */
414
+ function beforeAll(fn, timeout = getDefaultHookTimeout()) {
415
+ assertTypes(fn, "\"beforeAll\" callback", ["function"]);
416
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
417
+ return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(fn, timeout, true, stackTraceError), {
418
+ [CLEANUP_TIMEOUT_KEY]: timeout,
419
+ [CLEANUP_STACK_TRACE_KEY]: stackTraceError
420
+ }));
421
+ }
422
+ /**
423
+ * Registers a callback function to be executed once after all tests within the current suite have completed.
424
+ * This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
425
+ *
426
+ * **Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
427
+ *
428
+ * @param {Function} fn - The callback function to be executed after all tests.
429
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
430
+ * @returns {void}
431
+ * @example
432
+ * ```ts
433
+ * // Example of using afterAll to close a database connection
434
+ * afterAll(async () => {
435
+ * await database.disconnect();
436
+ * });
437
+ * ```
438
+ */
439
+ function afterAll(fn, timeout) {
440
+ assertTypes(fn, "\"afterAll\" callback", ["function"]);
441
+ return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
442
+ }
443
+ /**
444
+ * Registers a callback function to be executed before each test within the current suite.
445
+ * This hook is useful for scenarios where you need to reset or reinitialize the test environment before each test runs, such as resetting database states, clearing caches, or reinitializing variables.
446
+ *
447
+ * **Note:** The `beforeEach` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
448
+ *
449
+ * @param {Function} fn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.
450
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
451
+ * @returns {void}
452
+ * @example
453
+ * ```ts
454
+ * // Example of using beforeEach to reset a database state
455
+ * beforeEach(async () => {
456
+ * await database.reset();
457
+ * });
458
+ * ```
459
+ */
460
+ function beforeEach(fn, timeout = getDefaultHookTimeout()) {
461
+ assertTypes(fn, "\"beforeEach\" callback", ["function"]);
462
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
463
+ const runner = getRunner();
464
+ return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
465
+ [CLEANUP_TIMEOUT_KEY]: timeout,
466
+ [CLEANUP_STACK_TRACE_KEY]: stackTraceError
467
+ }));
468
+ }
469
+ /**
470
+ * Registers a callback function to be executed after each test within the current suite has completed.
471
+ * This hook is useful for scenarios where you need to clean up or reset the test environment after each test runs, such as deleting temporary files, clearing test-specific database entries, or resetting mocked functions.
472
+ *
473
+ * **Note:** The `afterEach` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
474
+ *
475
+ * @param {Function} fn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.
476
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
477
+ * @returns {void}
478
+ * @example
479
+ * ```ts
480
+ * // Example of using afterEach to delete temporary files created during a test
481
+ * afterEach(async () => {
482
+ * await fileSystem.deleteTempFiles();
483
+ * });
484
+ * ```
485
+ */
486
+ function afterEach(fn, timeout) {
487
+ assertTypes(fn, "\"afterEach\" callback", ["function"]);
488
+ const runner = getRunner();
489
+ return getCurrentSuite().on("afterEach", withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
490
+ }
491
+ /**
492
+ * Registers a callback function to be executed when a test fails within the current suite.
493
+ * This function allows for custom actions to be performed in response to test failures, such as logging, cleanup, or additional diagnostics.
494
+ *
495
+ * **Note:** The `onTestFailed` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
496
+ *
497
+ * @param {Function} fn - The callback function to be executed upon a test failure. The function receives the test result (including errors).
498
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
499
+ * @throws {Error} Throws an error if the function is not called within a test.
500
+ * @returns {void}
501
+ * @example
502
+ * ```ts
503
+ * // Example of using onTestFailed to log failure details
504
+ * onTestFailed(({ errors }) => {
505
+ * console.log(`Test failed: ${test.name}`, errors);
506
+ * });
507
+ * ```
508
+ */
509
+ const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
510
+ test.onFailed || (test.onFailed = []);
511
+ test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
512
+ });
513
+ /**
514
+ * Registers a callback function to be executed when the current test finishes, regardless of the outcome (pass or fail).
515
+ * This function is ideal for performing actions that should occur after every test execution, such as cleanup, logging, or resetting shared resources.
516
+ *
517
+ * This hook is useful if you have access to a resource in the test itself and you want to clean it up after the test finishes. It is a more compact way to clean up resources than using the combination of `beforeEach` and `afterEach`.
518
+ *
519
+ * **Note:** The `onTestFinished` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
520
+ *
521
+ * **Note:** The `onTestFinished` hook is not called if the test is canceled with a dynamic `ctx.skip()` call.
522
+ *
523
+ * @param {Function} fn - The callback function to be executed after a test finishes. The function can receive parameters providing details about the completed test, including its success or failure status.
524
+ * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
525
+ * @throws {Error} Throws an error if the function is not called within a test.
526
+ * @returns {void}
527
+ * @example
528
+ * ```ts
529
+ * // Example of using onTestFinished for cleanup
530
+ * const db = await connectToDatabase();
531
+ * onTestFinished(async () => {
532
+ * await db.disconnect();
533
+ * });
534
+ * ```
535
+ */
536
+ const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
537
+ test.onFinished || (test.onFinished = []);
538
+ test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
539
+ });
540
+ function createTestHook(name, handler) {
541
+ return (fn, timeout) => {
542
+ assertTypes(fn, `"${name}" callback`, ["function"]);
543
+ const current = getCurrentTest();
544
+ if (!current) {
545
+ throw new Error(`Hook ${name}() can only be called inside a test`);
546
+ }
547
+ return handler(current, fn, timeout);
548
+ };
549
+ }
550
+
393
551
  /**
394
552
  * Creates a suite of tests, allowing for grouping and hierarchical organization of tests.
395
553
  * Suites can contain both tests and other suites, enabling complex test structures.
@@ -610,7 +768,8 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
610
768
  repeats: options.repeats,
611
769
  mode: options.only ? "only" : options.skip ? "skip" : options.todo ? "todo" : "run",
612
770
  meta: options.meta ?? Object.create(null),
613
- annotations: []
771
+ annotations: [],
772
+ artifacts: []
614
773
  };
615
774
  const handler = options.handler;
616
775
  if (task.mode === "run" && !handler) {
@@ -1015,86 +1174,264 @@ function formatTemplateString(cases, args) {
1015
1174
  return res;
1016
1175
  }
1017
1176
 
1018
- const now$2 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1019
- async function collectTests(specs, runner) {
1020
- const files = [];
1021
- const config = runner.config;
1022
- for (const spec of specs) {
1023
- var _runner$onCollectStar;
1024
- const filepath = typeof spec === "string" ? spec : spec.filepath;
1025
- const testLocations = typeof spec === "string" ? undefined : spec.testLocations;
1026
- const file = createFileTask(filepath, config.root, config.name, runner.pool);
1027
- setFileContext(file, Object.create(null));
1028
- file.shuffle = config.sequence.shuffle;
1029
- (_runner$onCollectStar = runner.onCollectStart) === null || _runner$onCollectStar === void 0 ? void 0 : _runner$onCollectStar.call(runner, file);
1030
- clearCollectorContext(filepath, runner);
1031
- try {
1032
- var _runner$getImportDura;
1033
- const setupFiles = toArray(config.setupFiles);
1034
- if (setupFiles.length) {
1035
- const setupStart = now$2();
1036
- await runSetupFiles(config, setupFiles, runner);
1037
- const setupEnd = now$2();
1038
- file.setupDuration = setupEnd - setupStart;
1039
- } else {
1040
- file.setupDuration = 0;
1041
- }
1042
- const collectStart = now$2();
1043
- await runner.importFile(filepath, "collect");
1044
- const durations = (_runner$getImportDura = runner.getImportDurations) === null || _runner$getImportDura === void 0 ? void 0 : _runner$getImportDura.call(runner);
1045
- if (durations) {
1046
- file.importDurations = durations;
1177
+ const now$2 = Date.now;
1178
+ const collectorContext = {
1179
+ tasks: [],
1180
+ currentSuite: null
1181
+ };
1182
+ function collectTask(task) {
1183
+ var _collectorContext$cur;
1184
+ (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
1185
+ }
1186
+ async function runWithSuite(suite, fn) {
1187
+ const prev = collectorContext.currentSuite;
1188
+ collectorContext.currentSuite = suite;
1189
+ await fn();
1190
+ collectorContext.currentSuite = prev;
1191
+ }
1192
+ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
1193
+ if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY) {
1194
+ return fn;
1195
+ }
1196
+ const { setTimeout, clearTimeout } = getSafeTimers();
1197
+ // this function name is used to filter error in test/cli/test/fails.test.ts
1198
+ return (function runWithTimeout(...args) {
1199
+ const startTime = now$2();
1200
+ const runner = getRunner();
1201
+ runner._currentTaskStartTime = startTime;
1202
+ runner._currentTaskTimeout = timeout;
1203
+ return new Promise((resolve_, reject_) => {
1204
+ var _timer$unref;
1205
+ const timer = setTimeout(() => {
1206
+ clearTimeout(timer);
1207
+ rejectTimeoutError();
1208
+ }, timeout);
1209
+ // `unref` might not exist in browser
1210
+ (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
1211
+ function rejectTimeoutError() {
1212
+ const error = makeTimeoutError(isHook, timeout, stackTraceError);
1213
+ onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout(args, error);
1214
+ reject_(error);
1047
1215
  }
1048
- const defaultTasks = await getDefaultSuite().collect(file);
1049
- const fileHooks = createSuiteHooks();
1050
- mergeHooks(fileHooks, getHooks(defaultTasks));
1051
- for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) {
1052
- if (c.type === "test" || c.type === "suite") {
1053
- file.tasks.push(c);
1054
- } else if (c.type === "collector") {
1055
- const suite = await c.collect(file);
1056
- if (suite.name || suite.tasks.length) {
1057
- mergeHooks(fileHooks, getHooks(suite));
1058
- file.tasks.push(suite);
1059
- }
1060
- } else {
1061
- // check that types are exhausted
1062
- c;
1216
+ function resolve(result) {
1217
+ runner._currentTaskStartTime = undefined;
1218
+ runner._currentTaskTimeout = undefined;
1219
+ clearTimeout(timer);
1220
+ // if test/hook took too long in microtask, setTimeout won't be triggered,
1221
+ // but we still need to fail the test, see
1222
+ // https://github.com/vitest-dev/vitest/issues/2920
1223
+ if (now$2() - startTime >= timeout) {
1224
+ rejectTimeoutError();
1225
+ return;
1063
1226
  }
1227
+ resolve_(result);
1064
1228
  }
1065
- setHooks(file, fileHooks);
1066
- file.collectDuration = now$2() - collectStart;
1067
- } catch (e) {
1068
- var _runner$getImportDura2;
1069
- const error = processError(e);
1070
- file.result = {
1071
- state: "fail",
1072
- errors: [error]
1073
- };
1074
- const durations = (_runner$getImportDura2 = runner.getImportDurations) === null || _runner$getImportDura2 === void 0 ? void 0 : _runner$getImportDura2.call(runner);
1075
- if (durations) {
1076
- file.importDurations = durations;
1229
+ function reject(error) {
1230
+ runner._currentTaskStartTime = undefined;
1231
+ runner._currentTaskTimeout = undefined;
1232
+ clearTimeout(timer);
1233
+ reject_(error);
1077
1234
  }
1078
- }
1079
- calculateSuiteHash(file);
1080
- const hasOnlyTasks = someTasksAreOnly(file);
1081
- interpretTaskModes(file, config.testNamePattern, testLocations, hasOnlyTasks, false, config.allowOnly);
1082
- if (file.mode === "queued") {
1083
- file.mode = "run";
1084
- }
1085
- files.push(file);
1086
- }
1087
- return files;
1088
- }
1089
- function mergeHooks(baseHooks, hooks) {
1090
- for (const _key in hooks) {
1091
- const key = _key;
1092
- baseHooks[key].push(...hooks[key]);
1093
- }
1094
- return baseHooks;
1235
+ // sync test/hook will be caught by try/catch
1236
+ try {
1237
+ const result = fn(...args);
1238
+ // the result is a thenable, we don't wrap this in Promise.resolve
1239
+ // to avoid creating new promises
1240
+ if (typeof result === "object" && result != null && typeof result.then === "function") {
1241
+ result.then(resolve, reject);
1242
+ } else {
1243
+ resolve(result);
1244
+ }
1245
+ }
1246
+ // user sync test/hook throws an error
1247
+ catch (error) {
1248
+ reject(error);
1249
+ }
1250
+ });
1251
+ });
1252
+ }
1253
+ const abortControllers = new WeakMap();
1254
+ function abortIfTimeout([context], error) {
1255
+ if (context) {
1256
+ abortContextSignal(context, error);
1257
+ }
1258
+ }
1259
+ function abortContextSignal(context, error) {
1260
+ const abortController = abortControllers.get(context);
1261
+ abortController === null || abortController === void 0 ? void 0 : abortController.abort(error);
1262
+ }
1263
+ function createTestContext(test, runner) {
1264
+ var _runner$extendTaskCon;
1265
+ const context = function() {
1266
+ throw new Error("done() callback is deprecated, use promise instead");
1267
+ };
1268
+ let abortController = abortControllers.get(context);
1269
+ if (!abortController) {
1270
+ abortController = new AbortController();
1271
+ abortControllers.set(context, abortController);
1272
+ }
1273
+ context.signal = abortController.signal;
1274
+ context.task = test;
1275
+ context.skip = (condition, note) => {
1276
+ if (condition === false) {
1277
+ // do nothing
1278
+ return undefined;
1279
+ }
1280
+ test.result ?? (test.result = { state: "skip" });
1281
+ test.result.pending = true;
1282
+ throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
1283
+ };
1284
+ context.annotate = ((message, type, attachment) => {
1285
+ if (test.result && test.result.state !== "run") {
1286
+ throw new Error(`Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.`);
1287
+ }
1288
+ const annotation = {
1289
+ message,
1290
+ type: typeof type === "object" || type === undefined ? "notice" : type
1291
+ };
1292
+ const annotationAttachment = typeof type === "object" ? type : attachment;
1293
+ if (annotationAttachment) {
1294
+ annotation.attachment = annotationAttachment;
1295
+ manageArtifactAttachment(annotation.attachment);
1296
+ }
1297
+ return recordAsyncOperation(test, recordArtifact(test, {
1298
+ type: "internal:annotation",
1299
+ annotation
1300
+ }).then(async ({ annotation }) => {
1301
+ if (!runner.onTestAnnotate) {
1302
+ throw new Error(`Test runner doesn't support test annotations.`);
1303
+ }
1304
+ await finishSendTasksUpdate(runner);
1305
+ const resolvedAnnotation = await runner.onTestAnnotate(test, annotation);
1306
+ test.annotations.push(resolvedAnnotation);
1307
+ return resolvedAnnotation;
1308
+ }));
1309
+ });
1310
+ context.onTestFailed = (handler, timeout) => {
1311
+ test.onFailed || (test.onFailed = []);
1312
+ test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1313
+ };
1314
+ context.onTestFinished = (handler, timeout) => {
1315
+ test.onFinished || (test.onFinished = []);
1316
+ test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1317
+ };
1318
+ return ((_runner$extendTaskCon = runner.extendTaskContext) === null || _runner$extendTaskCon === void 0 ? void 0 : _runner$extendTaskCon.call(runner, context)) || context;
1319
+ }
1320
+ function makeTimeoutError(isHook, timeout, stackTraceError) {
1321
+ const message = `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
1322
+ const error = new Error(message);
1323
+ if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
1324
+ error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
1325
+ }
1326
+ return error;
1327
+ }
1328
+ const fileContexts = new WeakMap();
1329
+ function getFileContext(file) {
1330
+ const context = fileContexts.get(file);
1331
+ if (!context) {
1332
+ throw new Error(`Cannot find file context for ${file.name}`);
1333
+ }
1334
+ return context;
1335
+ }
1336
+ function setFileContext(file, context) {
1337
+ fileContexts.set(file, context);
1338
+ }
1339
+
1340
+ async function runSetupFiles(config, files, runner) {
1341
+ if (config.sequence.setupFiles === "parallel") {
1342
+ await Promise.all(files.map(async (fsPath) => {
1343
+ await runner.importFile(fsPath, "setup");
1344
+ }));
1345
+ } else {
1346
+ for (const fsPath of files) {
1347
+ await runner.importFile(fsPath, "setup");
1348
+ }
1349
+ }
1095
1350
  }
1096
1351
 
1097
1352
  const now$1 = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1353
+ async function collectTests(specs, runner) {
1354
+ const files = [];
1355
+ const config = runner.config;
1356
+ const $ = runner.trace;
1357
+ for (const spec of specs) {
1358
+ const filepath = typeof spec === "string" ? spec : spec.filepath;
1359
+ await $("collect_spec", { "code.file.path": filepath }, async () => {
1360
+ var _runner$onCollectStar;
1361
+ const testLocations = typeof spec === "string" ? undefined : spec.testLocations;
1362
+ const file = createFileTask(filepath, config.root, config.name, runner.pool);
1363
+ setFileContext(file, Object.create(null));
1364
+ file.shuffle = config.sequence.shuffle;
1365
+ (_runner$onCollectStar = runner.onCollectStart) === null || _runner$onCollectStar === void 0 ? void 0 : _runner$onCollectStar.call(runner, file);
1366
+ clearCollectorContext(filepath, runner);
1367
+ try {
1368
+ var _runner$getImportDura;
1369
+ const setupFiles = toArray(config.setupFiles);
1370
+ if (setupFiles.length) {
1371
+ const setupStart = now$1();
1372
+ await runSetupFiles(config, setupFiles, runner);
1373
+ const setupEnd = now$1();
1374
+ file.setupDuration = setupEnd - setupStart;
1375
+ } else {
1376
+ file.setupDuration = 0;
1377
+ }
1378
+ const collectStart = now$1();
1379
+ await runner.importFile(filepath, "collect");
1380
+ const durations = (_runner$getImportDura = runner.getImportDurations) === null || _runner$getImportDura === void 0 ? void 0 : _runner$getImportDura.call(runner);
1381
+ if (durations) {
1382
+ file.importDurations = durations;
1383
+ }
1384
+ const defaultTasks = await getDefaultSuite().collect(file);
1385
+ const fileHooks = createSuiteHooks();
1386
+ mergeHooks(fileHooks, getHooks(defaultTasks));
1387
+ for (const c of [...defaultTasks.tasks, ...collectorContext.tasks]) {
1388
+ if (c.type === "test" || c.type === "suite") {
1389
+ file.tasks.push(c);
1390
+ } else if (c.type === "collector") {
1391
+ const suite = await c.collect(file);
1392
+ if (suite.name || suite.tasks.length) {
1393
+ mergeHooks(fileHooks, getHooks(suite));
1394
+ file.tasks.push(suite);
1395
+ }
1396
+ } else {
1397
+ // check that types are exhausted
1398
+ c;
1399
+ }
1400
+ }
1401
+ setHooks(file, fileHooks);
1402
+ file.collectDuration = now$1() - collectStart;
1403
+ } catch (e) {
1404
+ var _runner$getImportDura2;
1405
+ const error = processError(e);
1406
+ file.result = {
1407
+ state: "fail",
1408
+ errors: [error]
1409
+ };
1410
+ const durations = (_runner$getImportDura2 = runner.getImportDurations) === null || _runner$getImportDura2 === void 0 ? void 0 : _runner$getImportDura2.call(runner);
1411
+ if (durations) {
1412
+ file.importDurations = durations;
1413
+ }
1414
+ }
1415
+ calculateSuiteHash(file);
1416
+ const hasOnlyTasks = someTasksAreOnly(file);
1417
+ interpretTaskModes(file, config.testNamePattern, testLocations, hasOnlyTasks, false, config.allowOnly);
1418
+ if (file.mode === "queued") {
1419
+ file.mode = "run";
1420
+ }
1421
+ files.push(file);
1422
+ });
1423
+ }
1424
+ return files;
1425
+ }
1426
+ function mergeHooks(baseHooks, hooks) {
1427
+ for (const _key in hooks) {
1428
+ const key = _key;
1429
+ baseHooks[key].push(...hooks[key]);
1430
+ }
1431
+ return baseHooks;
1432
+ }
1433
+
1434
+ const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1098
1435
  const unixNow = Date.now;
1099
1436
  const { clearTimeout, setTimeout } = getSafeTimers();
1100
1437
  function updateSuiteHookState(task, name, state, runner) {
@@ -1275,7 +1612,7 @@ async function runTest(test, runner) {
1275
1612
  updateTask("test-failed-early", test, runner);
1276
1613
  return;
1277
1614
  }
1278
- const start = now$1();
1615
+ const start = now();
1279
1616
  test.result = {
1280
1617
  state: "run",
1281
1618
  startTime: unixNow(),
@@ -1285,11 +1622,12 @@ async function runTest(test, runner) {
1285
1622
  const cleanupRunningTest = addRunningTest(test);
1286
1623
  setCurrentTest(test);
1287
1624
  const suite = test.suite || test.file;
1625
+ const $ = runner.trace;
1288
1626
  const repeats = test.repeats ?? 0;
1289
1627
  for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
1290
1628
  const retry = test.retry ?? 0;
1291
1629
  for (let retryCount = 0; retryCount <= retry; retryCount++) {
1292
- var _runner$onAfterRetryT, _test$result2, _test$result3;
1630
+ var _test$onFinished, _test$onFailed, _runner$onAfterRetryT, _test$result2, _test$result3;
1293
1631
  let beforeEachCleanups = [];
1294
1632
  try {
1295
1633
  var _runner$onBeforeTryTa, _runner$onAfterTryTas;
@@ -1298,15 +1636,15 @@ async function runTest(test, runner) {
1298
1636
  repeats: repeatCount
1299
1637
  }));
1300
1638
  test.result.repeatCount = repeatCount;
1301
- beforeEachCleanups = await callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]);
1639
+ beforeEachCleanups = await $("test.beforeEach", () => callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]));
1302
1640
  if (runner.runTask) {
1303
- await runner.runTask(test);
1641
+ await $("test.callback", () => runner.runTask(test));
1304
1642
  } else {
1305
1643
  const fn = getFn(test);
1306
1644
  if (!fn) {
1307
1645
  throw new Error("Test function is not found. Did you add it using `setFn`?");
1308
1646
  }
1309
- await fn();
1647
+ await $("test.callback", () => fn());
1310
1648
  }
1311
1649
  await ((_runner$onAfterTryTas = runner.onAfterTryTask) === null || _runner$onAfterTryTas === void 0 ? void 0 : _runner$onAfterTryTas.call(runner, test, {
1312
1650
  retry: retryCount,
@@ -1329,15 +1667,19 @@ async function runTest(test, runner) {
1329
1667
  failTask(test.result, e, runner.config.diffOptions);
1330
1668
  }
1331
1669
  try {
1332
- await callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]);
1333
- await callCleanupHooks(runner, beforeEachCleanups);
1670
+ await $("test.afterEach", () => callSuiteHook(suite, test, "afterEach", runner, [test.context, suite]));
1671
+ if (beforeEachCleanups.length) {
1672
+ await $("test.cleanup", () => callCleanupHooks(runner, beforeEachCleanups));
1673
+ }
1334
1674
  await callFixtureCleanup(test.context);
1335
1675
  } catch (e) {
1336
1676
  failTask(test.result, e, runner.config.diffOptions);
1337
1677
  }
1338
- await callTestHooks(runner, test, test.onFinished || [], "stack");
1339
- if (test.result.state === "fail") {
1340
- await callTestHooks(runner, test, test.onFailed || [], runner.config.sequence.hooks);
1678
+ if ((_test$onFinished = test.onFinished) === null || _test$onFinished === void 0 ? void 0 : _test$onFinished.length) {
1679
+ await $("test.onFinished", () => callTestHooks(runner, test, test.onFinished, "stack"));
1680
+ }
1681
+ if (test.result.state === "fail" && ((_test$onFailed = test.onFailed) === null || _test$onFailed === void 0 ? void 0 : _test$onFailed.length)) {
1682
+ await $("test.onFailed", () => callTestHooks(runner, test, test.onFailed, runner.config.sequence.hooks));
1341
1683
  }
1342
1684
  test.onFailed = undefined;
1343
1685
  test.onFinished = undefined;
@@ -1353,7 +1695,7 @@ async function runTest(test, runner) {
1353
1695
  state: "skip",
1354
1696
  note: (_test$result4 = test.result) === null || _test$result4 === void 0 ? void 0 : _test$result4.note,
1355
1697
  pending: true,
1356
- duration: now$1() - start
1698
+ duration: now() - start
1357
1699
  };
1358
1700
  updateTask("test-finished", test, runner);
1359
1701
  setCurrentTest(undefined);
@@ -1385,7 +1727,7 @@ async function runTest(test, runner) {
1385
1727
  }
1386
1728
  cleanupRunningTest();
1387
1729
  setCurrentTest(undefined);
1388
- test.result.duration = now$1() - start;
1730
+ test.result.duration = now() - start;
1389
1731
  await ((_runner$onAfterRunTas = runner.onAfterRunTask) === null || _runner$onAfterRunTas === void 0 ? void 0 : _runner$onAfterRunTas.call(runner, test));
1390
1732
  updateTask("test-finished", test, runner);
1391
1733
  }
@@ -1426,12 +1768,13 @@ async function runSuite(suite, runner) {
1426
1768
  updateTask("suite-failed-early", suite, runner);
1427
1769
  return;
1428
1770
  }
1429
- const start = now$1();
1771
+ const start = now();
1430
1772
  const mode = suite.mode;
1431
1773
  suite.result = {
1432
1774
  state: mode === "skip" || mode === "todo" ? mode : "run",
1433
1775
  startTime: unixNow()
1434
1776
  };
1777
+ const $ = runner.trace;
1435
1778
  updateTask("suite-prepare", suite, runner);
1436
1779
  let beforeAllCleanups = [];
1437
1780
  if (suite.mode === "skip") {
@@ -1444,7 +1787,7 @@ async function runSuite(suite, runner) {
1444
1787
  var _runner$onAfterRunSui;
1445
1788
  try {
1446
1789
  try {
1447
- beforeAllCleanups = await callSuiteHook(suite, suite, "beforeAll", runner, [suite]);
1790
+ beforeAllCleanups = await $("suite.beforeAll", () => callSuiteHook(suite, suite, "beforeAll", runner, [suite]));
1448
1791
  } catch (e) {
1449
1792
  markTasksAsSkipped(suite, runner);
1450
1793
  throw e;
@@ -1474,8 +1817,10 @@ async function runSuite(suite, runner) {
1474
1817
  failTask(suite.result, e, runner.config.diffOptions);
1475
1818
  }
1476
1819
  try {
1477
- await callSuiteHook(suite, suite, "afterAll", runner, [suite]);
1478
- await callCleanupHooks(runner, beforeAllCleanups);
1820
+ await $("suite.afterAll", () => callSuiteHook(suite, suite, "afterAll", runner, [suite]));
1821
+ if (beforeAllCleanups.length) {
1822
+ await $("suite.cleanup", () => callCleanupHooks(runner, beforeAllCleanups));
1823
+ }
1479
1824
  if (suite.file === suite) {
1480
1825
  const context = getFileContext(suite);
1481
1826
  await callFixtureCleanup(context);
@@ -1497,17 +1842,37 @@ async function runSuite(suite, runner) {
1497
1842
  suite.result.state = "pass";
1498
1843
  }
1499
1844
  }
1500
- suite.result.duration = now$1() - start;
1845
+ suite.result.duration = now() - start;
1501
1846
  await ((_runner$onAfterRunSui = runner.onAfterRunSuite) === null || _runner$onAfterRunSui === void 0 ? void 0 : _runner$onAfterRunSui.call(runner, suite));
1502
1847
  updateTask("suite-finished", suite, runner);
1503
1848
  }
1504
1849
  }
1505
1850
  let limitMaxConcurrency;
1506
1851
  async function runSuiteChild(c, runner) {
1852
+ const $ = runner.trace;
1507
1853
  if (c.type === "test") {
1508
- return limitMaxConcurrency(() => runTest(c, runner));
1854
+ return limitMaxConcurrency(() => {
1855
+ var _c$location, _c$location2;
1856
+ return $("run.test", {
1857
+ "vitest.test.id": c.id,
1858
+ "vitest.test.name": c.name,
1859
+ "vitest.test.mode": c.mode,
1860
+ "vitest.test.timeout": c.timeout,
1861
+ "code.file.path": c.file.filepath,
1862
+ "code.line.number": (_c$location = c.location) === null || _c$location === void 0 ? void 0 : _c$location.line,
1863
+ "code.column.number": (_c$location2 = c.location) === null || _c$location2 === void 0 ? void 0 : _c$location2.column
1864
+ }, () => runTest(c, runner));
1865
+ });
1509
1866
  } else if (c.type === "suite") {
1510
- return runSuite(c, runner);
1867
+ var _c$location3, _c$location4;
1868
+ return $("run.suite", {
1869
+ "vitest.suite.id": c.id,
1870
+ "vitest.suite.name": c.name,
1871
+ "vitest.suite.mode": c.mode,
1872
+ "code.file.path": c.file.filepath,
1873
+ "code.line.number": (_c$location3 = c.location) === null || _c$location3 === void 0 ? void 0 : _c$location3.line,
1874
+ "code.column.number": (_c$location4 = c.location) === null || _c$location4 === void 0 ? void 0 : _c$location4.column
1875
+ }, () => runSuite(c, runner));
1511
1876
  }
1512
1877
  }
1513
1878
  async function runFiles(files, runner) {
@@ -1523,12 +1888,22 @@ async function runFiles(files, runner) {
1523
1888
  };
1524
1889
  }
1525
1890
  }
1526
- await runSuite(file, runner);
1891
+ await runner.trace("run.spec", {
1892
+ "code.file.path": file.filepath,
1893
+ "vitest.suite.tasks.length": file.tasks.length
1894
+ }, () => runSuite(file, runner));
1527
1895
  }
1528
1896
  }
1529
1897
  const workerRunners = new WeakSet();
1898
+ function defaultTrace(_, attributes, cb) {
1899
+ if (typeof attributes === "function") {
1900
+ return attributes();
1901
+ }
1902
+ return cb();
1903
+ }
1530
1904
  async function startTests(specs, runner) {
1531
1905
  var _runner$cancel;
1906
+ runner.trace ?? (runner.trace = defaultTrace);
1532
1907
  const cancel = (_runner$cancel = runner.cancel) === null || _runner$cancel === void 0 ? void 0 : _runner$cancel.bind(runner);
1533
1908
  // Ideally, we need to have an event listener for this, but only have a runner here.
1534
1909
  // Adding another onCancel felt wrong (maybe it needs to be refactored)
@@ -1566,6 +1941,7 @@ async function startTests(specs, runner) {
1566
1941
  }
1567
1942
  async function publicCollect(specs, runner) {
1568
1943
  var _runner$onBeforeColle2, _runner$onCollected2;
1944
+ runner.trace ?? (runner.trace = defaultTrace);
1569
1945
  const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
1570
1946
  await ((_runner$onBeforeColle2 = runner.onBeforeCollect) === null || _runner$onBeforeColle2 === void 0 ? void 0 : _runner$onBeforeColle2.call(runner, paths));
1571
1947
  const files = await collectTests(specs, runner);
@@ -1573,189 +1949,72 @@ async function publicCollect(specs, runner) {
1573
1949
  return files;
1574
1950
  }
1575
1951
 
1576
- const now = Date.now;
1577
- const collectorContext = {
1578
- tasks: [],
1579
- currentSuite: null
1580
- };
1581
- function collectTask(task) {
1582
- var _collectorContext$cur;
1583
- (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
1584
- }
1585
- async function runWithSuite(suite, fn) {
1586
- const prev = collectorContext.currentSuite;
1587
- collectorContext.currentSuite = suite;
1588
- await fn();
1589
- collectorContext.currentSuite = prev;
1590
- }
1591
- function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
1592
- if (timeout <= 0 || timeout === Number.POSITIVE_INFINITY) {
1593
- return fn;
1594
- }
1595
- const { setTimeout, clearTimeout } = getSafeTimers();
1596
- // this function name is used to filter error in test/cli/test/fails.test.ts
1597
- return (function runWithTimeout(...args) {
1598
- const startTime = now();
1599
- const runner = getRunner();
1600
- runner._currentTaskStartTime = startTime;
1601
- runner._currentTaskTimeout = timeout;
1602
- return new Promise((resolve_, reject_) => {
1603
- var _timer$unref;
1604
- const timer = setTimeout(() => {
1605
- clearTimeout(timer);
1606
- rejectTimeoutError();
1607
- }, timeout);
1608
- // `unref` might not exist in browser
1609
- (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
1610
- function rejectTimeoutError() {
1611
- const error = makeTimeoutError(isHook, timeout, stackTraceError);
1612
- onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout(args, error);
1613
- reject_(error);
1614
- }
1615
- function resolve(result) {
1616
- runner._currentTaskStartTime = undefined;
1617
- runner._currentTaskTimeout = undefined;
1618
- clearTimeout(timer);
1619
- // if test/hook took too long in microtask, setTimeout won't be triggered,
1620
- // but we still need to fail the test, see
1621
- // https://github.com/vitest-dev/vitest/issues/2920
1622
- if (now() - startTime >= timeout) {
1623
- rejectTimeoutError();
1624
- return;
1625
- }
1626
- resolve_(result);
1627
- }
1628
- function reject(error) {
1629
- runner._currentTaskStartTime = undefined;
1630
- runner._currentTaskTimeout = undefined;
1631
- clearTimeout(timer);
1632
- reject_(error);
1633
- }
1634
- // sync test/hook will be caught by try/catch
1635
- try {
1636
- const result = fn(...args);
1637
- // the result is a thenable, we don't wrap this in Promise.resolve
1638
- // to avoid creating new promises
1639
- if (typeof result === "object" && result != null && typeof result.then === "function") {
1640
- result.then(resolve, reject);
1641
- } else {
1642
- resolve(result);
1643
- }
1644
- }
1645
- // user sync test/hook throws an error
1646
- catch (error) {
1647
- reject(error);
1648
- }
1649
- });
1650
- });
1651
- }
1652
- const abortControllers = new WeakMap();
1653
- function abortIfTimeout([context], error) {
1654
- if (context) {
1655
- abortContextSignal(context, error);
1656
- }
1657
- }
1658
- function abortContextSignal(context, error) {
1659
- const abortController = abortControllers.get(context);
1660
- abortController === null || abortController === void 0 ? void 0 : abortController.abort(error);
1661
- }
1662
- function createTestContext(test, runner) {
1663
- var _runner$extendTaskCon;
1664
- const context = function() {
1665
- throw new Error("done() callback is deprecated, use promise instead");
1666
- };
1667
- let abortController = abortControllers.get(context);
1668
- if (!abortController) {
1669
- abortController = new AbortController();
1670
- abortControllers.set(context, abortController);
1671
- }
1672
- context.signal = abortController.signal;
1673
- context.task = test;
1674
- context.skip = (condition, note) => {
1675
- if (condition === false) {
1676
- // do nothing
1677
- return undefined;
1678
- }
1679
- test.result ?? (test.result = { state: "skip" });
1680
- test.result.pending = true;
1681
- throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
1682
- };
1683
- async function annotate(message, location, type, attachment) {
1684
- const annotation = {
1685
- message,
1686
- type: type || "notice"
1952
+ /**
1953
+ * @experimental
1954
+ * @advanced
1955
+ *
1956
+ * Records a custom test artifact during test execution.
1957
+ *
1958
+ * This function allows you to attach structured data, files, or metadata to a test.
1959
+ *
1960
+ * Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
1961
+ *
1962
+ * @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
1963
+ * @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
1964
+ *
1965
+ * @returns A promise that resolves to the recorded artifact with location injected
1966
+ *
1967
+ * @throws {Error} If called after the test has finished running
1968
+ * @throws {Error} If the test runner doesn't support artifacts
1969
+ *
1970
+ * @example
1971
+ * ```ts
1972
+ * // In a custom assertion
1973
+ * async function toHaveValidSchema(this: MatcherState, actual: unknown) {
1974
+ * const validation = validateSchema(actual)
1975
+ *
1976
+ * await recordArtifact(this.task, {
1977
+ * type: 'my-plugin:schema-validation',
1978
+ * passed: validation.valid,
1979
+ * errors: validation.errors,
1980
+ * })
1981
+ *
1982
+ * return { pass: validation.valid, message: () => '...' }
1983
+ * }
1984
+ * ```
1985
+ */
1986
+ async function recordArtifact(task, artifact) {
1987
+ const runner = getRunner();
1988
+ if (task.result && task.result.state !== "run") {
1989
+ throw new Error(`Cannot record a test artifact outside of the test run. The test "${task.name}" finished running with the "${task.result.state}" state already.`);
1990
+ }
1991
+ const stack = findTestFileStackTrace(task.file.filepath, new Error("STACK_TRACE").stack);
1992
+ if (stack) {
1993
+ artifact.location = {
1994
+ file: stack.file,
1995
+ line: stack.line,
1996
+ column: stack.column
1687
1997
  };
1688
- if (attachment) {
1689
- if (attachment.body == null && !attachment.path) {
1690
- throw new TypeError(`Test attachment requires "body" or "path" to be set. Both are missing.`);
1691
- }
1692
- if (attachment.body && attachment.path) {
1693
- throw new TypeError(`Test attachment requires only one of "body" or "path" to be set. Both are specified.`);
1694
- }
1695
- annotation.attachment = attachment;
1696
- // convert to a string so it's easier to serialise
1697
- if (attachment.body instanceof Uint8Array) {
1698
- attachment.body = encodeUint8Array(attachment.body);
1699
- }
1700
- }
1701
- if (location) {
1702
- annotation.location = location;
1703
- }
1704
- if (!runner.onTestAnnotate) {
1705
- throw new Error(`Test runner doesn't support test annotations.`);
1998
+ if (artifact.type === "internal:annotation") {
1999
+ artifact.annotation.location = artifact.location;
1706
2000
  }
1707
- await finishSendTasksUpdate(runner);
1708
- const resolvedAnnotation = await runner.onTestAnnotate(test, annotation);
1709
- test.annotations.push(resolvedAnnotation);
1710
- return resolvedAnnotation;
1711
2001
  }
1712
- context.annotate = ((message, type, attachment) => {
1713
- if (test.result && test.result.state !== "run") {
1714
- throw new Error(`Cannot annotate tests outside of the test run. The test "${test.name}" finished running with the "${test.result.state}" state already.`);
2002
+ if (Array.isArray(artifact.attachments)) {
2003
+ for (const attachment of artifact.attachments) {
2004
+ manageArtifactAttachment(attachment);
1715
2005
  }
1716
- const stack = findTestFileStackTrace(test.file.filepath, new Error("STACK_TRACE").stack);
1717
- let location;
1718
- if (stack) {
1719
- location = {
1720
- file: stack.file,
1721
- line: stack.line,
1722
- column: stack.column
1723
- };
1724
- }
1725
- if (typeof type === "object") {
1726
- return recordAsyncAnnotation(test, annotate(message, location, undefined, type));
1727
- } else {
1728
- return recordAsyncAnnotation(test, annotate(message, location, type, attachment));
1729
- }
1730
- });
1731
- context.onTestFailed = (handler, timeout) => {
1732
- test.onFailed || (test.onFailed = []);
1733
- test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1734
- };
1735
- context.onTestFinished = (handler, timeout) => {
1736
- test.onFinished || (test.onFinished = []);
1737
- test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1738
- };
1739
- return ((_runner$extendTaskCon = runner.extendTaskContext) === null || _runner$extendTaskCon === void 0 ? void 0 : _runner$extendTaskCon.call(runner, context)) || context;
1740
- }
1741
- function makeTimeoutError(isHook, timeout, stackTraceError) {
1742
- const message = `${isHook ? "Hook" : "Test"} timed out in ${timeout}ms.\nIf this is a long-running ${isHook ? "hook" : "test"}, pass a timeout value as the last argument or configure it globally with "${isHook ? "hookTimeout" : "testTimeout"}".`;
1743
- const error = new Error(message);
1744
- if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
1745
- error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
1746
2006
  }
1747
- return error;
1748
- }
1749
- const fileContexts = new WeakMap();
1750
- function getFileContext(file) {
1751
- const context = fileContexts.get(file);
1752
- if (!context) {
1753
- throw new Error(`Cannot find file context for ${file.name}`);
2007
+ // annotations won't resolve as artifacts for backwards compatibility until next major
2008
+ if (artifact.type === "internal:annotation") {
2009
+ return artifact;
1754
2010
  }
1755
- return context;
1756
- }
1757
- function setFileContext(file, context) {
1758
- fileContexts.set(file, context);
2011
+ if (!runner.onTestArtifactRecord) {
2012
+ throw new Error(`Test runner doesn't support test artifacts.`);
2013
+ }
2014
+ await finishSendTasksUpdate(runner);
2015
+ const resolvedArtifact = await runner.onTestArtifactRecord(task, artifact);
2016
+ task.artifacts.push(resolvedArtifact);
2017
+ return resolvedArtifact;
1759
2018
  }
1760
2019
  const table = [];
1761
2020
  for (let i = 65; i < 91; i++) {
@@ -1799,7 +2058,13 @@ function encodeUint8Array(bytes) {
1799
2058
  }
1800
2059
  return base64;
1801
2060
  }
1802
- function recordAsyncAnnotation(test, promise) {
2061
+ /**
2062
+ * Records an async operation associated with a test task.
2063
+ *
2064
+ * This function tracks promises that should be awaited before a test completes.
2065
+ * The promise is automatically removed from the test's promise list once it settles.
2066
+ */
2067
+ function recordAsyncOperation(test, promise) {
1803
2068
  // if promise is explicitly awaited, remove it from the list
1804
2069
  promise = promise.finally(() => {
1805
2070
  if (!test.promises) {
@@ -1817,175 +2082,27 @@ function recordAsyncAnnotation(test, promise) {
1817
2082
  test.promises.push(promise);
1818
2083
  return promise;
1819
2084
  }
1820
-
1821
- function getDefaultHookTimeout() {
1822
- return getRunner().config.hookTimeout;
1823
- }
1824
- const CLEANUP_TIMEOUT_KEY = Symbol.for("VITEST_CLEANUP_TIMEOUT");
1825
- const CLEANUP_STACK_TRACE_KEY = Symbol.for("VITEST_CLEANUP_STACK_TRACE");
1826
- function getBeforeHookCleanupCallback(hook, result, context) {
1827
- if (typeof result === "function") {
1828
- const timeout = CLEANUP_TIMEOUT_KEY in hook && typeof hook[CLEANUP_TIMEOUT_KEY] === "number" ? hook[CLEANUP_TIMEOUT_KEY] : getDefaultHookTimeout();
1829
- const stackTraceError = CLEANUP_STACK_TRACE_KEY in hook && hook[CLEANUP_STACK_TRACE_KEY] instanceof Error ? hook[CLEANUP_STACK_TRACE_KEY] : undefined;
1830
- return withTimeout(result, timeout, true, stackTraceError, (_, error) => {
1831
- if (context) {
1832
- abortContextSignal(context, error);
1833
- }
1834
- });
1835
- }
1836
- }
1837
- /**
1838
- * Registers a callback function to be executed once before all tests within the current suite.
1839
- * This hook is useful for scenarios where you need to perform setup operations that are common to all tests in a suite, such as initializing a database connection or setting up a test environment.
1840
- *
1841
- * **Note:** The `beforeAll` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
1842
- *
1843
- * @param {Function} fn - The callback function to be executed before all tests.
1844
- * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
1845
- * @returns {void}
1846
- * @example
1847
- * ```ts
1848
- * // Example of using beforeAll to set up a database connection
1849
- * beforeAll(async () => {
1850
- * await database.connect();
1851
- * });
1852
- * ```
1853
- */
1854
- function beforeAll(fn, timeout = getDefaultHookTimeout()) {
1855
- assertTypes(fn, "\"beforeAll\" callback", ["function"]);
1856
- const stackTraceError = new Error("STACK_TRACE_ERROR");
1857
- return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(fn, timeout, true, stackTraceError), {
1858
- [CLEANUP_TIMEOUT_KEY]: timeout,
1859
- [CLEANUP_STACK_TRACE_KEY]: stackTraceError
1860
- }));
1861
- }
1862
- /**
1863
- * Registers a callback function to be executed once after all tests within the current suite have completed.
1864
- * This hook is useful for scenarios where you need to perform cleanup operations after all tests in a suite have run, such as closing database connections or cleaning up temporary files.
1865
- *
1866
- * **Note:** The `afterAll` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
1867
- *
1868
- * @param {Function} fn - The callback function to be executed after all tests.
1869
- * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
1870
- * @returns {void}
1871
- * @example
1872
- * ```ts
1873
- * // Example of using afterAll to close a database connection
1874
- * afterAll(async () => {
1875
- * await database.disconnect();
1876
- * });
1877
- * ```
1878
- */
1879
- function afterAll(fn, timeout) {
1880
- assertTypes(fn, "\"afterAll\" callback", ["function"]);
1881
- return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
1882
- }
1883
2085
  /**
1884
- * Registers a callback function to be executed before each test within the current suite.
1885
- * This hook is useful for scenarios where you need to reset or reinitialize the test environment before each test runs, such as resetting database states, clearing caches, or reinitializing variables.
2086
+ * Validates and prepares a test attachment for serialization.
1886
2087
  *
1887
- * **Note:** The `beforeEach` hooks are executed in the order they are defined one after another. You can configure this by changing the `sequence.hooks` option in the config file.
2088
+ * This function ensures attachments have either `body` or `path` set (but not both), and converts `Uint8Array` bodies to base64-encoded strings for easier serialization.
1888
2089
  *
1889
- * @param {Function} fn - The callback function to be executed before each test. This function receives an `TestContext` parameter if additional test context is needed.
1890
- * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
1891
- * @returns {void}
1892
- * @example
1893
- * ```ts
1894
- * // Example of using beforeEach to reset a database state
1895
- * beforeEach(async () => {
1896
- * await database.reset();
1897
- * });
1898
- * ```
1899
- */
1900
- function beforeEach(fn, timeout = getDefaultHookTimeout()) {
1901
- assertTypes(fn, "\"beforeEach\" callback", ["function"]);
1902
- const stackTraceError = new Error("STACK_TRACE_ERROR");
1903
- const runner = getRunner();
1904
- return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
1905
- [CLEANUP_TIMEOUT_KEY]: timeout,
1906
- [CLEANUP_STACK_TRACE_KEY]: stackTraceError
1907
- }));
1908
- }
1909
- /**
1910
- * Registers a callback function to be executed after each test within the current suite has completed.
1911
- * This hook is useful for scenarios where you need to clean up or reset the test environment after each test runs, such as deleting temporary files, clearing test-specific database entries, or resetting mocked functions.
1912
- *
1913
- * **Note:** The `afterEach` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
2090
+ * @param attachment - The attachment to validate and prepare
1914
2091
  *
1915
- * @param {Function} fn - The callback function to be executed after each test. This function receives an `TestContext` parameter if additional test context is needed.
1916
- * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
1917
- * @returns {void}
1918
- * @example
1919
- * ```ts
1920
- * // Example of using afterEach to delete temporary files created during a test
1921
- * afterEach(async () => {
1922
- * await fileSystem.deleteTempFiles();
1923
- * });
1924
- * ```
2092
+ * @throws {TypeError} If neither `body` nor `path` is provided
2093
+ * @throws {TypeError} If both `body` and `path` are provided
1925
2094
  */
1926
- function afterEach(fn, timeout) {
1927
- assertTypes(fn, "\"afterEach\" callback", ["function"]);
1928
- const runner = getRunner();
1929
- return getCurrentSuite().on("afterEach", withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
1930
- }
1931
- /**
1932
- * Registers a callback function to be executed when a test fails within the current suite.
1933
- * This function allows for custom actions to be performed in response to test failures, such as logging, cleanup, or additional diagnostics.
1934
- *
1935
- * **Note:** The `onTestFailed` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
1936
- *
1937
- * @param {Function} fn - The callback function to be executed upon a test failure. The function receives the test result (including errors).
1938
- * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
1939
- * @throws {Error} Throws an error if the function is not called within a test.
1940
- * @returns {void}
1941
- * @example
1942
- * ```ts
1943
- * // Example of using onTestFailed to log failure details
1944
- * onTestFailed(({ errors }) => {
1945
- * console.log(`Test failed: ${test.name}`, errors);
1946
- * });
1947
- * ```
1948
- */
1949
- const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
1950
- test.onFailed || (test.onFailed = []);
1951
- test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
1952
- });
1953
- /**
1954
- * Registers a callback function to be executed when the current test finishes, regardless of the outcome (pass or fail).
1955
- * This function is ideal for performing actions that should occur after every test execution, such as cleanup, logging, or resetting shared resources.
1956
- *
1957
- * This hook is useful if you have access to a resource in the test itself and you want to clean it up after the test finishes. It is a more compact way to clean up resources than using the combination of `beforeEach` and `afterEach`.
1958
- *
1959
- * **Note:** The `onTestFinished` hooks are running in reverse order of their registration. You can configure this by changing the `sequence.hooks` option in the config file.
1960
- *
1961
- * **Note:** The `onTestFinished` hook is not called if the test is canceled with a dynamic `ctx.skip()` call.
1962
- *
1963
- * @param {Function} fn - The callback function to be executed after a test finishes. The function can receive parameters providing details about the completed test, including its success or failure status.
1964
- * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
1965
- * @throws {Error} Throws an error if the function is not called within a test.
1966
- * @returns {void}
1967
- * @example
1968
- * ```ts
1969
- * // Example of using onTestFinished for cleanup
1970
- * const db = await connectToDatabase();
1971
- * onTestFinished(async () => {
1972
- * await db.disconnect();
1973
- * });
1974
- * ```
1975
- */
1976
- const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
1977
- test.onFinished || (test.onFinished = []);
1978
- test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
1979
- });
1980
- function createTestHook(name, handler) {
1981
- return (fn, timeout) => {
1982
- assertTypes(fn, `"${name}" callback`, ["function"]);
1983
- const current = getCurrentTest();
1984
- if (!current) {
1985
- throw new Error(`Hook ${name}() can only be called inside a test`);
1986
- }
1987
- return handler(current, fn, timeout);
1988
- };
2095
+ function manageArtifactAttachment(attachment) {
2096
+ if (attachment.body == null && !attachment.path) {
2097
+ throw new TypeError(`Test attachment requires "body" or "path" to be set. Both are missing.`);
2098
+ }
2099
+ if (attachment.body && attachment.path) {
2100
+ throw new TypeError(`Test attachment requires only one of "body" or "path" to be set. Both are specified.`);
2101
+ }
2102
+ // convert to a string so it's easier to serialise
2103
+ if (attachment.body instanceof Uint8Array) {
2104
+ attachment.body = encodeUint8Array(attachment.body);
2105
+ }
1989
2106
  }
1990
2107
 
1991
- export { afterAll, afterEach, beforeAll, beforeEach, publicCollect as collectTests, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, setFn, setHooks, startTests, suite, test, updateTask };
2108
+ export { afterAll, afterEach, beforeAll, beforeEach, publicCollect as collectTests, createTaskCollector, describe, getCurrentSuite, getCurrentTest, getFn, getHooks, it, onTestFailed, onTestFinished, recordArtifact, setFn, setHooks, startTests, suite, test, updateTask };