[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: global `sequence.concurrent: true` with top-level `test(..., { concurrent: false })` + depreacte `sequential` test API and options (#10196)

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>

authored by

Hiroshi Ogawa
Codex
Vladimir
and committed by
GitHub
(Apr 26, 2026, 9:13 AM +0200) 2847dfa2 e3998468

+151 -34
+5 -1
docs/api/describe.md
··· 230 230 }) 231 231 ``` 232 232 233 - ## describe.sequential 233 + ## describe.sequential <Deprecated /> {#describe-sequential} 234 234 235 235 - **Alias:** `suite.sequential` 236 + 237 + ::: warning DEPRECATED 238 + Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency. 239 + ::: 236 240 237 241 `describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. 238 242
+9 -1
docs/api/test.md
··· 223 223 - **Default:** `true` 224 224 - **Alias:** [`test.sequential`](#test-sequential) 225 225 226 + ::: warning DEPRECATED 227 + Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency. 228 + ::: 229 + 226 230 Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence. 227 231 228 232 ### skip ··· 453 457 454 458 Note that if tests are synchronous, Vitest will still run them sequentially. 455 459 456 - ## test.sequential 460 + ## test.sequential <Deprecated /> {#test-sequential} 457 461 458 462 - **Alias:** `it.sequential` 463 + 464 + ::: warning DEPRECATED 465 + Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency. 466 + ::: 459 467 460 468 `test.sequential` marks a test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. 461 469
+1 -1
packages/runner/src/suite.ts
··· 391 391 } 392 392 if ( 393 393 options.concurrent 394 - || (!options.sequential && runner.config.sequence.concurrent) 394 + ?? (!options.sequential && runner.config.sequence.concurrent) 395 395 ) { 396 396 task.concurrent = true 397 397 }
+50 -22
test/config/test/sequence-concurrent.test.ts
··· 3 3 import { runVitest } from '../../test-utils' 4 4 5 5 test('should run suites and tests concurrently unless sequential specified when sequence.concurrent is true', async () => { 6 - const { stderr, stdout } = await runVitest({ 6 + const { stderr, errorTree } = await runVitest({ 7 7 root: './fixtures/sequence-concurrent', 8 8 include: ['sequence-concurrent-true-*.test.ts'], 9 9 sequence: { ··· 12 12 }) 13 13 14 14 expect(stderr).toBe('') 15 - 16 - expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > sequential suite > first test completes first') 17 - expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > sequential suite > second test completes second') 18 - expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > third test completes third') 19 - expect(stdout).toContain('✓ sequence-concurrent-true-sequential.test.ts > last test completes last') 20 - expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > concurrent suite > first test completes last') 21 - expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > concurrent suite > second test completes third') 22 - expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > third test completes second') 23 - expect(stdout).toContain('✓ sequence-concurrent-true-concurrent.test.ts > last test completes first') 24 - expect(stdout).toContain('Test Files 2 passed (2)') 15 + expect(errorTree()).toMatchInlineSnapshot(` 16 + { 17 + "sequence-concurrent-true-concurrent-false.test.ts": { 18 + "last test completes last": "passed", 19 + "sequential suite": { 20 + "first test completes first": "passed", 21 + "second test completes second": "passed", 22 + }, 23 + "third test completes third": "passed", 24 + }, 25 + "sequence-concurrent-true-concurrent.test.ts": { 26 + "concurrent suite": { 27 + "first test completes last": "passed", 28 + "second test completes third": "passed", 29 + }, 30 + "last test completes first": "passed", 31 + "third test completes second": "passed", 32 + }, 33 + "sequence-concurrent-true-sequential.test.ts": { 34 + "last test completes last": "passed", 35 + "sequential suite": { 36 + "first test completes first": "passed", 37 + "second test completes second": "passed", 38 + }, 39 + "third test completes third": "passed", 40 + }, 41 + } 42 + `) 25 43 }) 26 44 27 45 test('should run suites and tests sequentially unless concurrent specified when sequence.concurrent is false', async () => { 28 - const { stderr, stdout } = await runVitest({ 46 + const { stderr, errorTree } = await runVitest({ 29 47 root: './fixtures/sequence-concurrent', 30 48 include: ['sequence-concurrent-false-*.test.ts'], 31 49 sequence: { ··· 34 52 }) 35 53 36 54 expect(stderr).toBe('') 37 - 38 - expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > sequential suite > first test completes first') 39 - expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > sequential suite > second test completes second') 40 - expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > third test completes third') 41 - expect(stdout).toContain('✓ sequence-concurrent-false-sequential.test.ts > last test completes last') 42 - expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > concurrent suite > first test completes last') 43 - expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > concurrent suite > second test completes third') 44 - expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > third test completes second') 45 - expect(stdout).toContain('✓ sequence-concurrent-false-concurrent.test.ts > last test completes first') 46 - expect(stdout).toContain('Test Files 2 passed (2)') 55 + expect(errorTree()).toMatchInlineSnapshot(` 56 + { 57 + "sequence-concurrent-false-concurrent.test.ts": { 58 + "concurrent suite": { 59 + "first test completes last": "passed", 60 + "second test completes third": "passed", 61 + }, 62 + "last test completes first": "passed", 63 + "third test completes second": "passed", 64 + }, 65 + "sequence-concurrent-false-sequential.test.ts": { 66 + "last test completes last": "passed", 67 + "sequential suite": { 68 + "first test completes first": "passed", 69 + "second test completes second": "passed", 70 + }, 71 + "third test completes third": "passed", 72 + }, 73 + } 74 + `) 47 75 })
+19
test/core/test/sequential.test.ts
··· 52 52 expect(task.concurrent).toBeFalsy() 53 53 expect(++count).toBe(4) 54 54 }) 55 + 56 + test('fifth test completes fifth', { concurrent: false }, async ({ task }) => { 57 + await delay(50) 58 + expect(task.concurrent).toBeFalsy() 59 + expect(++count).toBe(5) 60 + }) 61 + 62 + test('sixth test completes sixth', { concurrent: false }, ({ task }) => { 63 + expect(task.concurrent).toBeFalsy() 64 + expect(++count).toBe(6) 65 + }) 55 66 } 56 67 57 68 assertSequential() ··· 62 73 describe('describe', assertConcurrent) 63 74 64 75 describe.sequential('describe.sequential', () => { 76 + assertSequential() 77 + 78 + describe('describe', assertSequential) 79 + 80 + describe.concurrent('describe.concurrent', assertConcurrent) 81 + }) 82 + 83 + describe('describe concurrent false', { concurrent: false }, () => { 65 84 assertSequential() 66 85 67 86 describe('describe', assertSequential)
+25 -9
packages/runner/src/types/tasks.ts
··· 1 1 import type { Awaitable, TestError } from '@vitest/utils' 2 2 import type { TestFixtures } from '../fixture' 3 3 import type { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach } from '../hooks' 4 - import type { ChainableFunction, kChainableContext } from '../utils/chain' 4 + import type { kChainableContext, TypedChainableFunction } from '../utils/chain' 5 5 6 6 export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued' 7 7 export type TaskState = RunMode | 'pass' | 'fail' ··· 464 464 /** @internal */ 465 465 getFixtures: () => TestFixtures 466 466 } 467 - type ChainableTestAPI<ExtraContext = object> = ChainableFunction< 468 - 'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails', 467 + 468 + type ChainableTestContextMap = Pick< 469 + Required<TestOptions>, 470 + 'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'fails' 471 + > 472 + 473 + type ChainableTestAPI<ExtraContext = object> = TypedChainableFunction< 474 + ChainableTestContextMap, 469 475 TestCollectorCallable<ExtraContext>, 470 476 { 471 477 each: TestEachFunction ··· 554 560 /** 555 561 * Whether tests run sequentially. 556 562 * Tests inherit `sequential` from `describe()` and nested `describe()` will inherit from parent's `sequential`. 563 + * 564 + * @deprecated Use `concurrent: false` instead. 557 565 */ 558 566 sequential?: boolean 559 567 /** ··· 799 807 suite: SuiteAPI<ExtraContext> 800 808 } 801 809 802 - export interface InternalTestContext extends Record< 803 - 'concurrent' | 'sequential' | 'skip' | 'only' | 'todo' | 'fails' | 'each', 804 - boolean | undefined 805 - > { 810 + // use mapped type to preserve TestOptions references 811 + type InternalTestChainableContext = { 812 + [K in keyof ChainableTestContextMap]: boolean | undefined 813 + } 814 + 815 + export interface InternalTestContext extends InternalTestChainableContext { 816 + each: boolean | undefined 806 817 fixtures: TestFixtures 807 818 } 808 819 ··· 1061 1072 ): SuiteCollector<OverrideExtraContext> 1062 1073 } 1063 1074 1064 - type ChainableSuiteAPI<ExtraContext = object> = ChainableFunction< 1065 - 'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle', 1075 + type ChainableSuiteContextMap = Pick< 1076 + Required<SuiteOptions>, 1077 + 'concurrent' | 'sequential' | 'only' | 'skip' | 'todo' | 'shuffle' 1078 + > 1079 + 1080 + type ChainableSuiteAPI<ExtraContext = object> = TypedChainableFunction< 1081 + ChainableSuiteContextMap, 1066 1082 SuiteCollectorCallable<ExtraContext>, 1067 1083 { 1068 1084 each: TestEachFunction
+11
packages/runner/src/utils/chain.ts
··· 10 10 fn: (this: Record<T, any>, ...args: Parameters<F>) => ReturnType<F> 11 11 } & C 12 12 13 + // this uses mapped type technique to preserve T's jsdoc for chained property function 14 + export type TypedChainableFunction< 15 + T, 16 + F extends (...args: any) => any, 17 + C = object, 18 + > = F & { 19 + [x in keyof T]: TypedChainableFunction<T, F, C>; 20 + } & { 21 + fn: (this: Record<keyof T, any>, ...args: Parameters<F>) => ReturnType<F> 22 + } & C 23 + 13 24 export const kChainableContext: unique symbol = Symbol('kChainableContext') 14 25 15 26 export function getChainableContext(chainable: SuiteAPI): InternalChainableContext
+31
test/config/fixtures/sequence-concurrent/sequence-concurrent-true-concurrent-false.test.ts
··· 1 + import { describe, expect, test } from 'vitest' 2 + 3 + const delay = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout)) 4 + 5 + let count = 0 6 + 7 + describe('sequential suite', { concurrent: false }, () => { 8 + test('first test completes first', async ({ task }) => { 9 + await delay(40) 10 + expect(task.concurrent).toBeFalsy() 11 + expect(++count).toBe(1) 12 + }) 13 + 14 + test('second test completes second', async ({ task }) => { 15 + await delay(30) 16 + expect(task.concurrent).toBeFalsy() 17 + expect(++count).toBe(2) 18 + }) 19 + }) 20 + 21 + test('third test completes third', { concurrent: false }, async ({ task }) => { 22 + await delay(20) 23 + expect(task.concurrent).toBeFalsy() 24 + expect(++count).toBe(3) 25 + }) 26 + 27 + test('last test completes last', { concurrent: false }, async ({ task }) => { 28 + await delay(10) 29 + expect(task.concurrent).toBeFalsy() 30 + expect(++count).toBe(4) 31 + })