@vitest/runner 4.1.0-beta.3 → 4.1.0-beta.5

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
@@ -2,7 +2,7 @@ import { processError } from '@vitest/utils/error';
2
2
  import { isObject, filterOutComments, createDefer, assertTypes, toArray, isNegativeNaN, unique, objectAttr, shuffle } from '@vitest/utils/helpers';
3
3
  import { getSafeTimers } from '@vitest/utils/timers';
4
4
  import { format, formatRegExp, objDisplay } from '@vitest/utils/display';
5
- import { a as createChainable, v as validateTags, e as createTaskName, w as createNoTagsError, f as findTestFileStackTrace, d as createTagsFilter, b as createFileTask, c as calculateSuiteHash, u as someTasksAreOnly, q as interpretTaskModes, s as limitConcurrency, t as partitionSuiteChildren, p as hasTests, o as hasFailed } from './chunk-tasks.js';
5
+ import { w as getChainableContext, a as createChainable, v as validateTags, e as createTaskName, x as createNoTagsError, f as findTestFileStackTrace, d as createTagsFilter, b as createFileTask, c as calculateSuiteHash, u as someTasksAreOnly, q as interpretTaskModes, s as limitConcurrency, t as partitionSuiteChildren, p as hasTests, o as hasFailed } from './chunk-tasks.js';
6
6
  import '@vitest/utils/source-map';
7
7
  import 'pathe';
8
8
 
@@ -24,6 +24,9 @@ class TestRunAbortError extends Error {
24
24
  this.reason = reason;
25
25
  }
26
26
  }
27
+ class FixtureDependencyError extends Error {
28
+ name = "FixtureDependencyError";
29
+ }
27
30
  class AroundHookSetupError extends Error {
28
31
  name = "AroundHookSetupError";
29
32
  }
