diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index ee388f272202..8c735b7a2ee5 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -730,6 +730,10 @@ export default ({ mode }: { mode: string }) => {
text: 'Test Environment',
link: '/guide/environment',
},
+ {
+ text: 'Test Run Lifecycle',
+ link: '/guide/lifecycle',
+ },
{
text: 'Snapshot',
link: '/guide/snapshot',
@@ -858,7 +862,7 @@ export default ({ mode }: { mode: string }) => {
},
{
text: 'Advanced',
- collapsed: true,
+ collapsed: false,
items: [
{
text: 'Getting Started',
@@ -947,7 +951,7 @@ export default ({ mode }: { mode: string }) => {
},
{
text: 'Advanced',
- collapsed: true,
+ collapsed: false,
items: [
{
text: 'Vitest',
diff --git a/docs/config/globalsetup.md b/docs/config/globalsetup.md
index 743704569571..549d2aadea26 100644
--- a/docs/config/globalsetup.md
+++ b/docs/config/globalsetup.md
@@ -7,26 +7,45 @@ outline: deep
- **Type:** `string | string[]`
-Path to global setup files, relative to project root.
+Path to global setup files relative to project [root](/config/root).
-A global setup file can either export named functions `setup` and `teardown` or a `default` function that returns a teardown function ([example](https://github.com/vitest-dev/vitest/blob/main/test/global-setup/vitest.config.ts)).
+A global setup file can either export named functions `setup` and `teardown` or a `default` function that returns a teardown function:
-::: info
-Multiple globalSetup files are possible. setup and teardown are executed sequentially with teardown in reverse order.
+::: code-group
+```js [exports]
+export function setup(project) {
+ console.log('setup')
+}
+
+export function teardown() {
+ console.log('teardown')
+}
+```
+```js [default]
+export default function setup(project) {
+ console.log('setup')
+
+ return function teardown() {
+ console.log('teardown')
+ }
+}
+```
:::
-::: warning
-Global setup runs only if there is at least one running test. This means that global setup might start running during watch mode after test file is changed (the test file will wait for global setup to finish before running).
+Note that the `setup` method and a `default` function receive a [test project](/api/advanced/test-project) as the first argument. The global setup is called before the test workers are created and only if there is at least one test queued, and teardown is called after all test files have finished running. In [watch mode](/config/watch), the teardown is called before the process is exited instead. If you need to reconfigure your setup before the test rerun, you can use [`onTestsRerun`](#handling-test-reruns) hook instead.
-Beware that the global setup is running in a different global scope, so your tests don't have access to variables defined here. However, you can pass down serializable data to tests via [`provide`](#provide) method:
+Multiple global setup files are possible. `setup` and `teardown` are executed sequentially with teardown in reverse order.
+
+::: danger
+Beware that the global setup is running in a different global scope before test workers are even created, so your tests don't have access to global variables defined here. However, you can pass down serializable data to tests via [`provide`](/config/provide) method and read them in your tests via `inject` imported from `vitest`:
:::code-group
-```ts [example.test.js]
+```ts [example.test.ts]
import { inject } from 'vitest'
inject('wsPort') === 3000
```
-```ts [globalSetup.ts 3.0.0]
+```ts [globalSetup.ts]
import type { TestProject } from 'vitest/node'
export default function setup(project: TestProject) {
@@ -39,22 +58,13 @@ declare module 'vitest' {
}
}
```
-```ts [globalSetup.ts 2.0.0]
-import type { GlobalSetupContext } from 'vitest/node'
-export default function setup({ provide }: GlobalSetupContext) {
- provide('wsPort', 3000)
-}
-
-declare module 'vitest' {
- export interface ProvidedContext {
- wsPort: number
- }
-}
-```
+If you need to execute code in the same process as tests, use [`setupFiles`](/config/setupfiles) instead, but note that it runs before every test file.
:::
-Since Vitest 3, you can define a custom callback function to be called when Vitest reruns tests. If the function is asynchronous, the runner will wait for it to complete before executing tests. Note that you cannot destruct the `project` like `{ onTestsRerun }` because it relies on the context.
+### Handling Test Reruns
+
+You can define a custom callback function to be called when Vitest reruns tests. The test runner will wait for it to complete before executing tests. Note that you cannot destruct the `project` like `{ onTestsRerun }` because it relies on the context.
```ts [globalSetup.ts]
import type { TestProject } from 'vitest/node'
diff --git a/docs/config/sequence.md b/docs/config/sequence.md
index 491a24f6e9c0..c37fecb43c4d 100644
--- a/docs/config/sequence.md
+++ b/docs/config/sequence.md
@@ -26,7 +26,7 @@ Sharding is happening before sorting, and only if `--shard` option is provided.
If [`sequencer.groupOrder`](#grouporder) is specified, the sequencer will be called once for each group and pool.
-## groupOrder
+## sequence.groupOrder
- **Type:** `number`
- **Default:** `0`
@@ -97,7 +97,7 @@ Tests in these projects will run in this order:
If you want files and tests to run randomly, you can enable it with this option, or CLI argument [`--sequence.shuffle`](/guide/cli).
-Vitest usually uses cache to sort tests, so long running tests start earlier - this makes tests run faster. If your files and tests will run in random order you will lose this performance improvement, but it may be useful to track tests that accidentally depend on another run previously.
+Vitest usually uses cache to sort tests, so long-running tests start earlier, which makes tests run faster. If your files and tests run in random order, you will lose this performance improvement, but it may be useful to track tests that accidentally depend on another test run previously.
### sequence.shuffle.files {#sequence-shuffle-files}
diff --git a/docs/config/setupfiles.md b/docs/config/setupfiles.md
index f07fc0ea56c8..3f86fb432f68 100644
--- a/docs/config/setupfiles.md
+++ b/docs/config/setupfiles.md
@@ -7,33 +7,39 @@ outline: deep
- **Type:** `string | string[]`
-Path to setup files. They will be run before each test file.
+Paths to setup files resolved relative to the [`root`](/config/root). They will run before each _test file_ in the same process. By default, all test files run in parallel, but you can configure it with [`sequence.setupFiles`](/config/sequence#sequence-setupfiles) option.
+
+Vitest will ignore any exports from these files.
+
+:::warning
+Note that setup files are executed in the same process as tests, unlike [`globalSetup`](/config/globalsetup) that runs once in the main thread before any test worker is created.
+:::
:::info
Editing a setup file will automatically trigger a rerun of all tests.
:::
-You can use `process.env.VITEST_POOL_ID` (integer-like string) inside to distinguish between workers.
+If you have a heavy process running in the background, you can use `process.env.VITEST_POOL_ID` (integer-like string) inside to distinguish between workers and spread the workload.
-:::tip
-Note, that if you are running [`--isolate=false`](#isolate), this setup file will be run in the same global scope multiple times. Meaning, that you are accessing the same global object before each test, so make sure you are not doing the same thing more than you need.
-:::
+:::warning
+If [isolation](/config/isolate) is disabled, imported modules are cached, but the setup file itself is executed again before each test file, meaning that you are accessing the same global object before each test file. Make sure you are not doing the same thing more than necessary.
For example, you may rely on a global variable:
```ts
import { config } from '@some-testing-lib'
-if (!globalThis.defined) {
+if (!globalThis.setupInitialized) {
config.plugins = [myCoolPlugin]
computeHeavyThing()
- globalThis.defined = true
+ globalThis.setupInitialized = true
}
-// hooks are reset before each suite
+// hooks reset before each test file
afterEach(() => {
cleanup()
})
globalThis.resetBeforeEachTest = true
```
+:::
diff --git a/docs/guide/lifecycle.md b/docs/guide/lifecycle.md
new file mode 100644
index 000000000000..90fc65630dbd
--- /dev/null
+++ b/docs/guide/lifecycle.md
@@ -0,0 +1,320 @@
+---
+title: Test Run Lifecycle | Guide
+outline: deep
+---
+
+# Test Run Lifecycle
+
+Understanding the test run lifecycle is essential for writing effective tests, debugging issues, and optimizing your test suite. This guide explains when and in what order different lifecycle phases occur in Vitest, from initialization to teardown.
+
+## Overview
+
+A typical Vitest test run goes through these main phases:
+
+1. **Initialization** - Configuration loading and project setup
+2. **Global Setup** - One-time setup before any tests run
+3. **Worker Creation** - Test workers are spawned based on the [pool](/config/pool) configuration
+4. **Test File Collection** - Test files are discovered and organized
+5. **Test Execution** - Tests run with their hooks and assertions
+6. **Reporting** - Results are collected and reported
+7. **Global Teardown** - Final cleanup after all tests complete
+
+Phases 4–6 run once for each test file, so across your test suite they will execute multiple times and may also run in parallel across different files when you use more than [1 worker](/config/maxworkers).
+
+## Detailed Lifecycle Phases
+
+### 1. Initialization Phase
+
+When you run `vitest`, the framework first loads your configuration and prepares the test environment.
+
+**What happens:**
+- [Command-line](/guide/cli) arguments are parsed
+- [Configuration file](/config/) is loaded
+- Project structure is validated
+
+This phase can run again if the config file or one of its imports changes.
+
+**Scope:** Main process (before any test workers are created)
+
+### 2. Global Setup Phase
+
+If you have configured [`globalSetup`](/config/globalsetup) files, they run once before any test workers are created.
+
+**What happens:**
+- `setup()` functions (or exported `default` function) from global setup files execute sequentially
+- Multiple global setup files run in the order they are defined
+
+**Scope:** Main process (separate from test workers)
+
+**Important notes:**
+- Global setup runs in a **different global scope** from your tests
+- Tests cannot access variables defined in global setup (use [`provide`/`inject`](/config/provide) instead)
+- Global setup only runs if there is at least one test queued
+
+```ts [globalSetup.ts]
+export function setup(project) {
+ // Runs once before all tests
+ console.log('Global setup')
+
+ // Share data with tests
+ project.provide('apiUrl', 'http://localhost:3000')
+}
+
+export function teardown() {
+ // Runs once after all tests
+ console.log('Global teardown')
+}
+```
+
+### 3. Worker Creation Phase
+
+After global setup completes, Vitest creates test workers based on your [pool configuration](/config/pool).
+
+**What happens:**
+- Workers are spawned according to the `browser.enabled` or `pool` setting (`threads`, `forks`, `vmThreads`, or `vmForks`)
+- Each worker gets its own isolated environment (unless [isolation](/config/isolate) is disabled)
+- By default, workers are not reused to provide isolation. Workers are reused only if:
+ - [isolation](/config/isolate) is disabled
+ - OR pool is `vmThreads` or `vmForks` because [VM](https://nodejs.org/api/vm.html) provides enough isolation
+
+**Scope:** Worker processes/threads
+
+### 4. Test File Setup Phase
+
+Before each test file runs, [setup files](/config/setupfiles) are executed.
+
+**What happens:**
+- Setup files run in the same process as your tests
+- By default, setup files run in **parallel** (configurable via [`sequence.setupFiles`](/config/sequence#sequence-setupfiles))
+- Setup files execute before **each test file**
+- Any global _state_ or configuration can be initialized here
+
+**Scope:** Worker process (same as your tests)
+
+**Important notes:**
+- If [isolation](/config/isolate) is disabled, setup files still rerun before each test file to trigger side effects, but imported modules are cached
+- Editing a setup file triggers a rerun of all tests in watch mode
+
+```ts [setupFile.ts]
+import { afterEach } from 'vitest'
+
+// Runs before each test file
+console.log('Setup file executing')
+
+// Register hooks that apply to all tests
+afterEach(() => {
+ cleanup()
+})
+```
+
+### 5. Test Collection and Execution Phase
+
+This is the main phase where your tests actually run.
+
+#### Test File Execution Order
+
+Test files are executed based on your configuration:
+
+- **Sequential by default** within a worker
+- Files will run in **parallel** across different workers, configured by [`maxWorkers`](/config/maxworkers)
+- Order can be randomized with [`sequence.shuffle`](/config/sequence#sequence-shuffle) or fine-tuned with [`sequence.sequencer`](/config/sequence#sequence-sequencer)
+- Long-running tests typically start earlier (based on cache) unless shuffle is enabled
+
+#### Within Each Test File
+
+The execution follows this order:
+
+1. **File-level code** - All code outside `describe` blocks runs immediately
+2. **Test collection** - `describe` blocks are processed, and tests are registered as side effects of importing the test file
+3. **`beforeAll` hooks** - Run once before any tests in the suite
+4. **For each test:**
+ - `beforeEach` hooks execute (in order defined, or based on [`sequence.hooks`](/config/sequence#sequence-hooks))
+ - Test function executes
+ - `afterEach` hooks execute (reverse order by default with `sequence.hooks: 'stack'`)
+ - [`onTestFinished`](/api/#ontestfinished) callbacks run (always in reverse order)
+ - If test failed: [`onTestFailed`](/api/#ontestfailed) callbacks run
+ - Note: if `repeats` or `retry` are set, all of these steps are executed again
+5. **`afterAll` hooks** - Run once after all tests in the suite complete
+
+**Example execution flow:**
+
+```ts
+// This runs immediately (collection phase)
+console.log('File loaded')
+
+describe('User API', () => {
+ // This runs immediately (collection phase)
+ console.log('Suite defined')
+
+ beforeAll(() => {
+ // Runs once before all tests in this suite
+ console.log('beforeAll')
+ })
+
+ beforeEach(() => {
+ // Runs before each test
+ console.log('beforeEach')
+ })
+
+ test('creates user', () => {
+ // Test executes
+ console.log('test 1')
+ })
+
+ test('updates user', () => {
+ // Test executes
+ console.log('test 2')
+ })
+
+ afterEach(() => {
+ // Runs after each test
+ console.log('afterEach')
+ })
+
+ afterAll(() => {
+ // Runs once after all tests in this suite
+ console.log('afterAll')
+ })
+})
+
+// Output:
+// File loaded
+// Suite defined
+// beforeAll
+// beforeEach
+// test 1
+// afterEach
+// beforeEach
+// test 2
+// afterEach
+// afterAll
+```
+
+#### Nested Suites
+
+When using nested `describe` blocks, hooks follow a hierarchical pattern:
+
+```ts
+describe('outer', () => {
+ beforeAll(() => console.log('outer beforeAll'))
+ beforeEach(() => console.log('outer beforeEach'))
+
+ test('outer test', () => console.log('outer test'))
+
+ describe('inner', () => {
+ beforeAll(() => console.log('inner beforeAll'))
+ beforeEach(() => console.log('inner beforeEach'))
+
+ test('inner test', () => console.log('inner test'))
+
+ afterEach(() => console.log('inner afterEach'))
+ afterAll(() => console.log('inner afterAll'))
+ })
+
+ afterEach(() => console.log('outer afterEach'))
+ afterAll(() => console.log('outer afterAll'))
+})
+
+// Output:
+// outer beforeAll
+// outer beforeEach
+// outer test
+// outer afterEach
+// inner beforeAll
+// outer beforeEach
+// inner beforeEach
+// inner test
+// inner afterEach (with stack mode)
+// outer afterEach (with stack mode)
+// inner afterAll
+// outer afterAll
+```
+
+#### Concurrent Tests
+
+When using `test.concurrent` or [`sequence.concurrent`](/config/sequence#sequence-concurrent):
+
+- Tests within the same file can run in parallel
+- Each concurrent test still runs its own `beforeEach` and `afterEach` hooks
+- Use [test context](/guide/test-context) for concurrent snapshots: `test.concurrent('name', async ({ expect }) => {})`
+
+### 6. Reporting Phase
+
+Throughout the test run, reporters receive lifecycle events and display results.
+
+**What happens:**
+- Reporters receive events as tests progress
+- Results are collected and formatted
+- Test summaries are generated
+- Coverage reports are generated (if enabled)
+
+For detailed information about the reporter lifecycle, see the [Reporters](/api/advanced/reporters) guide.
+
+### 7. Global Teardown Phase
+
+After all tests complete, global teardown functions execute.
+
+**What happens:**
+- `teardown()` functions from [`globalSetup`](/config/globalsetup) files run
+- Multiple teardown functions run in **reverse order** of their setup
+- In watch mode, teardown runs before process exit, not between test reruns
+
+**Scope:** Main process
+
+```ts [globalSetup.ts]
+export function teardown() {
+ // Clean up global resources
+ console.log('Global teardown complete')
+}
+```
+
+## Lifecycle in Different Scopes
+
+Understanding where code executes is crucial for avoiding common pitfalls:
+
+| Phase | Scope | Access to Test Context | Runs |
+|-------|-------|----------------------|------|
+| Config File | Main process | ❌ No | Once per Vitest run |
+| Global Setup | Main process | ❌ No (use `provide`/`inject`) | Once per Vitest run |
+| Setup Files | Worker (same as tests) | ✅ Yes | Before each test file |
+| File-level code | Worker | ✅ Yes | Once per test file |
+| `beforeAll` / `afterAll` | Worker | ✅ Yes | Once per suite |
+| `beforeEach` / `afterEach` | Worker | ✅ Yes | Per test |
+| Test function | Worker | ✅ Yes | Once (or more with retries/repeats) |
+| Global Teardown | Main process | ❌ No | Once per Vitest run |
+
+## Watch Mode Lifecycle
+
+In watch mode, the lifecycle repeats with some differences:
+
+1. **Initial run** - Full lifecycle as described above
+2. **On file change:**
+ - New [test run](/api/advanced/reporters#ontestrunstart) starts
+ - Only affected test files are re-run
+ - [Setup files](/config/setupfiles) run again for those test files
+ - [Global setup](/config/globalsetup) does **not** re-run (use [`project.onTestsRerun`](/config/globalsetup#handling-test-reruns) for rerun-specific logic)
+3. **On exit:**
+ - Global teardown executes
+ - Process terminates
+
+## Performance Considerations
+
+Understanding the lifecycle helps optimize test performance:
+
+- **Global setup** is ideal for expensive one-time operations (database seeding, server startup)
+- **Setup files** run before each test file - avoid heavy operations here if you have many test files
+- **`beforeAll`** is better than `beforeEach` for expensive setup that doesn't need isolation
+- **Disabling [isolation](/config/isolate)** improves performance, but setup files still execute before each file
+- **[Pool configuration](/config/pool)** affects parallelization and available APIs
+
+For tips on how to improve performance, read the [Improving Performance](/guide/improving-performance) guide.
+
+## Related Documentation
+
+- [Global Setup Configuration](/config/globalsetup)
+- [Setup Files Configuration](/config/setupfiles)
+- [Test Sequencing Options](/config/sequence)
+- [Isolation Configuration](/config/isolate)
+- [Pool Configuration](/config/pool)
+- [Extending Reporters](/guide/advanced/reporters) - for reporter lifecycle events
+- [Test API Reference](/api/) - for hook APIs and test functions