diff --git a/docs/api/index.md b/docs/api/index.md
index 46c204496e06..36817aea75b1 100644
--- a/docs/api/index.md
+++ b/docs/api/index.md
@@ -1010,6 +1010,10 @@ test('performs an organization query', async () => {
})
```
+::: tip
+This hook is always called in reverse order and is not affected by [`sequence.hooks`](/config/#sequence-hooks) option.
+:::
+
### onTestFailed
This hook is called only after the test has failed. It is called after `afterEach` hooks since they can influence the test result. It receives a `TaskResult` object with the current test result. This hook is useful for debugging.
diff --git a/docs/config/index.md b/docs/config/index.md
index 156db2bb69e2..a207e60958e9 100644
--- a/docs/config/index.md
+++ b/docs/config/index.md
@@ -1865,6 +1865,10 @@ Changes the order in which hooks are executed.
- `list` will order all hooks in the order they are defined
- `parallel` will run hooks in a single group in parallel (hooks in parent suites will still run before the current suite's hooks)
+::: tip
+This option doesn't affect [`onTestFinished`](/api/#ontestfinished). It is always called in reverse order.
+:::
+
#### sequence.setupFiles 0.29.3+ {#sequence-setupfiles}
- **Type**: `'list' | 'parallel'`
diff --git a/packages/runner/src/run.ts b/packages/runner/src/run.ts
index b0321fe6c06b..c0d55c414b21 100644
--- a/packages/runner/src/run.ts
+++ b/packages/runner/src/run.ts
@@ -1,4 +1,5 @@
import limit from 'p-limit'
+import type { Awaitable } from '@vitest/utils'
import { getSafeTimers, shuffle } from '@vitest/utils'
import { processError } from '@vitest/utils/error'
import type { DiffOptions } from '@vitest/utils/diff'
@@ -33,6 +34,19 @@ function getSuiteHooks(suite: Suite, name: keyof SuiteHooks, sequence: SequenceH
return hooks
}
+async function callTaskHooks(task: Task, hooks: ((result: TaskResult) => Awaitable)[], sequence: SequenceHooks) {
+ if (sequence === 'stack')
+ hooks = hooks.slice().reverse()
+
+ if (sequence === 'parallel') {
+ await Promise.all(hooks.map(fn => fn(task.result!)))
+ }
+ else {
+ for (const fn of hooks)
+ await fn(task.result!)
+ }
+}
+
export async function callSuiteHook(
suite: Suite,
currentTask: Task,
@@ -211,7 +225,7 @@ export async function runTest(test: Test | Custom, runner: VitestRunner) {
}
try {
- await Promise.all(test.onFinished?.map(fn => fn(test.result!)) || [])
+ await callTaskHooks(test, test.onFinished || [], 'stack')
}
catch (e) {
failTask(test.result, e, runner.config.diffOptions)
@@ -219,7 +233,7 @@ export async function runTest(test: Test | Custom, runner: VitestRunner) {
if (test.result.state === 'fail') {
try {
- await Promise.all(test.onFailed?.map(fn => fn(test.result!)) || [])
+ await callTaskHooks(test, test.onFailed || [], runner.config.sequence.hooks)
}
catch (e) {
failTask(test.result, e, runner.config.diffOptions)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 04cc0f735f7d..cd723124d451 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10923,7 +10923,7 @@ packages:
/@types/eslint-scope@3.7.6:
resolution: {integrity: sha512-zfM4ipmxVKWdxtDaJ3MP3pBurDXOCoyjvlpE3u6Qzrmw4BPbfm4/ambIeTk/r/J0iq/+2/xp0Fmt+gFvXJY2PQ==}
dependencies:
- '@types/eslint': 8.44.6
+ '@types/eslint': 8.56.10
'@types/estree': 1.0.5
dev: true
@@ -10934,13 +10934,6 @@ packages:
'@types/json-schema': 7.0.14
dev: true
- /@types/eslint@8.44.6:
- resolution: {integrity: sha512-P6bY56TVmX8y9J87jHNgQh43h6VVU+6H7oN7hgvivV81K2XY8qJZ5vqPy/HdUoVIelii2kChYVzQanlswPWVFw==}
- dependencies:
- '@types/estree': 1.0.5
- '@types/json-schema': 7.0.14
- dev: true
-
/@types/eslint@8.56.10:
resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==}
dependencies:
@@ -11727,7 +11720,7 @@ packages:
resolution: {integrity: sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
- eslint: ^8.56.0 || ^9.0.0
+ eslint: ^8.56.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.1.0)
'@types/json-schema': 7.0.15
@@ -26239,7 +26232,7 @@ packages:
resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==}
engines: {node: '>= 10.13.0'}
dependencies:
- '@types/json-schema': 7.0.14
+ '@types/json-schema': 7.0.15
ajv: 6.12.6
ajv-keywords: 3.5.2(ajv@6.12.6)
dev: true
diff --git a/test/core/test/on-finished.test.ts b/test/core/test/on-finished.test.ts
index 5f4f050d7534..83d049cf971f 100644
--- a/test/core/test/on-finished.test.ts
+++ b/test/core/test/on-finished.test.ts
@@ -1,6 +1,7 @@
import { expect, it, onTestFinished } from 'vitest'
const collected: any[] = []
+const multiple: any[] = []
it('on-finished regular', () => {
collected.push(1)
@@ -38,6 +39,23 @@ it.fails('failed finish context', (t) => {
collected.push(null)
})
+it('multiple on-finished', () => {
+ onTestFinished(() => {
+ multiple.push(1)
+ })
+ onTestFinished(() => {
+ multiple.push(2)
+ })
+ onTestFinished(async () => {
+ await new Promise(r => setTimeout(r, 100))
+ multiple.push(3)
+ })
+ onTestFinished(() => {
+ multiple.push(4)
+ })
+})
+
it('after', () => {
expect(collected).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
+ expect(multiple).toEqual([4, 3, 2, 1])
})