@@ -47,7 +50,7 @@ function getFn(key) {
47
50
  function setTestFixture(key, fixture) {
48
51
  testFixtureMap.set(key, fixture);
49
52
  }
50
- function getTestFixture(key) {
53
+ function getTestFixtures(key) {
51
54
  return testFixtureMap.get(key);
52
55
  }
53
56
  function setHooks(key, hooks) {
@@ -57,85 +60,182 @@ function getHooks(key) {
57
60
  return hooksMap.get(key);
58
61
  }
59
62
 
60
- function mergeScopedFixtures(testFixtures, scopedFixtures) {
61
- const scopedFixturesMap = scopedFixtures.reduce((map, fixture) => {
62
- map[fixture.prop] = fixture;
63
- return map;
64
- }, {});
65
- const newFixtures = {};
66
- testFixtures.forEach((fixture) => {
67
- const useFixture = scopedFixturesMap[fixture.prop] || { ...fixture };
68
- newFixtures[useFixture.prop] = useFixture;
69
- });
70
- for (const fixtureKep in newFixtures) {
71
- var _fixture$deps;
72
- const fixture = newFixtures[fixtureKep];
73
- // if the fixture was define before the scope, then its dep
74
- // will reference the original fixture instead of the scope
75
- fixture.deps = (_fixture$deps = fixture.deps) === null || _fixture$deps === void 0 ? void 0 : _fixture$deps.map((dep) => newFixtures[dep.prop]);
76
- }
77
- return Object.values(newFixtures);
78
- }
79
- function mergeContextFixtures(fixtures, context, runner) {
80
- const fixtureOptionKeys = [
63
+ class TestFixtures {
64
+ _suiteContexts;
65
+ _overrides = new WeakMap();
66
+ _registrations;
67
+ static _definitions = [];
68
+ static _builtinFixtures = [
69
+ "task",
70
+ "signal",
71
+ "onTestFailed",
72
+ "onTestFinished",
73
+ "skip",
74
+ "annotate"
75
+ ];
76
+ static _fixtureOptionKeys = [
81
77
  "auto",
82
78
  "injected",
83
79
  "scope"
84
80
  ];
85
- const fixtureArray = Object.entries(fixtures).map(([prop, value]) => {
86
- const fixtureItem = { value };
87
- if (Array.isArray(value) && value.length >= 2 && isObject(value[1]) && Object.keys(value[1]).some((key) => fixtureOptionKeys.includes(key))) {
88
- var _runner$injectValue;
89
- // fixture with options
90
- Object.assign(fixtureItem, value[1]);
91
- const userValue = value[0];
92
- fixtureItem.value = fixtureItem.injected ? ((_runner$injectValue = runner.injectValue) === null || _runner$injectValue === void 0 ? void 0 : _runner$injectValue.call(runner, prop)) ?? userValue : userValue;
93
- }
94
- fixtureItem.scope = fixtureItem.scope || "test";
95
- if (fixtureItem.scope === "worker" && !runner.getWorkerContext) {
96
- fixtureItem.scope = "file";
97
- }
98
- fixtureItem.prop = prop;
99
- fixtureItem.isFn = typeof fixtureItem.value === "function";
100
- return fixtureItem;
101
- });
102
- if (Array.isArray(context.fixtures)) {
103
- context.fixtures = context.fixtures.concat(fixtureArray);
104
- } else {
105
- context.fixtures = fixtureArray;
106
- }
107
- // Update dependencies of fixture functions
108
- fixtureArray.forEach((fixture) => {
109
- if (fixture.isFn) {
110
- const usedProps = getUsedProps(fixture.value);
111
- if (usedProps.length) {
112
- fixture.deps = context.fixtures.filter(({ prop }) => prop !== fixture.prop && usedProps.includes(prop));
81
+ static _fixtureScopes = [
82
+ "test",
83
+ "file",
84
+ "worker"
85
+ ];
86
+ static _workerContextSuite = { type: "worker" };
87
+ static clearDefinitions() {
88
+ TestFixtures._definitions.length = 0;
89
+ }
90
+ static getWorkerContexts() {
91
+ return TestFixtures._definitions.map((f) => f.getWorkerContext());
92
+ }
93
+ static getFileContexts(file) {
94
+ return TestFixtures._definitions.map((f) => f.getFileContext(file));
95
+ }
96
+ constructor(registrations) {
97
+ this._registrations = registrations ?? new Map();
98
+ this._suiteContexts = new WeakMap();
99
+ TestFixtures._definitions.push(this);
100
+ }
101
+ extend(runner, userFixtures) {
102
+ const { suite } = getCurrentSuite();
103
+ const isTopLevel = !suite || suite.file === suite;
104
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel);
105
+ return new TestFixtures(registrations);
106
+ }
107
+ get(suite) {
108
+ let currentSuite = suite;
109
+ while (currentSuite) {
110
+ const overrides = this._overrides.get(currentSuite);
111
+ // return the closest override
112
+ if (overrides) {
113
+ return overrides;
113
114
  }
114
- // test can access anything, so we ignore it
115
- if (fixture.scope !== "test") {
116
- var _fixture$deps2;
117
- (_fixture$deps2 = fixture.deps) === null || _fixture$deps2 === void 0 ? void 0 : _fixture$deps2.forEach((dep) => {
118
- if (!dep.isFn) {
119
- // non fn fixtures are always resolved and available to anyone
120
- return;
121
- }
122
- // worker scope can only import from worker scope
123
- if (fixture.scope === "worker" && dep.scope === "worker") {
124
- return;
125
- }
126
- // file scope an import from file and worker scopes
127
- if (fixture.scope === "file" && dep.scope !== "test") {
128
- return;
129
- }
130
- throw new SyntaxError(`cannot use the ${dep.scope} fixture "${dep.prop}" inside the ${fixture.scope} fixture "${fixture.prop}"`);
131
- });
115
+ if (currentSuite === currentSuite.file) {
116
+ break;
132
117
  }
118
+ currentSuite = currentSuite.suite || currentSuite.file;
119
+ }
120
+ return this._registrations;
121
+ }
122
+ override(runner, userFixtures) {
123
+ const { suite: currentSuite, file } = getCurrentSuite();
124
+ const suite = currentSuite || file;
125
+ const isTopLevel = !currentSuite || currentSuite.file === currentSuite;
126
+ // Create a copy of the closest parent's registrations to avoid modifying them
127
+ // For chained calls, this.get(suite) returns this suite's overrides; for first call, returns parent's
128
+ const suiteRegistrations = new Map(this.get(suite));
129
+ const registrations = this.parseUserFixtures(runner, userFixtures, isTopLevel, suiteRegistrations);
130
+ // If defined in top-level, just override all registrations
131
+ // We don't support overriding suite-level fixtures anyway (it will throw an error)
132
+ if (isTopLevel) {
133
+ this._registrations = registrations;
134
+ } else {
135
+ this._overrides.set(suite, registrations);
133
136
  }
134
- });
135
- return context;
137
+ }
138
+ getFileContext(file) {
139
+ if (!this._suiteContexts.has(file)) {
140
+ this._suiteContexts.set(file, Object.create(null));
141
+ }
142
+ return this._suiteContexts.get(file);
143
+ }
144
+ getWorkerContext() {
145
+ if (!this._suiteContexts.has(TestFixtures._workerContextSuite)) {
146
+ this._suiteContexts.set(TestFixtures._workerContextSuite, Object.create(null));
147
+ }
148
+ return this._suiteContexts.get(TestFixtures._workerContextSuite);
149
+ }
150
+ parseUserFixtures(runner, userFixtures, supportNonTest, registrations = new Map(this._registrations)) {
151
+ const errors = [];
152
+ Object.entries(userFixtures).forEach(([name, fn]) => {
153
+ let options;
154
+ let value;
155
+ let _options;
156
+ if (Array.isArray(fn) && fn.length >= 2 && isObject(fn[1]) && Object.keys(fn[1]).some((key) => TestFixtures._fixtureOptionKeys.includes(key))) {
157
+ _options = fn[1];
158
+ options = {
159
+ auto: _options.auto ?? false,
160
+ scope: _options.scope ?? "test",
161
+ injected: _options.injected ?? false
162
+ };
163
+ value = options.injected ? runner.injectValue?.(name) ?? fn[0] : fn[0];
164
+ } else {
165
+ value = fn;
166
+ }
167
+ const parent = registrations.get(name);
168
+ if (parent && options) {
169
+ if (parent.scope !== options.scope) {
170
+ errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered with a "${options.scope}" scope.`));
171
+ }
172
+ if (parent.auto !== options.auto) {
173
+ errors.push(new FixtureDependencyError(`The "${name}" fixture was already registered as { auto: ${options.auto} }.`));
174
+ }
175
+ } else if (parent) {
176
+ options = {
177
+ auto: parent.auto,
178
+ scope: parent.scope,
179
+ injected: parent.injected
180
+ };
181
+ } else if (!options) {
182
+ options = {
183
+ auto: false,
184
+ injected: false,
185
+ scope: "test"
186
+ };
187
+ }
188
+ if (options.scope && !TestFixtures._fixtureScopes.includes(options.scope)) {
189
+ errors.push(new FixtureDependencyError(`The "${name}" fixture has unknown scope "${options.scope}".`));
190
+ }
191
+ if (!supportNonTest && options.scope !== "test") {
192
+ errors.push(new FixtureDependencyError(`The "${name}" fixture cannot be defined with a ${options.scope} scope${!_options?.scope && parent?.scope ? " (inherited from the base fixture)" : ""} inside the describe block. Define it at the top level of the file instead.`));
193
+ }
194
+ const deps = isFixtureFunction(value) ? getUsedProps(value) : new Set();
195
+ const item = {
196
+ name,
197
+ value,
198
+ auto: options.auto ?? false,
199
+ injected: options.injected ?? false,
200
+ scope: options.scope ?? "test",
201
+ deps,
202
+ parent
203
+ };
204
+ registrations.set(name, item);
205
+ if (item.scope === "worker" && (runner.pool === "vmThreads" || runner.pool === "vmForks")) {
206
+ item.scope = "file";
207
+ }
208
+ });
209
+ // validate fixture dependency scopes
210
+ for (const fixture of registrations.values()) {
211
+ for (const depName of fixture.deps) {
212
+ if (TestFixtures._builtinFixtures.includes(depName)) {
213
+ continue;
214
+ }
215
+ const dep = registrations.get(depName);
216
+ if (!dep) {
217
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on unknown fixture "${depName}".`));
218
+ continue;
219
+ }
220
+ if (depName === fixture.name && !fixture.parent) {
221
+ errors.push(new FixtureDependencyError(`The "${fixture.name}" fixture depends on itself, but does not have a base implementation.`));
222
+ continue;
223
+ }
224
+ if (TestFixtures._fixtureScopes.indexOf(fixture.scope) > TestFixtures._fixtureScopes.indexOf(dep.scope)) {
225
+ errors.push(new FixtureDependencyError(`The ${fixture.scope} "${fixture.name}" fixture cannot depend on a ${dep.scope} fixture "${dep.name}".`));
226
+ continue;
227
+ }
228
+ }
229
+ }
230
+ if (errors.length === 1) {
231
+ throw errors[0];
232
+ } else if (errors.length > 1) {
233
+ throw new AggregateError(errors, "Cannot resolve user fixtures. See errors for more information.");
234
+ }
235
+ return registrations;
236
+ }
136
237
  }
137
- const fixtureValueMaps = new Map();
138
- const cleanupFnArrayMap = new Map();
238
+ const cleanupFnArrayMap = new WeakMap();
139
239
  async function callFixtureCleanup(context) {
140
240
  const cleanupFnArray = cleanupFnArrayMap.get(context) ?? [];
141
241
  for (const cleanup of cleanupFnArray.reverse()) {
@@ -148,8 +248,7 @@ async function callFixtureCleanup(context) {
148
248
  * This can be used as a checkpoint to later clean up only fixtures added after this point.
149
249
  */
150
250
  function getFixtureCleanupCount(context) {
151
- var _cleanupFnArrayMap$ge;
152
- return ((_cleanupFnArrayMap$ge = cleanupFnArrayMap.get(context)) === null || _cleanupFnArrayMap$ge === void 0 ? void 0 : _cleanupFnArrayMap$ge.length) ?? 0;
251
+ return cleanupFnArrayMap.get(context)?.length ?? 0;
153
252
  }
154
253
  /**
155
254
  * Cleans up only fixtures that were added after the given checkpoint index.
@@ -170,96 +269,126 @@ async function callFixtureCleanupFrom(context, fromIndex) {
170
269
  // Remove cleaned up items from the array, keeping items before checkpoint
171
270
  cleanupFnArray.length = fromIndex;
172
271
  }
173
- function withFixtures(runner, fn, testContext) {
174
- return (hookContext) => {
175
- const context = hookContext || testContext;
272
+ const contextHasFixturesCache = new WeakMap();
273
+ function withFixtures(fn, options) {
274
+ const collector = getCurrentSuite();
275
+ const suite = collector.suite || collector.file;
276
+ return async (hookContext) => {
277
+ const context = hookContext || options?.context;
176
278
  if (!context) {
279
+ if (options?.suiteHook) {
280
+ validateSuiteHook(fn, options.suiteHook, options.stackTraceError);
281
+ }
177
282
  return fn({});
178
283
  }
179
- const fixtures = getTestFixture(context);
180
- if (!(fixtures === null || fixtures === void 0 ? void 0 : fixtures.length)) {
284
+ const fixtures = options?.fixtures || getTestFixtures(context);
285
+ if (!fixtures) {
181
286
  return fn(context);
182
287
  }
183
- const usedProps = getUsedProps(fn);
184
- const hasAutoFixture = fixtures.some(({ auto }) => auto);
185
- if (!usedProps.length && !hasAutoFixture) {
288
+ const registrations = fixtures.get(suite);
289
+ if (!registrations.size) {
186
290
  return fn(context);
187
291
  }
188
- if (!fixtureValueMaps.get(context)) {
189
- fixtureValueMaps.set(context, new Map());
292
+ const usedFixtures = [];
293
+ const usedProps = getUsedProps(fn);
294
+ for (const fixture of registrations.values()) {
295
+ if (fixture.auto || usedProps.has(fixture.name)) {
296
+ usedFixtures.push(fixture);
297
+ }
298
+ }
299
+ if (!usedFixtures.length) {
300
+ return fn(context);
190
301
  }
191
- const fixtureValueMap = fixtureValueMaps.get(context);
192
302
  if (!cleanupFnArrayMap.has(context)) {
193
303
  cleanupFnArrayMap.set(context, []);
194
304
  }
195
305
  const cleanupFnArray = cleanupFnArrayMap.get(context);
196
- const usedFixtures = fixtures.filter(({ prop, auto }) => auto || usedProps.includes(prop));
197
- const pendingFixtures = resolveDeps(usedFixtures);
306
+ const pendingFixtures = resolveDeps(usedFixtures, registrations);
198
307
  if (!pendingFixtures.length) {
199
308
  return fn(context);
200
309
  }
201
- async function resolveFixtures() {
202
- for (const fixture of pendingFixtures) {
310
+ // Check if suite-level hook is trying to access test-scoped fixtures
311
+ // Suite hooks (beforeAll/afterAll/aroundAll) can only access file/worker scoped fixtures
312
+ if (options?.suiteHook) {
313
+ const testScopedFixtures = pendingFixtures.filter((f) => f.scope === "test");
314
+ if (testScopedFixtures.length > 0) {
315
+ const fixtureNames = testScopedFixtures.map((f) => `"${f.name}"`).join(", ");
316
+ const alternativeHook = {
317
+ aroundAll: "aroundEach",
318
+ beforeAll: "beforeEach",
319
+ afterAll: "afterEach"
320
+ };
321
+ const error = new FixtureDependencyError(`Test-scoped fixtures cannot be used inside ${options.suiteHook} hook. ` + `The following fixtures are test-scoped: ${fixtureNames}. ` + `Use { scope: 'file' } or { scope: 'worker' } fixtures instead, or move the logic to ${alternativeHook[options.suiteHook]} hook.`);
322
+ // Use stack trace from hook registration for better error location
323
+ if (options.stackTraceError?.stack) {
324
+ error.stack = error.message + options.stackTraceError.stack.replace(options.stackTraceError.message, "");
325
+ }
326
+ throw error;
327
+ }
328
+ }
329
+ if (!contextHasFixturesCache.has(context)) {
330
+ contextHasFixturesCache.set(context, new WeakSet());
331
+ }
332
+ const cachedFixtures = contextHasFixturesCache.get(context);
333
+ for (const fixture of pendingFixtures) {
334
+ if (fixture.scope === "test") {
203
335
  // fixture could be already initialized during "before" hook
204
- if (fixtureValueMap.has(fixture)) {
336
+ // we can't check "fixture.name" in context because context may
337
+ // access the parent fixture ({ a: ({ a }) => {} })
338
+ if (cachedFixtures.has(fixture)) {
205
339
  continue;
206
340
  }
207
- const resolvedValue = await resolveFixtureValue(runner, fixture, context, cleanupFnArray);
208
- context[fixture.prop] = resolvedValue;
209
- fixtureValueMap.set(fixture, resolvedValue);
210
- if (fixture.scope === "test") {
211
- cleanupFnArray.unshift(() => {
212
- fixtureValueMap.delete(fixture);
213
- });
214
- }
341
+ cachedFixtures.add(fixture);
342
+ const resolvedValue = await resolveTestFixtureValue(fixture, context, cleanupFnArray);
343
+ context[fixture.name] = resolvedValue;
344
+ cleanupFnArray.push(() => {
345
+ cachedFixtures.delete(fixture);
346
+ });
347
+ } else {
348
+ const resolvedValue = await resolveScopeFixtureValue(fixtures, suite, fixture);
349
+ context[fixture.name] = resolvedValue;
215
350
  }
216
351
  }
217
- return resolveFixtures().then(() => fn(context));
352
+ return fn(context);
218
353
  };
219
354
  }
220
- const globalFixturePromise = new WeakMap();
221
- function resolveFixtureValue(runner, fixture, context, cleanupFnArray) {
222
- var _runner$getWorkerCont;
223
- const fileContext = getFileContext(context.task.file);
224
- const workerContext = (_runner$getWorkerCont = runner.getWorkerContext) === null || _runner$getWorkerCont === void 0 ? void 0 : _runner$getWorkerCont.call(runner);
225
- if (!fixture.isFn) {
226
- var _fixture$prop;
227
- fileContext[_fixture$prop = fixture.prop] ?? (fileContext[_fixture$prop] = fixture.value);
228
- if (workerContext) {
229
- var _fixture$prop2;
230
- workerContext[_fixture$prop2 = fixture.prop] ?? (workerContext[_fixture$prop2] = fixture.value);
231
- }
355
+ function isFixtureFunction(value) {
356
+ return typeof value === "function";
357
+ }
358
+ function resolveTestFixtureValue(fixture, context, cleanupFnArray) {
359
+ if (!isFixtureFunction(fixture.value)) {
232
360
  return fixture.value;
233
361
  }
234
- if (fixture.scope === "test") {
235
- return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
236
- }
237
- // in case the test runs in parallel
238
- if (globalFixturePromise.has(fixture)) {
239
- return globalFixturePromise.get(fixture);
362
+ return resolveFixtureFunction(fixture.value, context, cleanupFnArray);
363
+ }
364
+ const scopedFixturePromiseCache = new WeakMap();
365
+ async function resolveScopeFixtureValue(fixtures, suite, fixture) {
366
+ const workerContext = fixtures.getWorkerContext();
367
+ const fileContext = fixtures.getFileContext(suite.file);
368
+ const fixtureContext = fixture.scope === "worker" ? workerContext : fileContext;
369
+ if (!isFixtureFunction(fixture.value)) {
370
+ fixtureContext[fixture.name] = fixture.value;
371
+ return fixture.value;
240
372
  }
241
- let fixtureContext;
242
- if (fixture.scope === "worker") {
243
- if (!workerContext) {
244
- throw new TypeError("[@vitest/runner] The worker context is not available in the current test runner. Please, provide the `getWorkerContext` method when initiating the runner.");
245
- }
246
- fixtureContext = workerContext;
247
- } else {
248
- fixtureContext = fileContext;
373
+ if (fixture.name in fixtureContext) {
374
+ return fixtureContext[fixture.name];
249
375
  }
250
- if (fixture.prop in fixtureContext) {
251
- return fixtureContext[fixture.prop];
376
+ if (scopedFixturePromiseCache.has(fixture)) {
377
+ return scopedFixturePromiseCache.get(fixture);
252
378
  }
253
379
  if (!cleanupFnArrayMap.has(fixtureContext)) {
254
380
  cleanupFnArrayMap.set(fixtureContext, []);
255
381
  }
256
382
  const cleanupFnFileArray = cleanupFnArrayMap.get(fixtureContext);
257
- const promise = resolveFixtureFunction(fixture.value, fixtureContext, cleanupFnFileArray).then((value) => {
258
- fixtureContext[fixture.prop] = value;
259
- globalFixturePromise.delete(fixture);
383
+ const promise = resolveFixtureFunction(fixture.value, fixture.scope === "file" ? {
384
+ ...workerContext,
385
+ ...fileContext
386
+ } : fixtureContext, cleanupFnFileArray).then((value) => {
387
+ fixtureContext[fixture.name] = value;
388
+ scopedFixturePromiseCache.delete(fixture);
260
389
  return value;
261
390
  });
262
- globalFixturePromise.set(fixture, promise);
391
+ scopedFixturePromiseCache.set(fixture, promise);
263
392
  return promise;
264
393
  }
265
394
  async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
@@ -290,27 +419,50 @@ async function resolveFixtureFunction(fixtureFn, context, cleanupFnArray) {
290
419
  });
291
420
  return useFnArgPromise;
292
421
  }
293
- function resolveDeps(fixtures, depSet = new Set(), pendingFixtures = []) {
294
- fixtures.forEach((fixture) => {
422
+ function resolveDeps(usedFixtures, registrations, depSet = new Set(), pendingFixtures = []) {
423
+ usedFixtures.forEach((fixture) => {
295
424
  if (pendingFixtures.includes(fixture)) {
296
425
  return;
297
426
  }
298
- if (!fixture.isFn || !fixture.deps) {
427
+ if (!isFixtureFunction(fixture.value) || !fixture.deps) {
299
428
  pendingFixtures.push(fixture);
300
429
  return;
301
430
  }
302
431
  if (depSet.has(fixture)) {
303
- throw new Error(`Circular fixture dependency detected: ${fixture.prop} <- ${[...depSet].reverse().map((d) => d.prop).join(" <- ")}`);
432
+ if (fixture.parent) {
433
+ fixture = fixture.parent;
434
+ } else {
435
+ throw new Error(`Circular fixture dependency detected: ${fixture.name} <- ${[...depSet].reverse().map((d) => d.name).join(" <- ")}`);
436
+ }
304
437
  }
305
438
  depSet.add(fixture);
306
- resolveDeps(fixture.deps, depSet, pendingFixtures);
439
+ resolveDeps([...fixture.deps].map((n) => n === fixture.name ? fixture.parent : registrations.get(n)).filter((n) => !!n), registrations, depSet, pendingFixtures);
307
440
  pendingFixtures.push(fixture);
308
441
  depSet.clear();
309
442
  });
310
443
  return pendingFixtures;
311
444
  }
445
+ function validateSuiteHook(fn, hook, error) {
446
+ const usedProps = getUsedProps(fn);
447
+ if (usedProps.size) {
448
+ console.warn(`The ${hook} hook uses fixtures "${[...usedProps].join("\", \"")}", but has no access to context. Did you forget to call it as "test.${hook}()" instead of "${hook}()"? This will throw an error in a future major. See https://vitest.dev/guide/test-context#suite-level-hooks`);
449
+ if (error) {
450
+ const processor = globalThis.__vitest_worker__?.onFilterStackTrace || ((s) => s || "");
451
+ const stack = processor(error.stack || "");
452
+ console.warn(stack);
453
+ }
454
+ }
455
+ }
456
+ const kPropsSymbol = Symbol("$vitest:fixture-props");
457
+ function configureProps(fn, options) {
458
+ Object.defineProperty(fn, kPropsSymbol, {
459
+ value: options,
460
+ enumerable: false
461
+ });
462
+ }
312
463
  function getUsedProps(fn) {
313
- let fnString = filterOutComments(fn.toString());
464
+ const { index: fixturesIndex = 0, original: implementation = fn } = kPropsSymbol in fn ? fn[kPropsSymbol] : {};
465
+ let fnString = filterOutComments(implementation.toString());
314
466
  // match lowered async function and strip it off
315
467
  // example code on esbuild-try https://esbuild.github.io/try/#YgAwLjI0LjAALS1zdXBwb3J0ZWQ6YXN5bmMtYXdhaXQ9ZmFsc2UAZQBlbnRyeS50cwBjb25zdCBvID0gewogIGYxOiBhc3luYyAoKSA9PiB7fSwKICBmMjogYXN5bmMgKGEpID0+IHt9LAogIGYzOiBhc3luYyAoYSwgYikgPT4ge30sCiAgZjQ6IGFzeW5jIGZ1bmN0aW9uKGEpIHt9LAogIGY1OiBhc3luYyBmdW5jdGlvbiBmZihhKSB7fSwKICBhc3luYyBmNihhKSB7fSwKCiAgZzE6IGFzeW5jICgpID0+IHt9LAogIGcyOiBhc3luYyAoeyBhIH0pID0+IHt9LAogIGczOiBhc3luYyAoeyBhIH0sIGIpID0+IHt9LAogIGc0OiBhc3luYyBmdW5jdGlvbiAoeyBhIH0pIHt9LAogIGc1OiBhc3luYyBmdW5jdGlvbiBnZyh7IGEgfSkge30sCiAgYXN5bmMgZzYoeyBhIH0pIHt9LAoKICBoMTogYXN5bmMgKCkgPT4ge30sCiAgLy8gY29tbWVudCBiZXR3ZWVuCiAgaDI6IGFzeW5jIChhKSA9PiB7fSwKfQ
316
468
  // __async(this, null, function*
@@ -321,23 +473,20 @@ function getUsedProps(fn) {
321
473
  }
322
474
  const match = fnString.match(/[^(]*\(([^)]*)/);
323
475
  if (!match) {
324
- return [];
476
+ return new Set();
325
477
  }
326
478
  const args = splitByComma(match[1]);
327
479
  if (!args.length) {
328
- return [];
480
+ return new Set();
329
481
  }
330
- let first = args[0];
331
- if ("__VITEST_FIXTURE_INDEX__" in fn) {
332
- first = args[fn.__VITEST_FIXTURE_INDEX__];
333
- if (!first) {
334
- return [];
335
- }
482
+ const fixturesArgument = args[fixturesIndex];
483
+ if (!fixturesArgument) {
484
+ return new Set();
336
485
  }
337
- if (!(first[0] === "{" && first.endsWith("}"))) {
338
- throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${first}".`);
486
+ if (!(fixturesArgument[0] === "{" && fixturesArgument.endsWith("}"))) {
487
+ throw new Error(`The first argument inside a fixture must use object destructuring pattern, e.g. ({ test } => {}). Instead, received "${fixturesArgument}".`);
339
488
  }
340
- const _first = first.slice(1, -1).replace(/\s/g, "");
489
+ const _first = fixturesArgument.slice(1, -1).replace(/\s/g, "");
341
490
  const props = splitByComma(_first).map((prop) => {
342
491
  return prop.replace(/:.*|=.*/g, "");
343
492
  });
@@ -345,7 +494,7 @@ function getUsedProps(fn) {
345
494
  if (last && last.startsWith("...")) {
346
495
  throw new Error(`Rest parameters are not supported in fixtures, received "${last}".`);
347
496
  }
348
- return props;
497
+ return new Set(props);
349
498
  }
350
499
  function splitByComma(s) {
351
500
  const result = [];
@@ -427,7 +576,8 @@ function getBeforeHookCleanupCallback(hook, result, context) {
427
576
  function beforeAll(fn, timeout = getDefaultHookTimeout()) {
428
577
  assertTypes(fn, "\"beforeAll\" callback", ["function"]);
429
578
  const stackTraceError = new Error("STACK_TRACE_ERROR");
430
- return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(fn, timeout, true, stackTraceError), {
579
+ const context = getChainableContext(this);
580
+ return getCurrentSuite().on("beforeAll", Object.assign(withTimeout(withSuiteFixtures("beforeAll", fn, context, stackTraceError), timeout, true, stackTraceError), {
431
581
  [CLEANUP_TIMEOUT_KEY]: timeout,
432
582
  [CLEANUP_STACK_TRACE_KEY]: stackTraceError
433
583
  }));
@@ -451,7 +601,9 @@ function beforeAll(fn, timeout = getDefaultHookTimeout()) {
451
601
  */
452
602
  function afterAll(fn, timeout) {
453
603
  assertTypes(fn, "\"afterAll\" callback", ["function"]);
454
- return getCurrentSuite().on("afterAll", withTimeout(fn, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR")));
604
+ const context = getChainableContext(this);
605
+ const stackTraceError = new Error("STACK_TRACE_ERROR");
606
+ return getCurrentSuite().on("afterAll", withTimeout(withSuiteFixtures("afterAll", fn, context, stackTraceError), timeout ?? getDefaultHookTimeout(), true, stackTraceError));
455
607
  }
456
608
  /**
457
609
  * Registers a callback function to be executed before each test within the current suite.
@@ -473,8 +625,7 @@ function afterAll(fn, timeout) {
473
625
  function beforeEach(fn, timeout = getDefaultHookTimeout()) {
474
626
  assertTypes(fn, "\"beforeEach\" callback", ["function"]);
475
627
  const stackTraceError = new Error("STACK_TRACE_ERROR");
476
- const runner = getRunner();
477
- return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
628
+ return getCurrentSuite().on("beforeEach", Object.assign(withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true, stackTraceError, abortIfTimeout), {
478
629
  [CLEANUP_TIMEOUT_KEY]: timeout,
479
630
  [CLEANUP_STACK_TRACE_KEY]: stackTraceError
480
631
  }));
@@ -498,8 +649,7 @@ function beforeEach(fn, timeout = getDefaultHookTimeout()) {
498
649
  */
499
650
  function afterEach(fn, timeout) {
500
651
  assertTypes(fn, "\"afterEach\" callback", ["function"]);
501
- const runner = getRunner();
502
- return getCurrentSuite().on("afterEach", withTimeout(withFixtures(runner, fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
652
+ return getCurrentSuite().on("afterEach", withTimeout(withFixtures(fn), timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
503
653
  }
504
654
  /**
505
655
  * Registers a callback function to be executed when a test fails within the current suite.
@@ -520,7 +670,7 @@ function afterEach(fn, timeout) {
520
670
  * ```
521
671
  */
522
672
  const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) => {
523
- test.onFailed || (test.onFailed = []);
673
+ test.onFailed ||= [];
524
674
  test.onFailed.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
525
675
  });
526
676
  /**
@@ -547,7 +697,7 @@ const onTestFailed = createTestHook("onTestFailed", (test, handler, timeout) =>
547
697
  * ```
548
698
  */
549
699
  const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout) => {
550
- test.onFinished || (test.onFinished = []);
700
+ test.onFinished ||= [];
551
701
  test.onFinished.push(withTimeout(handler, timeout ?? getDefaultHookTimeout(), true, new Error("STACK_TRACE_ERROR"), abortIfTimeout));
552
702
  });
553
703
  /**
@@ -559,9 +709,6 @@ const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout)
559
709
  * **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other.
560
710
  * The first registered hook is the outermost wrapper.
561
711
  *
562
- * **Note:** Unlike `aroundEach`, the `aroundAll` hook does not receive test context or support fixtures,
563
- * as it runs at the suite level before any individual test context is created.
564
- *
565
712
  * @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests.
566
713
  * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used.
567
714
  * @returns {void}
@@ -574,9 +721,9 @@ const onTestFinished = createTestHook("onTestFinished", (test, handler, timeout)
574
721
  * ```
575
722
  * @example
576
723
  * ```ts
577
- * // Example of using aroundAll with AsyncLocalStorage context
578
- * aroundAll(async (runSuite) => {
579
- * await asyncLocalStorage.run({ suiteId: 'my-suite' }, runSuite);
724
+ * // Example of using aroundAll with fixtures
725
+ * aroundAll(async (runSuite, { db }) => {
726
+ * await db.transaction(() => runSuite());
580
727
  * });
581
728
  * ```
582
729
  */
@@ -584,7 +731,8 @@ function aroundAll(fn, timeout) {
584
731
  assertTypes(fn, "\"aroundAll\" callback", ["function"]);
585
732
  const stackTraceError = new Error("STACK_TRACE_ERROR");
586
733
  const resolvedTimeout = timeout ?? getDefaultHookTimeout();
587
- return getCurrentSuite().on("aroundAll", Object.assign(fn, {
734
+ const context = getChainableContext(this);
735
+ return getCurrentSuite().on("aroundAll", Object.assign(withSuiteFixtures("aroundAll", fn, context, stackTraceError, 1), {
588
736
  [AROUND_TIMEOUT_KEY]: resolvedTimeout,
589
737
  [AROUND_STACK_TRACE_KEY]: stackTraceError
590
738
  }));
@@ -619,37 +767,39 @@ function aroundEach(fn, timeout) {
619
767
  assertTypes(fn, "\"aroundEach\" callback", ["function"]);
620
768
  const stackTraceError = new Error("STACK_TRACE_ERROR");
621
769
  const resolvedTimeout = timeout ?? getDefaultHookTimeout();
622
- const runner = getRunner();
623
- // Create a wrapper function that supports fixtures in the second argument (context)
624
- // withFixtures resolves fixtures into context, then we call fn with all 3 args
625
- const wrappedFn = withAroundEachFixtures(runner, fn);
626
- // Store timeout and stack trace on the function for use in callAroundEachHooks
627
- // Setup and teardown phases will each have their own timeout
628
- return getCurrentSuite().on("aroundEach", Object.assign(wrappedFn, {
770
+ const wrapper = (runTest, context, suite) => {
771
+ const innerFn = (ctx) => fn(runTest, ctx, suite);
772
+ configureProps(innerFn, {
773
+ index: 1,
774
+ original: fn
775
+ });
776
+ const fixtureResolver = withFixtures(innerFn);
777
+ return fixtureResolver(context);
778
+ };
779
+ return getCurrentSuite().on("aroundEach", Object.assign(wrapper, {
629
780
  [AROUND_TIMEOUT_KEY]: resolvedTimeout,
630
781
  [AROUND_STACK_TRACE_KEY]: stackTraceError
631
782
  }));
632
783
  }
633
- /**
634
- * Wraps an aroundEach listener to support fixtures.
635
- * Similar to withFixtures, but handles the aroundEach signature where:
636
- * - First arg is runTest function
637
- * - Second arg is context (where fixtures are destructured from)
638
- * - Third arg is suite
639
- */
640
- function withAroundEachFixtures(runner, fn) {
641
- // Create the wrapper that will be returned
642
- const wrapper = (runTest, context, suite) => {
643
- // Create inner function that will be passed to withFixtures
644
- // This function receives context (with fixtures resolved) and calls original fn
645
- const innerFn = (ctx) => fn(runTest, ctx, suite);
646
- innerFn.__VITEST_FIXTURE_INDEX__ = 1;
647
- innerFn.toString = () => fn.toString();
648
- // Use withFixtures to resolve fixtures, passing context as the hook context
649
- const fixtureResolver = withFixtures(runner, innerFn);
650
- return fixtureResolver(context);
784
+ function withSuiteFixtures(suiteHook, fn, context, stackTraceError, contextIndex = 0) {
785
+ return (...args) => {
786
+ const suite = args.at(-1);
787
+ const prefix = args.slice(0, -1);
788
+ const wrapper = (ctx) => fn(...prefix, ctx, suite);
789
+ configureProps(wrapper, {
790
+ index: contextIndex,
791
+ original: fn
792
+ });
793
+ const fixtures = context?.getFixtures();
794
+ const fileContext = fixtures?.getFileContext(suite.file);
795
+ const fixtured = withFixtures(wrapper, {
796
+ suiteHook,
797
+ fixtures,
798
+ context: fileContext,
799
+ stackTraceError
800
+ });
801
+ return fixtured();
651
802
  };
652
- return wrapper;
653
803
  }
654
804
  function getAroundHookTimeout(hook) {
655
805
  return AROUND_TIMEOUT_KEY in hook && typeof hook[AROUND_TIMEOUT_KEY] === "number" ? hook[AROUND_TIMEOUT_KEY] : getDefaultHookTimeout();
@@ -875,19 +1025,17 @@ function parseArguments(optionsOrFn, timeoutOrTest) {
875
1025
  };
876
1026
  }
877
1027
  // implementations
878
- function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions, parentCollectorFixtures) {
1028
+ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions) {
879
1029
  const tasks = [];
880
1030
  let suite;
881
1031
  initSuite(true);
882
1032
  const task = function(name = "", options = {}) {
883
- var _collectorContext$cur, _collectorContext$cur2, _collectorContext$cur3, _collectorContext$cur4;
884
- const currentSuite = (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.suite;
885
- const parentTask = currentSuite ?? ((_collectorContext$cur2 = collectorContext.currentSuite) === null || _collectorContext$cur2 === void 0 ? void 0 : _collectorContext$cur2.file);
886
- const parentTags = (parentTask === null || parentTask === void 0 ? void 0 : parentTask.tags) || [];
1033
+ const currentSuite = collectorContext.currentSuite?.suite;
1034
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
1035
+ const parentTags = parentTask?.tags || [];
887
1036
  const testTags = unique([...parentTags, ...toArray(options.tags)]);
888
1037
  const tagsOptions = testTags.map((tag) => {
889
- var _runner$config$tags;
890
- const tagDefinition = (_runner$config$tags = runner.config.tags) === null || _runner$config$tags === void 0 ? void 0 : _runner$config$tags.find((t) => t.name === tag);
1038
+ const tagDefinition = runner.config.tags?.find((t) => t.name === tag);
891
1039
  if (!tagDefinition && runner.config.strictTags) {
892
1040
  throw createNoTagsError(runner.config.tags, tag);
893
1041
  }
@@ -906,7 +1054,7 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
906
1054
  ...options
907
1055
  };
908
1056
  const timeout = options.timeout ?? runner.config.testTimeout;
909
- const parentMeta = currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.meta;
1057
+ const parentMeta = currentSuite?.meta;
910
1058
  const tagMeta = tagsOptions.meta;
911
1059
  const testMeta = Object.create(null);
912
1060
  if (tagMeta) {
@@ -921,14 +1069,14 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
921
1069
  const task = {
922
1070
  id: "",
923
1071
  name,
924
- fullName: createTaskName([(currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullName) ?? ((_collectorContext$cur3 = collectorContext.currentSuite) === null || _collectorContext$cur3 === void 0 || (_collectorContext$cur3 = _collectorContext$cur3.file) === null || _collectorContext$cur3 === void 0 ? void 0 : _collectorContext$cur3.fullName), name]),
925
- fullTestName: createTaskName([currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullTestName, name]),
1072
+ fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
1073
+ fullTestName: createTaskName([currentSuite?.fullTestName, name]),
926
1074
  suite: currentSuite,
927
1075
  each: options.each,
928
1076
  fails: options.fails,
929
1077
  context: undefined,
930
1078
  type: "test",
931
- file: (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.file) ?? ((_collectorContext$cur4 = collectorContext.currentSuite) === null || _collectorContext$cur4 === void 0 ? void 0 : _collectorContext$cur4.file),
1079
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
932
1080
  timeout,
933
1081
  retry: options.retry ?? runner.config.retry,
934
1082
  repeats: options.repeats,
@@ -945,21 +1093,20 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
945
1093
  if (options.concurrent || !options.sequential && runner.config.sequence.concurrent) {
946
1094
  task.concurrent = true;
947
1095
  }
948
- task.shuffle = suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle;
1096
+ task.shuffle = suiteOptions?.shuffle;
949
1097
  const context = createTestContext(task, runner);
950
1098
  // create test context
951
1099
  Object.defineProperty(task, "context", {
952
1100
  value: context,
953
1101
  enumerable: false
954
1102
  });
955
- setTestFixture(context, options.fixtures);
956
- // custom can be called from any place, let's assume the limit is 15 stacks
1103
+ setTestFixture(context, options.fixtures ?? new TestFixtures());
957
1104
  const limit = Error.stackTraceLimit;
958
- Error.stackTraceLimit = 15;
1105
+ Error.stackTraceLimit = 10;
959
1106
  const stackTraceError = new Error("STACK_TRACE_ERROR");
960
1107
  Error.stackTraceLimit = limit;
961
1108
  if (handler) {
962
- setFn(task, withTimeout(withAwaitAsyncAssertions(withFixtures(runner, handler, context), task), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
1109
+ setFn(task, withTimeout(withCancel(withAwaitAsyncAssertions(withFixtures(handler, { context }), task), task.context.signal), timeout, false, stackTraceError, (_, error) => abortIfTimeout([context], error)));
963
1110
  }
964
1111
  if (runner.config.includeTaskLocation) {
965
1112
  const error = stackTraceError.stack;
@@ -981,11 +1128,11 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
981
1128
  options = Object.assign({}, suiteOptions, options);
982
1129
  }
983
1130
  // inherit concurrent / sequential from suite
984
- const concurrent = this.concurrent ?? (!this.sequential && (options === null || options === void 0 ? void 0 : options.concurrent));
1131
+ const concurrent = this.concurrent ?? (!this.sequential && options?.concurrent);
985
1132
  if (options.concurrent != null && concurrent != null) {
986
1133
  options.concurrent = concurrent;
987
1134
  }
988
- const sequential = this.sequential ?? (!this.concurrent && (options === null || options === void 0 ? void 0 : options.sequential));
1135
+ const sequential = this.sequential ?? (!this.concurrent && options?.sequential);
989
1136
  if (options.sequential != null && sequential != null) {
990
1137
  options.sequential = sequential;
991
1138
  }
@@ -996,7 +1143,6 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
996
1143
  });
997
1144
  test.type = "test";
998
1145
  });
999
- let collectorFixtures = parentCollectorFixtures;
1000
1146
  const collector = {
1001
1147
  type: "collector",
1002
1148
  name,
@@ -1004,48 +1150,39 @@ function createSuiteCollector(name, factory = () => {}, mode, each, suiteOptions
1004
1150
  suite,
1005
1151
  options: suiteOptions,
1006
1152
  test,
1153
+ file: suite.file,
1007
1154
  tasks,
1008
1155
  collect,
1009
1156
  task,
1010
1157
  clear,
1011
- on: addHook,
1012
- fixtures() {
1013
- return collectorFixtures;
1014
- },
1015
- scoped(fixtures) {
1016
- const parsed = mergeContextFixtures(fixtures, { fixtures: collectorFixtures }, runner);
1017
- if (parsed.fixtures) {
1018
- collectorFixtures = parsed.fixtures;
1019
- }
1020
- }
1158
+ on: addHook
1021
1159
  };
1022
1160
  function addHook(name, ...fn) {
1023
1161
  getHooks(suite)[name].push(...fn);
1024
1162
  }
1025
1163
  function initSuite(includeLocation) {
1026
- var _collectorContext$cur5, _collectorContext$cur6, _collectorContext$cur7, _collectorContext$cur8;
1027
1164
  if (typeof suiteOptions === "number") {
1028
1165
  suiteOptions = { timeout: suiteOptions };
1029
1166
  }
1030
- const currentSuite = (_collectorContext$cur5 = collectorContext.currentSuite) === null || _collectorContext$cur5 === void 0 ? void 0 : _collectorContext$cur5.suite;
1031
- const parentTask = currentSuite ?? ((_collectorContext$cur6 = collectorContext.currentSuite) === null || _collectorContext$cur6 === void 0 ? void 0 : _collectorContext$cur6.file);
1032
- const suiteTags = toArray(suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.tags);
1167
+ const currentSuite = collectorContext.currentSuite?.suite;
1168
+ const parentTask = currentSuite ?? collectorContext.currentSuite?.file;
1169
+ const suiteTags = toArray(suiteOptions?.tags);
1033
1170
  validateTags(runner.config, suiteTags);
1034
1171
  suite = {
1035
1172
  id: "",
1036
1173
  type: "suite",
1037
1174
  name,
1038
- fullName: createTaskName([(currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullName) ?? ((_collectorContext$cur7 = collectorContext.currentSuite) === null || _collectorContext$cur7 === void 0 || (_collectorContext$cur7 = _collectorContext$cur7.file) === null || _collectorContext$cur7 === void 0 ? void 0 : _collectorContext$cur7.fullName), name]),
1039
- fullTestName: createTaskName([currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fullTestName, name]),
1175
+ fullName: createTaskName([currentSuite?.fullName ?? collectorContext.currentSuite?.file?.fullName, name]),
1176
+ fullTestName: createTaskName([currentSuite?.fullTestName, name]),
1040
1177
  suite: currentSuite,
1041
1178
  mode,
1042
1179
  each,
1043
- file: (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.file) ?? ((_collectorContext$cur8 = collectorContext.currentSuite) === null || _collectorContext$cur8 === void 0 ? void 0 : _collectorContext$cur8.file),
1044
- shuffle: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.shuffle,
1180
+ file: currentSuite?.file ?? collectorContext.currentSuite?.file,
1181
+ shuffle: suiteOptions?.shuffle,
1045
1182
  tasks: [],
1046
- meta: (suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.meta) ?? Object.create(null),
1047
- concurrent: suiteOptions === null || suiteOptions === void 0 ? void 0 : suiteOptions.concurrent,
1048
- tags: unique([...(parentTask === null || parentTask === void 0 ? void 0 : parentTask.tags) || [], ...suiteTags])
1183
+ meta: suiteOptions?.meta ?? Object.create(null),
1184
+ concurrent: suiteOptions?.concurrent,
1185
+ tags: unique([...parentTask?.tags || [], ...suiteTags])
1049
1186
  };
1050
1187
  if (runner && includeLocation && runner.config.includeTaskLocation) {
1051
1188
  const limit = Error.stackTraceLimit;
@@ -1099,7 +1236,6 @@ function withAwaitAsyncAssertions(fn, task) {
1099
1236
  }
1100
1237
  function createSuite() {
1101
1238
  function suiteFn(name, factoryOrOptions, optionsOrFactory) {
1102
- var _currentSuite$options;
1103
1239
  if (getCurrentTest()) {
1104
1240
  throw new Error("Calling the suite function inside test function is not allowed. It can be only called at the top level or inside another suite function.");
1105
1241
  }
@@ -1107,13 +1243,13 @@ function createSuite() {
1107
1243
  let { options, handler: factory } = parseArguments(factoryOrOptions, optionsOrFactory);
1108
1244
  const isConcurrentSpecified = options.concurrent || this.concurrent || options.sequential === false;
1109
1245
  const isSequentialSpecified = options.sequential || this.sequential || options.concurrent === false;
1110
- const { meta: parentMeta, ...parentOptions } = (currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.options) || {};
1246
+ const { meta: parentMeta, ...parentOptions } = currentSuite?.options || {};
1111
1247
  // inherit options from current suite
1112
1248
  options = {
1113
1249
  ...parentOptions,
1114
1250
  ...options
1115
1251
  };
1116
- const shuffle = this.shuffle ?? options.shuffle ?? (currentSuite === null || currentSuite === void 0 || (_currentSuite$options = currentSuite.options) === null || _currentSuite$options === void 0 ? void 0 : _currentSuite$options.shuffle) ?? (runner === null || runner === void 0 ? void 0 : runner.config.sequence.shuffle);
1252
+ const shuffle = this.shuffle ?? options.shuffle ?? currentSuite?.options?.shuffle ?? runner?.config.sequence.shuffle;
1117
1253
  if (shuffle != null) {
1118
1254
  options.shuffle = shuffle;
1119
1255
  }
@@ -1134,11 +1270,12 @@ function createSuite() {
1134
1270
  if (parentMeta) {
1135
1271
  options.meta = Object.assign(Object.create(null), parentMeta, options.meta);
1136
1272
  }
1137
- return createSuiteCollector(formatName(name), factory, mode, this.each, options, currentSuite === null || currentSuite === void 0 ? void 0 : currentSuite.fixtures());
1273
+ return createSuiteCollector(formatName(name), factory, mode, this.each, options);
1138
1274
  }
1139
1275
  suiteFn.each = function(cases, ...args) {
1140
- const suite = this.withContext();
1141
- this.setContext("each", true);
1276
+ const context = getChainableContext(this);
1277
+ const suite = context.withContext();
1278
+ context.setContext("each", true);
1142
1279
  if (Array.isArray(cases) && args.length) {
1143
1280
  cases = formatTemplateString(cases, args);
1144
1281
  }
@@ -1163,7 +1300,7 @@ function createSuite() {
1163
1300
  }
1164
1301
  }
1165
1302
  });
1166
- this.setContext("each", undefined);
1303
+ context.setContext("each", undefined);
1167
1304
  };
1168
1305
  };
1169
1306
  suiteFn.for = function(cases, ...args) {
@@ -1189,11 +1326,12 @@ function createSuite() {
1189
1326
  "todo"
1190
1327
  ], suiteFn);
1191
1328
  }
1192
- function createTaskCollector(fn, context) {
1329
+ function createTaskCollector(fn) {
1193
1330
  const taskFn = fn;
1194
1331
  taskFn.each = function(cases, ...args) {
1195
- const test = this.withContext();
1196
- this.setContext("each", true);
1332
+ const context = getChainableContext(this);
1333
+ const test = context.withContext();
1334
+ context.setContext("each", true);
1197
1335
  if (Array.isArray(cases) && args.length) {
1198
1336
  cases = formatTemplateString(cases, args);
1199
1337
  }
@@ -1218,11 +1356,12 @@ function createTaskCollector(fn, context) {
1218
1356
  }
1219
1357
  }
1220
1358
  });
1221
- this.setContext("each", undefined);
1359
+ context.setContext("each", undefined);
1222
1360
  };
1223
1361
  };
1224
1362
  taskFn.for = function(cases, ...args) {
1225
- const test = this.withContext();
1363
+ const context = getChainableContext(this);
1364
+ const test = context.withContext();
1226
1365
  if (Array.isArray(cases) && args.length) {
1227
1366
  cases = formatTemplateString(cases, args);
1228
1367
  }
@@ -1233,8 +1372,10 @@ function createTaskCollector(fn, context) {
1233
1372
  // monkey-patch handler to allow parsing fixture
1234
1373
  const handlerWrapper = handler ? (ctx) => handler(item, ctx) : undefined;
1235
1374
  if (handlerWrapper) {
1236
- handlerWrapper.__VITEST_FIXTURE_INDEX__ = 1;
1237
- handlerWrapper.toString = () => handler.toString();
1375
+ configureProps(handlerWrapper, {
1376
+ index: 1,
1377
+ original: handler
1378
+ });
1238
1379
  }
1239
1380
  test(formatTitle(_name, toArray(item), idx), options, handlerWrapper);
1240
1381
  });
@@ -1246,24 +1387,85 @@ function createTaskCollector(fn, context) {
1246
1387
  taskFn.runIf = function(condition) {
1247
1388
  return condition ? this : this.skip;
1248
1389
  };
1390
+ /**
1391
+ * Parse builder pattern arguments into a fixtures object.
1392
+ * Handles both builder pattern (name, options?, value) and object syntax.
1393
+ */
1394
+ function parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn) {
1395
+ // Object syntax: just return as-is
1396
+ if (typeof fixturesOrName !== "string") {
1397
+ return fixturesOrName;
1398
+ }
1399
+ const fixtureName = fixturesOrName;
1400
+ let fixtureOptions;
1401
+ let fixtureValue;
1402
+ if (maybeFn !== undefined) {
1403
+ // (name, options, value) or (name, options, fn)
1404
+ fixtureOptions = optionsOrFn;
1405
+ fixtureValue = maybeFn;
1406
+ } else {
1407
+ // (name, value) or (name, fn)
1408
+ // Check if optionsOrFn looks like fixture options (has scope or auto)
1409
+ if (optionsOrFn !== null && typeof optionsOrFn === "object" && !Array.isArray(optionsOrFn) && ("scope" in optionsOrFn || "auto" in optionsOrFn)) {
1410
+ // (name, options) with no value - treat as empty object fixture
1411
+ fixtureOptions = optionsOrFn;
1412
+ fixtureValue = {};
1413
+ } else {
1414
+ // (name, value) or (name, fn)
1415
+ fixtureOptions = undefined;
1416
+ fixtureValue = optionsOrFn;
1417
+ }
1418
+ }
1419
+ // Function value: wrap with onCleanup pattern
1420
+ if (typeof fixtureValue === "function") {
1421
+ const builderFn = fixtureValue;
1422
+ // Wrap builder pattern function (returns value) to use() pattern
1423
+ const fixture = async (ctx, use) => {
1424
+ let cleanup;
1425
+ const onCleanup = (fn) => {
1426
+ if (cleanup !== undefined) {
1427
+ throw new Error(`onCleanup can only be called once per fixture. ` + `Define separate fixtures if you need multiple cleanup functions.`);
1428
+ }
1429
+ cleanup = fn;
1430
+ };
1431
+ const value = await builderFn(ctx, { onCleanup });
1432
+ await use(value);
1433
+ if (cleanup) {
1434
+ await cleanup();
1435
+ }
1436
+ };
1437
+ configureProps(fixture, { original: builderFn });
1438
+ if (fixtureOptions) {
1439
+ return { [fixtureName]: [fixture, fixtureOptions] };
1440
+ }
1441
+ return { [fixtureName]: fixture };
1442
+ }
1443
+ // Non-function value: use directly
1444
+ if (fixtureOptions) {
1445
+ return { [fixtureName]: [fixtureValue, fixtureOptions] };
1446
+ }
1447
+ return { [fixtureName]: fixtureValue };
1448
+ }
1449
+ taskFn.override = function(fixturesOrName, optionsOrFn, maybeFn) {
1450
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
1451
+ getChainableContext(this).getFixtures().override(runner, userFixtures);
1452
+ return this;
1453
+ };
1249
1454
  taskFn.scoped = function(fixtures) {
1250
- const collector = getCurrentSuite();
1251
- collector.scoped(fixtures);
1455
+ console.warn(`test.scoped() is deprecated and will be removed in future versions. Please use test.override() instead.`);
1456
+ return this.override(fixtures);
1252
1457
  };
1253
- taskFn.extend = function(fixtures) {
1254
- const _context = mergeContextFixtures(fixtures, context || {}, runner);
1255
- const originalWrapper = fn;
1256
- return createTest(function(name, optionsOrFn, optionsOrTest) {
1257
- const collector = getCurrentSuite();
1258
- const scopedFixtures = collector.fixtures();
1259
- const context = { ...this };
1260
- if (scopedFixtures) {
1261
- context.fixtures = mergeScopedFixtures(context.fixtures || [], scopedFixtures);
1262
- }
1263
- originalWrapper.call(context, formatName(name), optionsOrFn, optionsOrTest);
1264
- }, _context);
1458
+ taskFn.extend = function(fixturesOrName, optionsOrFn, maybeFn) {
1459
+ const userFixtures = parseBuilderFixtures(fixturesOrName, optionsOrFn, maybeFn);
1460
+ const fixtures = getChainableContext(this).getFixtures().extend(runner, userFixtures);
1461
+ const _test = createTest(function(name, optionsOrFn, optionsOrTest) {
1462
+ fn.call(this, formatName(name), optionsOrFn, optionsOrTest);
1463
+ });
1464
+ getChainableContext(_test).mergeContext({ fixtures });
1465
+ return _test;
1265
1466
  };
1266
1467
  taskFn.describe = suite;
1468
+ taskFn.suite = suite;
1267
1469
  taskFn.beforeEach = beforeEach;
1268
1470
  taskFn.afterEach = afterEach;
1269
1471
  taskFn.beforeAll = beforeAll;
@@ -1277,14 +1479,11 @@ function createTaskCollector(fn, context) {
1277
1479
  "only",
1278
1480
  "todo",
1279
1481
  "fails"
1280
- ], taskFn);
1281
- if (context) {
1282
- _test.mergeContext(context);
1283
- }
1482
+ ], taskFn, { fixtures: new TestFixtures() });
1284
1483
  return _test;
1285
1484
  }
1286
- function createTest(fn, context) {
1287
- return createTaskCollector(fn, context);
1485
+ function createTest(fn) {
1486
+ return createTaskCollector(fn);
1288
1487
  }
1289
1488
  function formatName(name) {
1290
1489
  return typeof name === "string" ? name : typeof name === "function" ? name.name || "<anonymous>" : String(name);
@@ -1311,14 +1510,13 @@ function formatTitle(template, items, idx) {
1311
1510
  const isObjectItem = isObject(items[0]);
1312
1511
  function formatAttribute(s) {
1313
1512
  return s.replace(/\$([$\w.]+)/g, (_, key) => {
1314
- var _runner$config;
1315
1513
  const isArrayKey = /^\d+$/.test(key);
1316
1514
  if (!isObjectItem && !isArrayKey) {
1317
1515
  return `$${key}`;
1318
1516
  }
1319
1517
  const arrayElement = isArrayKey ? objectAttr(items, key) : undefined;
1320
1518
  const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
1321
- return objDisplay(value, { truncate: runner === null || runner === void 0 || (_runner$config = runner.config) === null || _runner$config === void 0 || (_runner$config = _runner$config.chaiConfig) === null || _runner$config === void 0 ? void 0 : _runner$config.truncateThreshold });
1519
+ return objDisplay(value, { truncate: runner?.config?.chaiConfig?.truncateThreshold });
1322
1520
  });
1323
1521
  }
1324
1522
  let output = "";
@@ -1374,8 +1572,7 @@ const collectorContext = {
1374
1572
  currentSuite: null
1375
1573
  };
1376
1574
  function collectTask(task) {
1377
- var _collectorContext$cur;
1378
- (_collectorContext$cur = collectorContext.currentSuite) === null || _collectorContext$cur === void 0 ? void 0 : _collectorContext$cur.tasks.push(task);
1575
+ collectorContext.currentSuite?.tasks.push(task);
1379
1576
  }
1380
1577
  async function runWithSuite(suite, fn) {
1381
1578
  const prev = collectorContext.currentSuite;
@@ -1395,16 +1592,15 @@ function withTimeout(fn, timeout, isHook = false, stackTraceError, onTimeout) {
1395
1592
  runner._currentTaskStartTime = startTime;
1396
1593
  runner._currentTaskTimeout = timeout;
1397
1594
  return new Promise((resolve_, reject_) => {
1398
- var _timer$unref;
1399
1595
  const timer = setTimeout(() => {
1400
1596
  clearTimeout(timer);
1401
1597
  rejectTimeoutError();
1402
1598
  }, timeout);
1403
1599
  // `unref` might not exist in browser
1404
- (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
1600
+ timer.unref?.();
1405
1601
  function rejectTimeoutError() {
1406
1602
  const error = makeTimeoutError(isHook, timeout, stackTraceError);
1407
- onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout(args, error);
1603
+ onTimeout?.(args, error);
1408
1604
  reject_(error);
1409
1605
  }
1410
1606
  function resolve(result) {
@@ -1444,6 +1640,23 @@ catch (error) {
1444
1640
  });
1445
1641
  });
1446
1642
  }
1643
+ function withCancel(fn, signal) {
1644
+ return (function runWithCancel(...args) {
1645
+ return new Promise((resolve, reject) => {
1646
+ signal.addEventListener("abort", () => reject(signal.reason));
1647
+ try {
1648
+ const result = fn(...args);
1649
+ if (typeof result === "object" && result != null && typeof result.then === "function") {
1650
+ result.then(resolve, reject);
1651
+ } else {
1652
+ resolve(result);
1653
+ }
1654
+ } catch (error) {
1655
+ reject(error);
1656
+ }
1657
+ });
1658
+ });
1659
+ }
1447
1660
  const abortControllers = new WeakMap();
1448
1661
  function abortIfTimeout([context], error) {
1449
1662
  if (context) {
@@ -1452,10 +1665,9 @@ function abortIfTimeout([context], error) {
1452
1665
  }
1453
1666
  function abortContextSignal(context, error) {
1454
1667
  const abortController = abortControllers.get(context);
1455
- abortController === null || abortController === void 0 ? void 0 : abortController.abort(error);
1668
+ abortController?.abort(error);
1456
1669
  }
1457
1670
  function createTestContext(test, runner) {
1458
- var _runner$extendTaskCon;
1459
1671
  const context = function() {
1460
1672
  throw new Error("done() callback is deprecated, use promise instead");
1461
1673
  };
@@ -1471,7 +1683,7 @@ function createTestContext(test, runner) {
1471
1683
  // do nothing
1472
1684
  return undefined;
1473
1685
  }
1474
- test.result ?? (test.result = { state: "skip" });
1686
+ test.result ??= { state: "skip" };
1475
1687
  test.result.pending = true;
1476
1688
  throw new PendingError("test is skipped; abort execution", test, typeof condition === "string" ? condition : note);
1477
1689
  };
@@ -1502,34 +1714,23 @@ function createTestContext(test, runner) {
1502
1714
  }));
1503
1715
  });
1504
1716
  context.onTestFailed = (handler, timeout) => {
1505
- test.onFailed || (test.onFailed = []);
1717
+ test.onFailed ||= [];
1506
1718
  test.onFailed.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1507
1719
  };
1508
1720
  context.onTestFinished = (handler, timeout) => {
1509
- test.onFinished || (test.onFinished = []);
1721
+ test.onFinished ||= [];
1510
1722
  test.onFinished.push(withTimeout(handler, timeout ?? runner.config.hookTimeout, true, new Error("STACK_TRACE_ERROR"), (_, error) => abortController.abort(error)));
1511
1723
  };
1512
- return ((_runner$extendTaskCon = runner.extendTaskContext) === null || _runner$extendTaskCon === void 0 ? void 0 : _runner$extendTaskCon.call(runner, context)) || context;
1724
+ return runner.extendTaskContext?.(context) || context;
1513
1725
  }
1514
1726
  function makeTimeoutError(isHook, timeout, stackTraceError) {
1515
1727
  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"}".`;
1516
1728
  const error = new Error(message);
1517
- if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
1729
+ if (stackTraceError?.stack) {
1518
1730
  error.stack = stackTraceError.stack.replace(error.message, stackTraceError.message);
1519
1731
  }
1520
1732
  return error;
1521
1733
  }
1522
- const fileContexts = new WeakMap();
1523
- function getFileContext(file) {
1524
- const context = fileContexts.get(file);
1525
- if (!context) {
1526
- throw new Error(`Cannot find file context for ${file.name}`);
1527
- }
1528
- return context;
1529
- }
1530
- function setFileContext(file, context) {
1531
- fileContexts.set(file, context);
1532
- }
1533
1734
 
1534
1735
  async function runSetupFiles(config, files, runner) {
1535
1736
  if (config.sequence.setupFiles === "parallel") {
@@ -1558,13 +1759,11 @@ async function collectTests(specs, runner) {
1558
1759
  const testTagsFilter = typeof spec === "object" && spec.testTagsFilter ? createTagsFilter(spec.testTagsFilter, config.tags) : undefined;
1559
1760
  const fileTags = typeof spec === "string" ? [] : spec.fileTags || [];
1560
1761
  const file = createFileTask(filepath, config.root, config.name, runner.pool, runner.viteEnvironment);
1561
- setFileContext(file, Object.create(null));
1562
1762
  file.tags = fileTags;
1563
1763
  file.shuffle = config.sequence.shuffle;
1564
1764
  try {
1565
- var _runner$onCollectStar, _runner$getImportDura;
1566
1765
  validateTags(runner.config, fileTags);
1567
- (_runner$onCollectStar = runner.onCollectStart) === null || _runner$onCollectStar === void 0 ? void 0 : _runner$onCollectStar.call(runner, file);
1766
+ runner.onCollectStart?.(file);
1568
1767
  clearCollectorContext(file, runner);
1569
1768
  const setupFiles = toArray(config.setupFiles);
1570
1769
  if (setupFiles.length) {
@@ -1577,7 +1776,7 @@ async function collectTests(specs, runner) {
1577
1776
  }
1578
1777
  const collectStart = now$1();
1579
1778
  await runner.importFile(filepath, "collect");
1580
- const durations = (_runner$getImportDura = runner.getImportDurations) === null || _runner$getImportDura === void 0 ? void 0 : _runner$getImportDura.call(runner);
1779
+ const durations = runner.getImportDurations?.();
1581
1780
  if (durations) {
1582
1781
  file.importDurations = durations;
1583
1782
  }
@@ -1601,13 +1800,12 @@ async function collectTests(specs, runner) {
1601
1800
  setHooks(file, fileHooks);
1602
1801
  file.collectDuration = now$1() - collectStart;
1603
1802
  } catch (e) {
1604
- var _runner$getImportDura2;
1605
- const error = processError(e);
1803
+ const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, runner.config.diffOptions)) : [processError(e, runner.config.diffOptions)];
1606
1804
  file.result = {
1607
1805
  state: "fail",
1608
- errors: [error]
1806
+ errors
1609
1807
  };
1610
- const durations = (_runner$getImportDura2 = runner.getImportDurations) === null || _runner$getImportDura2 === void 0 ? void 0 : _runner$getImportDura2.call(runner);
1808
+ const durations = runner.getImportDurations?.();
1611
1809
  if (durations) {
1612
1810
  file.importDurations = durations;
1613
1811
  }
@@ -1637,6 +1835,7 @@ function mergeHooks(baseHooks, hooks) {
1637
1835
  const now = globalThis.performance ? globalThis.performance.now.bind(globalThis.performance) : Date.now;
1638
1836
  const unixNow = Date.now;
1639
1837
  const { clearTimeout, setTimeout } = getSafeTimers();
1838
+ let limitMaxConcurrency;
1640
1839
  /**
1641
1840
  * Normalizes retry configuration to extract individual values.
1642
1841
  * Handles both number and object forms.
@@ -1710,14 +1909,14 @@ async function callTestHooks(runner, test, hooks, sequence) {
1710
1909
  };
1711
1910
  if (sequence === "parallel") {
1712
1911
  try {
1713
- await Promise.all(hooks.map((fn) => fn(test.context)));
1912
+ await Promise.all(hooks.map((fn) => limitMaxConcurrency(() => fn(test.context))));
1714
1913
  } catch (e) {
1715
1914
  failTask(test.result, e, runner.config.diffOptions);
1716
1915
  }
1717
1916
  } else {
1718
1917
  for (const fn of hooks) {
1719
1918
  try {
1720
- await fn(test.context);
1919
+ await limitMaxConcurrency(() => fn(test.context));
1721
1920
  } catch (e) {
1722
1921
  failTask(test.result, e, runner.config.diffOptions);
1723
1922
  }
@@ -1739,7 +1938,9 @@ async function callSuiteHook(suite, currentTask, name, runner, args) {
1739
1938
  updateSuiteHookState(currentTask, name, "run", runner);
1740
1939
  }
1741
1940
  async function runHook(hook) {
1742
- return getBeforeHookCleanupCallback(hook, await hook(...args), name === "beforeEach" ? args[0] : undefined);
1941
+ return limitMaxConcurrency(async () => {
1942
+ return getBeforeHookCleanupCallback(hook, await hook(...args), name === "beforeEach" ? args[0] : undefined);
1943
+ });
1743
1944
  }
1744
1945
  if (sequence === "parallel") {
1745
1946
  callbacks.push(...await Promise.all(hooks.map((hook) => runHook(hook))));
@@ -1772,7 +1973,7 @@ function makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError) {
1772
1973
  const message = `The ${phase} phase of "${hookName}" hook timed out after ${timeout}ms.`;
1773
1974
  const ErrorClass = phase === "setup" ? AroundHookSetupError : AroundHookTeardownError;
1774
1975
  const error = new ErrorClass(message);
1775
- if (stackTraceError === null || stackTraceError === void 0 ? void 0 : stackTraceError.stack) {
1976
+ if (stackTraceError?.stack) {
1776
1977
  error.stack = stackTraceError.stack.replace(stackTraceError.message, error.message);
1777
1978
  }
1778
1979
  return error;
@@ -1783,17 +1984,19 @@ async function callAroundHooks(runInner, options) {
1783
1984
  await runInner();
1784
1985
  return;
1785
1986
  }
1987
+ const hookErrors = [];
1786
1988
  const createTimeoutPromise = (timeout, phase, stackTraceError) => {
1787
1989
  let timer;
1990
+ let timedout = false;
1788
1991
  const promise = new Promise((_, reject) => {
1789
1992
  if (timeout > 0 && timeout !== Number.POSITIVE_INFINITY) {
1790
- var _timer$unref;
1791
1993
  timer = setTimeout(() => {
1994
+ timedout = true;
1792
1995
  const error = makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError);
1793
- onTimeout === null || onTimeout === void 0 ? void 0 : onTimeout(error);
1996
+ onTimeout?.(error);
1794
1997
  reject(error);
1795
1998
  }, timeout);
1796
- (_timer$unref = timer.unref) === null || _timer$unref === void 0 ? void 0 : _timer$unref.call(timer);
1999
+ timer.unref?.();
1797
2000
  }
1798
2001
  });
1799
2002
  const clear = () => {
@@ -1804,7 +2007,8 @@ async function callAroundHooks(runInner, options) {
1804
2007
  };
1805
2008
  return {
1806
2009
  promise,
1807
- clear
2010
+ clear,
2011
+ isTimedOut: () => timedout
1808
2012
  };
1809
2013
  };
1810
2014
  const runNextHook = async (index) => {
@@ -1817,6 +2021,8 @@ async function callAroundHooks(runInner, options) {
1817
2021
  let useCalled = false;
1818
2022
  let setupTimeout;
1819
2023
  let teardownTimeout;
2024
+ let setupLimitConcurrencyRelease;
2025
+ let teardownLimitConcurrencyRelease;
1820
2026
  // Promise that resolves when use() is called (setup phase complete)
1821
2027
  let resolveUseCalled;
1822
2028
  const useCalledPromise = new Promise((resolve) => {
@@ -1835,6 +2041,13 @@ async function callAroundHooks(runInner, options) {
1835
2041
  rejectHookComplete = reject;
1836
2042
  });
1837
2043
  const use = async () => {
2044
+ // shouldn't continue to next (runTest/Suite or inner aroundEach/All) when aroundEach/All setup timed out.
2045
+ if (setupTimeout.isTimedOut()) {
2046
+ // we can throw any error to bail out.
2047
+ // this error is not seen by end users since `runNextHook` already rejected with timeout error
2048
+ // and this error is caught by `rejectHookComplete`.
2049
+ throw new Error("__VITEST_INTERNAL_AROUND_HOOK_ABORT__");
2050
+ }
1838
2051
  if (useCalled) {
1839
2052
  throw new AroundHookMultipleCallsError(`The \`${callbackName}\` callback was called multiple times in the \`${hookName}\` hook. ` + `The callback can only be called once per hook.`);
1840
2053
  }
@@ -1842,13 +2055,16 @@ async function callAroundHooks(runInner, options) {
1842
2055
  resolveUseCalled();
1843
2056
  // Setup phase completed - clear setup timer
1844
2057
  setupTimeout.clear();
2058
+ setupLimitConcurrencyRelease?.();
1845
2059
  // Run inner hooks - don't time this against our teardown timeout
1846
- await runNextHook(index + 1);
2060
+ await runNextHook(index + 1).catch((e) => hookErrors.push(e));
2061
+ teardownLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
1847
2062
  // Start teardown timer after inner hooks complete - only times this hook's teardown code
1848
2063
  teardownTimeout = createTimeoutPromise(timeout, "teardown", stackTraceError);
1849
2064
  // Signal that use() is returning (teardown phase starting)
1850
2065
  resolveUseReturned();
1851
2066
  };
2067
+ setupLimitConcurrencyRelease = await limitMaxConcurrency.acquire();
1852
2068
  // Start setup timeout
1853
2069
  setupTimeout = createTimeoutPromise(timeout, "setup", stackTraceError);
1854
2070
  (async () => {
@@ -1860,6 +2076,9 @@ async function callAroundHooks(runInner, options) {
1860
2076
  resolveHookComplete();
1861
2077
  } catch (error) {
1862
2078
  rejectHookComplete(error);
2079
+ } finally {
2080
+ setupLimitConcurrencyRelease?.();
2081
+ teardownLimitConcurrencyRelease?.();
1863
2082
  }
1864
2083
  })();
1865
2084
  // Wait for either: use() to be called OR hook to complete (error) OR setup timeout
@@ -1870,6 +2089,7 @@ async function callAroundHooks(runInner, options) {
1870
2089
  setupTimeout.promise
1871
2090
  ]);
1872
2091
  } finally {
2092
+ setupLimitConcurrencyRelease?.();
1873
2093
  setupTimeout.clear();
1874
2094
  }
1875
2095
  // Wait for use() to return (inner hooks complete) OR hook to complete (error during inner hooks)
@@ -1877,12 +2097,16 @@ async function callAroundHooks(runInner, options) {
1877
2097
  // Now teardownTimeout is guaranteed to be set
1878
2098
  // Wait for hook to complete (teardown) OR teardown timeout
1879
2099
  try {
1880
- await Promise.race([hookCompletePromise, teardownTimeout.promise]);
2100
+ await Promise.race([hookCompletePromise, teardownTimeout?.promise]);
1881
2101
  } finally {
1882
- teardownTimeout.clear();
2102
+ teardownLimitConcurrencyRelease?.();
2103
+ teardownTimeout?.clear();
1883
2104
  }
1884
2105
  };
1885
- await runNextHook(0);
2106
+ await runNextHook(0).catch((e) => hookErrors.push(e));
2107
+ if (hookErrors.length > 0) {
2108
+ throw hookErrors;
2109
+ }
1886
2110
  }
1887
2111
  async function callAroundAllHooks(suite, runSuiteInner) {
1888
2112
  await callAroundHooks(runSuiteInner, {
@@ -1912,7 +2136,6 @@ const eventsPacks = [];
1912
2136
  const pendingTasksUpdates = [];
1913
2137
  function sendTasksUpdate(runner) {
1914
2138
  if (packs.size) {
1915
- var _runner$onTaskUpdate;
1916
2139
  const taskPacks = Array.from(packs).map(([id, task]) => {
1917
2140
  return [
1918
2141
  id,
@@ -1920,7 +2143,7 @@ function sendTasksUpdate(runner) {
1920
2143
  task[1]
1921
2144
  ];
1922
2145
  });
1923
- const p = (_runner$onTaskUpdate = runner.onTaskUpdate) === null || _runner$onTaskUpdate === void 0 ? void 0 : _runner$onTaskUpdate.call(runner, taskPacks, eventsPacks);
2146
+ const p = runner.onTaskUpdate?.(taskPacks, eventsPacks);
1924
2147
  if (p) {
1925
2148
  pendingTasksUpdates.push(p);
1926
2149
  // remove successful promise to not grow array indefnitely,
@@ -1947,7 +2170,7 @@ function throttle(fn, ms) {
1947
2170
  return fn.apply(this, args);
1948
2171
  }
1949
2172
  // Make sure fn is still called even if there are no further calls
1950
- pendingCall ?? (pendingCall = setTimeout(() => call.bind(this)(...args), ms));
2173
+ pendingCall ??= setTimeout(() => call.bind(this)(...args), ms);
1951
2174
  };
1952
2175
  }
1953
2176
  // throttle based on summary reporter's DURATION_UPDATE_INTERVAL_MS
@@ -1971,14 +2194,14 @@ async function callCleanupHooks(runner, cleanups) {
1971
2194
  if (typeof fn !== "function") {
1972
2195
  return;
1973
2196
  }
1974
- await fn();
2197
+ await limitMaxConcurrency(() => fn());
1975
2198
  }));
1976
2199
  } else {
1977
2200
  for (const fn of cleanups) {
1978
2201
  if (typeof fn !== "function") {
1979
2202
  continue;
1980
2203
  }
1981
- await fn();
2204
+ await limitMaxConcurrency(() => fn());
1982
2205
  }
1983
2206
  }
1984
2207
  }
@@ -2002,14 +2225,13 @@ function passesRetryCondition(test, errors) {
2002
2225
  return false;
2003
2226
  }
2004
2227
  async function runTest(test, runner) {
2005
- var _runner$onBeforeRunTa, _test$result, _runner$onAfterRunTas;
2006
- await ((_runner$onBeforeRunTa = runner.onBeforeRunTask) === null || _runner$onBeforeRunTa === void 0 ? void 0 : _runner$onBeforeRunTa.call(runner, test));
2228
+ await runner.onBeforeRunTask?.(test);
2007
2229
  if (test.mode !== "run" && test.mode !== "queued") {
2008
2230
  updateTask("test-prepare", test, runner);
2009
2231
  updateTask("test-finished", test, runner);
2010
2232
  return;
2011
2233
  }
2012
- if (((_test$result = test.result) === null || _test$result === void 0 ? void 0 : _test$result.state) === "fail") {
2234
+ if (test.result?.state === "fail") {
2013
2235
  // should not be possible to get here, I think this is just copy pasted from suite
2014
2236
  // TODO: maybe someone fails tests in `beforeAll` hooks?
2015
2237
  // https://github.com/vitest-dev/vitest/pull/7069
@@ -2031,48 +2253,40 @@ async function runTest(test, runner) {
2031
2253
  for (let repeatCount = 0; repeatCount <= repeats; repeatCount++) {
2032
2254
  const retry = getRetryCount(test.retry);
2033
2255
  for (let retryCount = 0; retryCount <= retry; retryCount++) {
2034
- var _test$result2, _test$result3;
2035
2256
  let beforeEachCleanups = [];
2036
2257
  // fixtureCheckpoint is passed by callAroundEachHooks - it represents the count
2037
2258
  // of fixture cleanup functions AFTER all aroundEach fixtures have been resolved
2038
2259
  // but BEFORE the test runs. This allows us to clean up only fixtures created
2039
2260
  // inside runTest while preserving aroundEach fixtures for teardown.
2040
2261
  await callAroundEachHooks(suite, test, async (fixtureCheckpoint) => {
2041
- var _test$onFinished, _test$onFailed, _runner$onAfterRetryT;
2042
2262
  try {
2043
- var _runner$onBeforeTryTa, _runner$onAfterTryTas;
2044
- await ((_runner$onBeforeTryTa = runner.onBeforeTryTask) === null || _runner$onBeforeTryTa === void 0 ? void 0 : _runner$onBeforeTryTa.call(runner, test, {
2263
+ await runner.onBeforeTryTask?.(test, {
2045
2264
  retry: retryCount,
2046
2265
  repeats: repeatCount
2047
- }));
2266
+ });
2048
2267
  test.result.repeatCount = repeatCount;
2049
2268
  beforeEachCleanups = await $("test.beforeEach", () => callSuiteHook(suite, test, "beforeEach", runner, [test.context, suite]));
2050
2269
  if (runner.runTask) {
2051
- await $("test.callback", () => runner.runTask(test));
2270
+ await $("test.callback", () => limitMaxConcurrency(() => runner.runTask(test)));
2052
2271
  } else {
2053
2272
  const fn = getFn(test);
2054
2273
  if (!fn) {
2055
2274
  throw new Error("Test function is not found. Did you add it using `setFn`?");
2056
2275
  }
2057
- await $("test.callback", () => fn());
2276
+ await $("test.callback", () => limitMaxConcurrency(() => fn()));
2058
2277
  }
2059
- await ((_runner$onAfterTryTas = runner.onAfterTryTask) === null || _runner$onAfterTryTas === void 0 ? void 0 : _runner$onAfterTryTas.call(runner, test, {
2278
+ await runner.onAfterTryTask?.(test, {
2060
2279
  retry: retryCount,
2061
2280
  repeats: repeatCount
2062
- }));
2281
+ });
2063
2282
  if (test.result.state !== "fail") {
2064
- if (!test.repeats) {
2065
- test.result.state = "pass";
2066
- } else if (test.repeats && retry === retryCount) {
2067
- test.result.state = "pass";
2068
- }
2283
+ test.result.state = "pass";
2069
2284
  }
2070
2285
  } catch (e) {
2071
2286
  failTask(test.result, e, runner.config.diffOptions);
2072
2287
  }
2073
2288
  try {
2074
- var _runner$onTaskFinishe;
2075
- await ((_runner$onTaskFinishe = runner.onTaskFinished) === null || _runner$onTaskFinishe === void 0 ? void 0 : _runner$onTaskFinishe.call(runner, test));
2289
+ await runner.onTaskFinished?.(test);
2076
2290
  } catch (e) {
2077
2291
  failTask(test.result, e, runner.config.diffOptions);
2078
2292
  }
@@ -2087,18 +2301,18 @@ async function runTest(test, runner) {
2087
2301
  } catch (e) {
2088
2302
  failTask(test.result, e, runner.config.diffOptions);
2089
2303
  }
2090
- if ((_test$onFinished = test.onFinished) === null || _test$onFinished === void 0 ? void 0 : _test$onFinished.length) {
2304
+ if (test.onFinished?.length) {
2091
2305
  await $("test.onFinished", () => callTestHooks(runner, test, test.onFinished, "stack"));
2092
2306
  }
2093
- if (test.result.state === "fail" && ((_test$onFailed = test.onFailed) === null || _test$onFailed === void 0 ? void 0 : _test$onFailed.length)) {
2307
+ if (test.result.state === "fail" && test.onFailed?.length) {
2094
2308
  await $("test.onFailed", () => callTestHooks(runner, test, test.onFailed, runner.config.sequence.hooks));
2095
2309
  }
2096
2310
  test.onFailed = undefined;
2097
2311
  test.onFinished = undefined;
2098
- await ((_runner$onAfterRetryT = runner.onAfterRetryTask) === null || _runner$onAfterRetryT === void 0 ? void 0 : _runner$onAfterRetryT.call(runner, test, {
2312
+ await runner.onAfterRetryTask?.(test, {
2099
2313
  retry: retryCount,
2100
2314
  repeats: repeatCount
2101
- }));
2315
+ });
2102
2316
  }).catch((error) => {
2103
2317
  failTask(test.result, error, runner.config.diffOptions);
2104
2318
  });
@@ -2110,12 +2324,11 @@ async function runTest(test, runner) {
2110
2324
  failTask(test.result, e, runner.config.diffOptions);
2111
2325
  }
2112
2326
  // skipped with new PendingError
2113
- if (((_test$result2 = test.result) === null || _test$result2 === void 0 ? void 0 : _test$result2.pending) || ((_test$result3 = test.result) === null || _test$result3 === void 0 ? void 0 : _test$result3.state) === "skip") {
2114
- var _test$result4;
2327
+ if (test.result?.pending || test.result?.state === "skip") {
2115
2328
  test.mode = "skip";
2116
2329
  test.result = {
2117
2330
  state: "skip",
2118
- note: (_test$result4 = test.result) === null || _test$result4 === void 0 ? void 0 : _test$result4.note,
2331
+ note: test.result?.note,
2119
2332
  pending: true,
2120
2333
  duration: now() - start
2121
2334
  };
@@ -2157,7 +2370,7 @@ async function runTest(test, runner) {
2157
2370
  cleanupRunningTest();
2158
2371
  setCurrentTest(undefined);
2159
2372
  test.result.duration = now() - start;
2160
- await ((_runner$onAfterRunTas = runner.onAfterRunTask) === null || _runner$onAfterRunTas === void 0 ? void 0 : _runner$onAfterRunTas.call(runner, test));
2373
+ await runner.onAfterRunTask?.(test);
2161
2374
  updateTask("test-finished", test, runner);
2162
2375
  }
2163
2376
  function failTask(result, err, diffOptions) {
@@ -2167,12 +2380,17 @@ function failTask(result, err, diffOptions) {
2167
2380
  result.pending = true;
2168
2381
  return;
2169
2382
  }
2383
+ if (err instanceof TestRunAbortError) {
2384
+ result.state = "skip";
2385
+ result.note = err.message;
2386
+ return;
2387
+ }
2170
2388
  result.state = "fail";
2171
2389
  const errors = Array.isArray(err) ? err : [err];
2172
2390
  for (const e of errors) {
2173
- const error = processError(e, diffOptions);
2174
- result.errors ?? (result.errors = []);
2175
- result.errors.push(error);
2391
+ const errors = e instanceof AggregateError ? e.errors.map((e) => processError(e, diffOptions)) : [processError(e, diffOptions)];
2392
+ result.errors ??= [];
2393
+ result.errors.push(...errors);
2176
2394
  }
2177
2395
  }
2178
2396
  function markTasksAsSkipped(suite, runner) {
@@ -2188,10 +2406,25 @@ function markTasksAsSkipped(suite, runner) {
2188
2406
  }
2189
2407
  });
2190
2408
  }
2409
+ function markPendingTasksAsSkipped(suite, runner, note) {
2410
+ suite.tasks.forEach((t) => {
2411
+ if (!t.result || t.result.state === "run") {
2412
+ t.mode = "skip";
2413
+ t.result = {
2414
+ ...t.result,
2415
+ state: "skip",
2416
+ note
2417
+ };
2418
+ updateTask("test-cancel", t, runner);
2419
+ }
2420
+ if (t.type === "suite") {
2421
+ markPendingTasksAsSkipped(t, runner, note);
2422
+ }
2423
+ });
2424
+ }
2191
2425
  async function runSuite(suite, runner) {
2192
- var _runner$onBeforeRunSu, _suite$result;
2193
- await ((_runner$onBeforeRunSu = runner.onBeforeRunSuite) === null || _runner$onBeforeRunSu === void 0 ? void 0 : _runner$onBeforeRunSu.call(runner, suite));
2194
- if (((_suite$result = suite.result) === null || _suite$result === void 0 ? void 0 : _suite$result.state) === "fail") {
2426
+ await runner.onBeforeRunSuite?.(suite);
2427
+ if (suite.result?.state === "fail") {
2195
2428
  markTasksAsSkipped(suite, runner);
2196
2429
  // failed during collection
2197
2430
  updateTask("suite-failed-early", suite, runner);
@@ -2213,7 +2446,6 @@ async function runSuite(suite, runner) {
2213
2446
  suite.result.state = "todo";
2214
2447
  updateTask("suite-finished", suite, runner);
2215
2448
  } else {
2216
- var _runner$onAfterRunSui;
2217
2449
  let suiteRan = false;
2218
2450
  try {
2219
2451
  await callAroundAllHooks(suite, async () => {
@@ -2257,8 +2489,8 @@ async function runSuite(suite, runner) {
2257
2489
  await $("suite.cleanup", () => callCleanupHooks(runner, beforeAllCleanups));
2258
2490
  }
2259
2491
  if (suite.file === suite) {
2260
- const context = getFileContext(suite);
2261
- await callFixtureCleanup(context);
2492
+ const contexts = TestFixtures.getFileContexts(suite.file);
2493
+ await Promise.all(contexts.map((context) => callFixtureCleanup(context)));
2262
2494
  }
2263
2495
  } catch (e) {
2264
2496
  failTask(suite.result, e, runner.config.diffOptions);
@@ -2274,9 +2506,8 @@ async function runSuite(suite, runner) {
2274
2506
  }
2275
2507
  if (suite.mode === "run" || suite.mode === "queued") {
2276
2508
  if (!runner.config.passWithNoTests && !hasTests(suite)) {
2277
- var _suite$result$errors;
2278
2509
  suite.result.state = "fail";
2279
- if (!((_suite$result$errors = suite.result.errors) === null || _suite$result$errors === void 0 ? void 0 : _suite$result$errors.length)) {
2510
+ if (!suite.result.errors?.length) {
2280
2511
  const error = processError(new Error(`No test found in suite ${suite.name}`));
2281
2512
  suite.result.errors = [error];
2282
2513
  }
@@ -2287,44 +2518,38 @@ async function runSuite(suite, runner) {
2287
2518
  }
2288
2519
  }
2289
2520
  suite.result.duration = now() - start;
2290
- await ((_runner$onAfterRunSui = runner.onAfterRunSuite) === null || _runner$onAfterRunSui === void 0 ? void 0 : _runner$onAfterRunSui.call(runner, suite));
2521
+ await runner.onAfterRunSuite?.(suite);
2291
2522
  updateTask("suite-finished", suite, runner);
2292
2523
  }
2293
2524
  }
2294
- let limitMaxConcurrency;
2295
2525
  async function runSuiteChild(c, runner) {
2296
2526
  const $ = runner.trace;
2297
2527
  if (c.type === "test") {
2298
- return limitMaxConcurrency(() => {
2299
- var _c$location, _c$location2;
2300
- return $("run.test", {
2301
- "vitest.test.id": c.id,
2302
- "vitest.test.name": c.name,
2303
- "vitest.test.mode": c.mode,
2304
- "vitest.test.timeout": c.timeout,
2305
- "code.file.path": c.file.filepath,
2306
- "code.line.number": (_c$location = c.location) === null || _c$location === void 0 ? void 0 : _c$location.line,
2307
- "code.column.number": (_c$location2 = c.location) === null || _c$location2 === void 0 ? void 0 : _c$location2.column
2308
- }, () => runTest(c, runner));
2309
- });
2528
+ return $("run.test", {
2529
+ "vitest.test.id": c.id,
2530
+ "vitest.test.name": c.name,
2531
+ "vitest.test.mode": c.mode,
2532
+ "vitest.test.timeout": c.timeout,
2533
+ "code.file.path": c.file.filepath,
2534
+ "code.line.number": c.location?.line,
2535
+ "code.column.number": c.location?.column
2536
+ }, () => runTest(c, runner));
2310
2537
  } else if (c.type === "suite") {
2311
- var _c$location3, _c$location4;
2312
2538
  return $("run.suite", {
2313
2539
  "vitest.suite.id": c.id,
2314
2540
  "vitest.suite.name": c.name,
2315
2541
  "vitest.suite.mode": c.mode,
2316
2542
  "code.file.path": c.file.filepath,
2317
- "code.line.number": (_c$location3 = c.location) === null || _c$location3 === void 0 ? void 0 : _c$location3.line,
2318
- "code.column.number": (_c$location4 = c.location) === null || _c$location4 === void 0 ? void 0 : _c$location4.column
2543
+ "code.line.number": c.location?.line,
2544
+ "code.column.number": c.location?.column
2319
2545
  }, () => runSuite(c, runner));
2320
2546
  }
2321
2547
  }
2322
2548
  async function runFiles(files, runner) {
2323
- limitMaxConcurrency ?? (limitMaxConcurrency = limitConcurrency(runner.config.maxConcurrency));
2549
+ limitMaxConcurrency ??= limitConcurrency(runner.config.maxConcurrency);
2324
2550
  for (const file of files) {
2325
2551
  if (!file.tasks.length && !runner.config.passWithNoTests) {
2326
- var _file$result;
2327
- if (!((_file$result = file.result) === null || _file$result === void 0 || (_file$result = _file$result.errors) === null || _file$result === void 0 ? void 0 : _file$result.length)) {
2552
+ if (!file.result?.errors?.length) {
2328
2553
  const error = processError(new Error(`No test suite found in file ${file.filepath}`));
2329
2554
  file.result = {
2330
2555
  state: "fail",
@@ -2346,37 +2571,35 @@ function defaultTrace(_, attributes, cb) {
2346
2571
  return cb();
2347
2572
  }
2348
2573
  async function startTests(specs, runner) {
2349
- var _runner$cancel;
2350
- runner.trace ?? (runner.trace = defaultTrace);
2351
- const cancel = (_runner$cancel = runner.cancel) === null || _runner$cancel === void 0 ? void 0 : _runner$cancel.bind(runner);
2574
+ runner.trace ??= defaultTrace;
2575
+ const cancel = runner.cancel?.bind(runner);
2352
2576
  // Ideally, we need to have an event listener for this, but only have a runner here.
2353
2577
  // Adding another onCancel felt wrong (maybe it needs to be refactored)
2354
2578
  runner.cancel = (reason) => {
2355
2579
  // We intentionally create only one error since there is only one test run that can be cancelled
2356
2580
  const error = new TestRunAbortError("The test run was aborted by the user.", reason);
2357
- getRunningTests().forEach((test) => abortContextSignal(test.context, error));
2358
- return cancel === null || cancel === void 0 ? void 0 : cancel(reason);
2581
+ getRunningTests().forEach((test) => {
2582
+ abortContextSignal(test.context, error);
2583
+ markPendingTasksAsSkipped(test.file, runner, error.message);
2584
+ });
2585
+ return cancel?.(reason);
2359
2586
  };
2360
2587
  if (!workerRunners.has(runner)) {
2361
- var _runner$onCleanupWork;
2362
- (_runner$onCleanupWork = runner.onCleanupWorkerContext) === null || _runner$onCleanupWork === void 0 ? void 0 : _runner$onCleanupWork.call(runner, async () => {
2363
- var _runner$getWorkerCont;
2364
- const context = (_runner$getWorkerCont = runner.getWorkerContext) === null || _runner$getWorkerCont === void 0 ? void 0 : _runner$getWorkerCont.call(runner);
2365
- if (context) {
2366
- await callFixtureCleanup(context);
2367
- }
2588
+ runner.onCleanupWorkerContext?.(async () => {
2589
+ await Promise.all([...TestFixtures.getWorkerContexts()].map((context) => callFixtureCleanup(context))).finally(() => {
2590
+ TestFixtures.clearDefinitions();
2591
+ });
2368
2592
  });
2369
2593
  workerRunners.add(runner);
2370
2594
  }
2371
2595
  try {
2372
- var _runner$onBeforeColle, _runner$onCollected, _runner$onBeforeRunFi, _runner$onAfterRunFil;
2373
2596
  const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
2374
- await ((_runner$onBeforeColle = runner.onBeforeCollect) === null || _runner$onBeforeColle === void 0 ? void 0 : _runner$onBeforeColle.call(runner, paths));
2597
+ await runner.onBeforeCollect?.(paths);
2375
2598
  const files = await collectTests(specs, runner);
2376
- await ((_runner$onCollected = runner.onCollected) === null || _runner$onCollected === void 0 ? void 0 : _runner$onCollected.call(runner, files));
2377
- await ((_runner$onBeforeRunFi = runner.onBeforeRunFiles) === null || _runner$onBeforeRunFi === void 0 ? void 0 : _runner$onBeforeRunFi.call(runner, files));
2599
+ await runner.onCollected?.(files);
2600
+ await runner.onBeforeRunFiles?.(files);
2378
2601
  await runFiles(files, runner);
2379
- await ((_runner$onAfterRunFil = runner.onAfterRunFiles) === null || _runner$onAfterRunFil === void 0 ? void 0 : _runner$onAfterRunFil.call(runner, files));
2602
+ await runner.onAfterRunFiles?.(files);
2380
2603
  await finishSendTasksUpdate(runner);
2381
2604
  return files;
2382
2605
  } finally {
@@ -2384,12 +2607,11 @@ async function startTests(specs, runner) {
2384
2607
  }
2385
2608
  }
2386
2609
  async function publicCollect(specs, runner) {
2387
- var _runner$onBeforeColle2, _runner$onCollected2;
2388
- runner.trace ?? (runner.trace = defaultTrace);
2610
+ runner.trace ??= defaultTrace;
2389
2611
  const paths = specs.map((f) => typeof f === "string" ? f : f.filepath);
2390
- await ((_runner$onBeforeColle2 = runner.onBeforeCollect) === null || _runner$onBeforeColle2 === void 0 ? void 0 : _runner$onBeforeColle2.call(runner, paths));
2612
+ await runner.onBeforeCollect?.(paths);
2391
2613
  const files = await collectTests(specs, runner);
2392
- await ((_runner$onCollected2 = runner.onCollected) === null || _runner$onCollected2 === void 0 ? void 0 : _runner$onCollected2.call(runner, files));
2614
+ await runner.onCollected?.(files);
2393
2615
  return files;
2394
2616
  }
2395
2617
 
@@ -2403,12 +2625,13 @@ async function publicCollect(specs, runner) {
2403
2625
  *
2404
2626
  * Vitest automatically injects the source location where the artifact was created and manages any attachments you include.
2405
2627
  *
2628
+ * **Note:** artifacts must be recorded before the task is reported. Any artifacts recorded after that will not be included in the task.
2629
+ *
2406
2630
  * @param task - The test task context, typically accessed via `this.task` in custom matchers or `context.task` in tests
2407
2631
  * @param artifact - The artifact to record. Must extend {@linkcode TestArtifactBase}
2408
2632
  *
2409
2633
  * @returns A promise that resolves to the recorded artifact with location injected
2410
2634
  *
2411
- * @throws {Error} If called after the test has finished running
2412
2635
  * @throws {Error} If the test runner doesn't support artifacts
2413
2636
  *
2414
2637
  * @example
@@ -2429,9 +2652,6 @@ async function publicCollect(specs, runner) {
2429
2652
  */
2430
2653
  async function recordArtifact(task, artifact) {
2431
2654
  const runner = getRunner();
2432
- if (task.result && task.result.state !== "run") {
2433
- 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.`);
2434
- }
2435
2655
  const stack = findTestFileStackTrace(task.file.filepath, new Error("STACK_TRACE").stack);
2436
2656
  if (stack) {
2437
2657
  artifact.location = {