From a96ea140e0024aa36988a1d66fae7d870c7f8358 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 3 Oct 2025 09:38:13 +0200 Subject: [PATCH 1/3] fix(browser): exclude deprecated context import from optimization (#8658) --- packages/browser/src/node/plugin.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/browser/src/node/plugin.ts b/packages/browser/src/node/plugin.ts index faacb214d..abb90a18b 100644 --- a/packages/browser/src/node/plugin.ts +++ b/packages/browser/src/node/plugin.ts @@ -234,6 +234,9 @@ export default (parentServer: ParentBrowserProject, base = '/'): Plugin[] => { 'vitest/browser', 'vitest/internal/browser', 'vitest/runners', + 'vite/module-runner', + '@vitest/browser/utils', + '@vitest/browser/context', '@vitest/browser/client', '@vitest/utils', '@vitest/utils/source-map', -- 2.51.2 From 6b21cfe5571df215e344803c025175f0264d994d Mon Sep 17 00:00:00 2001 From: Yusuf Aran Date: Fri, 3 Oct 2025 10:36:03 +0200 Subject: [PATCH 2/3] feat: add hooks with type-safe extra context to TestAPI (#8623) --- docs/guide/test-context.md | 22 ++++++++++++++++++++++ packages/runner/src/suite.ts | 6 ++++++ packages/runner/src/types/tasks.ts | 10 +++++++++- test/core/test/test-extend.test.ts | 30 ++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/docs/guide/test-context.md b/docs/guide/test-context.md index 3041610f6..a77521b08 100644 --- a/docs/guide/test-context.md +++ b/docs/guide/test-context.md @@ -455,4 +455,26 @@ test('types are correct', ({ // ... }) ``` + ::: + +When using `test.extend`, the extended `test` object provides type-safe `beforeEach` and `afterEach` hooks that are aware of the new context: + +```ts +const test = baseTest.extend<{ + todos: number[] +}>({ + todos: async ({}, use) => { + await use([]) + }, +}) + +// Unlike global hooks, these hooks are aware of the extended context +test.beforeEach(({ todos }) => { + todos.push(1) +}) + +test.afterEach(({ todos }) => { + console.log(todos) +}) +``` diff --git a/packages/runner/src/suite.ts b/packages/runner/src/suite.ts index 99558c777..69f153f3f 100644 --- a/packages/runner/src/suite.ts +++ b/packages/runner/src/suite.ts @@ -33,6 +33,7 @@ import { withTimeout, } from './context' import { mergeContextFixtures, mergeScopedFixtures, withFixtures } from './fixture' +import { afterAll, afterEach, beforeAll, beforeEach } from './hooks' import { getHooks, setFn, setHooks, setTestFixture } from './map' import { getCurrentTest } from './test-state' import { findTestFileStackTrace } from './utils' @@ -794,6 +795,11 @@ export function createTaskCollector( }, _context) } + taskFn.beforeEach = beforeEach + taskFn.afterEach = afterEach + taskFn.beforeAll = beforeAll + taskFn.afterAll = afterAll + const _test = createChainable( ['concurrent', 'sequential', 'skip', 'only', 'todo', 'fails'], taskFn, diff --git a/packages/runner/src/types/tasks.ts b/packages/runner/src/types/tasks.ts index ae3e77e57..8847538c6 100644 --- a/packages/runner/src/types/tasks.ts +++ b/packages/runner/src/types/tasks.ts @@ -1,5 +1,6 @@ import type { Awaitable, TestError } from '@vitest/utils' import type { FixtureItem } from '../fixture' +import type { afterAll, afterEach, beforeAll, beforeEach } from '../hooks' import type { ChainableFunction } from '../utils/chain' export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued' @@ -482,8 +483,15 @@ interface ExtendedAPI { runIf: (condition: any) => ChainableTestAPI } +interface Hooks { + beforeAll: typeof beforeAll + afterAll: typeof afterAll + beforeEach: typeof beforeEach + afterEach: typeof afterEach +} + export type TestAPI = ChainableTestAPI - & ExtendedAPI & { + & ExtendedAPI & Hooks & { extend: = object>( fixtures: Fixtures ) => TestAPI<{ diff --git a/test/core/test/test-extend.test.ts b/test/core/test/test-extend.test.ts index 7e69f399e..8ad581e7a 100644 --- a/test/core/test/test-extend.test.ts +++ b/test/core/test/test-extend.test.ts @@ -497,3 +497,33 @@ describe('suite with timeout', () => { expect(task.timeout).toBe(1_000) }) }, 100) + +describe('type-safe fixture hooks', () => { + const counterTest = test.extend<{ + counter: { value: number } + fileCounter: { value: number } + }>({ + counter: async ({}, use) => { await use({ value: 0 }) }, + fileCounter: [async ({}, use) => { await use({ value: 0 }) }, { scope: 'file' }], + }) + + counterTest.beforeEach(({ counter }) => { + // shouldn't have typescript error because of 'counter' here + counter.value += 1 + }) + + counterTest.afterEach(({ fileCounter }) => { + // shouldn't have typescript error because of 'fileCounter' here + fileCounter.value += 2 + }) + + // beforeAll and afterAll hooks are not tested here, because they don't provide an extra context + + counterTest('beforeEach fixture hook can adapt type-safe context', ({ counter }) => { + expect(counter.value).toBe(1) + }) + + counterTest('afterEach fixture hook can adapt type-safe context', ({ fileCounter }) => { + expect(fileCounter.value).toBe(2) + }) +}) -- 2.51.2 From c333aedccc0c391057420a8592f095298d42f2dd Mon Sep 17 00:00:00 2001 From: abeer0 <47961062+iiio2@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:53:08 +0600 Subject: [PATCH 3/3] chore: compare `attachment.body` for `null` & `undefined` (#8640) Co-authored-by: Vladimir --- packages/runner/src/context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runner/src/context.ts b/packages/runner/src/context.ts index 8f029f4d9..2ed5d2a4f 100644 --- a/packages/runner/src/context.ts +++ b/packages/runner/src/context.ts @@ -168,8 +168,8 @@ export function createTestContext( type: type || 'notice', } if (attachment) { - if (!attachment.body && !attachment.path) { - throw new TypeError(`Test attachment requires body or path to be set. Both are missing.`) + if (attachment.body == null && !attachment.path) { + throw new TypeError(`Test attachment requires "body" or "path" to be set. Both are missing.`) } if (attachment.body && attachment.path) { throw new TypeError(`Test attachment requires only one of "body" or "path" to be set. Both are specified.`) -- 2.51.2