···373373})
374374```
375375376376+#### Per-Scope Context <Version>3.2.0</Version>
377377+378378+You can define context that will be initiated once per file or a worker. It is initiated the same way as a regular fixture with an objects parameter:
379379+380380+```ts
381381+import { test as baseTest } from 'vitest'
382382+383383+export const test = baseTest.extend({
384384+ perFile: [
385385+ ({}, { use }) => use([]),
386386+ { scope: 'file' },
387387+ ],
388388+ perWorker: [
389389+ ({}, { use }) => use([]),
390390+ { scope: 'worker' },
391391+ ],
392392+})
393393+```
394394+395395+The value is initialised the first time any test has accessed it, unless the fixture options have `auto: true` - in this case the value is initialised before any test has run.
396396+397397+```ts
398398+const test = baseTest.extend({
399399+ perFile: [
400400+ ({}, { use }) => use([]),
401401+ {
402402+ scope: 'file',
403403+ // always run this hook before any test
404404+ auto: true
405405+ },
406406+ ],
407407+})
408408+```
409409+410410+The `worker` scope will run the fixture once per worker. The number of running workers depends on various factors. By default, every file runs in a separate worker, so `file` and `worker` scopes work the same way.
411411+412412+However, if you disable [isolation](/config/#isolate), then the number of workers is limited by the [`maxWorkers`](/config/#maxworkers) or [`poolOptions`](/config/#pooloptions) configuration.
413413+414414+Note that specifying `scope: 'worker'` when running tests in `vmThreads` or `vmForks` will work the same way as `scope: 'file'`. This limitation exists because every test file has its own VM context, so if Vitest were to initiate it once, one context could leak to another and create many reference inconsistencies (instances of the same class would reference different constructors, for example).
415415+376416#### TypeScript
377417378418To provide fixture types for all your custom contexts, you can pass the fixtures type as a generic.
···55 SequenceHooks,
66 SequenceSetupFiles,
77 Suite,
88- Task,
98 TaskEventPack,
109 TaskResultPack,
1110 Test,
···8483 /**
8584 * Called before running a single test. Doesn't have "result" yet.
8685 */
8787- onBeforeRunTask?: (test: Task) => unknown
8686+ onBeforeRunTask?: (test: Test) => unknown
8887 /**
8988 * Called before actually running the test function. Already has "result" with "state" and "startTime".
9089 */
9190 onBeforeTryTask?: (
9292- test: Task,
9191+ test: Test,
9392 options: { retry: number; repeats: number }
9493 ) => unknown
9594 /**
···9998 /**
10099 * Called after result and state are set.
101100 */
102102- onAfterRunTask?: (test: Task) => unknown
101101+ onAfterRunTask?: (test: Test) => unknown
103102 /**
104103 * Called right after running the test function. Doesn't have new state yet. Will not be called, if the test function throws.
105104 */
106105 onAfterTryTask?: (
107107- test: Task,
106106+ test: Test,
108107 options: { retry: number; repeats: number }
109108 ) => unknown
110109···126125 * If defined, will be called instead of usual Vitest handling. Useful, if you have your custom test function.
127126 * "before" and "after" hooks will not be ignored.
128127 */
129129- runTask?: (test: Task) => Promise<void>
128128+ runTask?: (test: Test) => Promise<void>
130129131130 /**
132131 * Called, when a task is updated. The same as "onTaskUpdate" in a reporter, but this is running in the same thread as tests.
···173172 * The name of the current pool. Can affect how stack trace is inferred on the server side.
174173 */
175174 pool?: string
175175+176176+ /**
177177+ * Return the worker context for fixtures specified with `scope: 'worker'`
178178+ */
179179+ getWorkerContext?: () => Record<string, unknown>
180180+ onCleanupWorkerContext?: (cleanup: () => unknown) => void
176181177182 /** @private */
178183 _currentTaskStartTime?: number
+11
packages/runner/src/types/tasks.ts
···523523export interface FixtureOptions {
524524 /**
525525 * Whether to automatically set up current fixture, even though it's not being used in tests.
526526+ * @default false
526527 */
527528 auto?: boolean
528529 /**
529530 * Indicated if the injected value from the config should be preferred over the fixture value
530531 */
531532 injected?: boolean
533533+ /**
534534+ * When should the fixture be set up.
535535+ * - **test**: fixture will be set up before every test
536536+ * - **worker**: fixture will be set up once per worker
537537+ * - **file**: fixture will be set up once per file
538538+ *
539539+ * **Warning:** The `vmThreads` and `vmForks` pools initiate worker fixtures once per test file.
540540+ * @default 'test'
541541+ */
542542+ scope?: 'test' | 'worker' | 'file'
532543}
533544534545export type Use<T> = (value: T) => Promise<void>
+2
packages/runner/src/utils/collect.ts
···11import type { File, Suite, TaskBase } from '../types/tasks'
22import { processError } from '@vitest/utils/error'
33import { relative } from 'pathe'
44+import { setFileContext } from '../context'
4556/**
67 * If any tasks been marked as `only`, mark all other tasks as `skip`.
···194195 pool,
195196 }
196197 file.file = file
198198+ setFileContext(file, Object.create(null))
197199 return file
198200}
199201
···11-import type { CancelReason, File, Suite, Task, TaskEventPack, TaskResultPack, TestAnnotation, VitestRunner } from '@vitest/runner'
22-import type { RunnerTestCase, SerializedConfig, TestExecutionMethod, WorkerGlobalState } from 'vitest'
11+import type { CancelReason, File, Suite, Task, TaskEventPack, TaskResultPack, Test, TestAnnotation, VitestRunner } from '@vitest/runner'
22+import type { SerializedConfig, TestExecutionMethod, WorkerGlobalState } from 'vitest'
33import type { VitestExecutor } from 'vitest/execute'
44import type { VitestBrowserClientMocker } from './mocker'
55import { globalChannel, onCancel } from '@vitest/browser/client'
···5959 await super.onBeforeTryTask?.(...args)
6060 }
61616262- onAfterRunTask = async (task: Task) => {
6262+ onAfterRunTask = async (task: Test) => {
6363 await super.onAfterRunTask?.(task)
64646565 if (this.config.bail && task.result?.state === 'fail') {
···146146 return rpc().onCollected(this.method, files)
147147 }
148148149149- onTestAnnotate = (test: RunnerTestCase, annotation: TestAnnotation): Promise<TestAnnotation> => {
149149+ onTestAnnotate = (test: Test, annotation: TestAnnotation): Promise<TestAnnotation> => {
150150 if (annotation.location) {
151151 // the file should be the test file
152152 // tests from other files are not supported
+1
packages/browser/src/client/tester/state.ts
···3030 throw new Error('Not called in the browser')
3131 },
3232 },
3333+ onCleanup: fn => getBrowserState().cleanups.push(fn),
3334 moduleCache: getBrowserState().moduleCache,
3435 rpc: null as any,
3536 durations: {
+4
packages/browser/src/client/tester/tester.ts
···234234 await userEvent.cleanup()
235235 .catch(error => unhandledError(error, 'Cleanup Error'))
236236237237+ await Promise.all(
238238+ getBrowserState().cleanups.map(fn => fn()),
239239+ ).catch(error => unhandledError(error, 'Cleanup Error'))
240240+237241 // if isolation is disabled, Vitest reuses the same iframe and we
238242 // don't need to switch the context back at all
239243 if (contextSwitched) {