Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 13 additions & 24 deletions docs/api/describe.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,19 @@ describe.concurrent('suite', () => {
})
```

Set `concurrent` to `false` to opt out of concurrency inherited from a parent suite or [`sequence.concurrent`](/config/sequence#sequence-concurrent):

```ts
describe.concurrent('suite', () => {
test('concurrent test', async () => { /* ... */ })

describe('sequential suite', { concurrent: false }, () => {
test('sequential test 1', async () => { /* ... */ })
test('sequential test 2', async () => { /* ... */ })
})
})
```

`.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid:

```ts
Expand All @@ -230,30 +243,6 @@ describe.concurrent('suite', () => {
})
```

## describe.sequential <Deprecated /> {#describe-sequential}

- **Alias:** `suite.sequential`

::: warning DEPRECATED
Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency.
:::

`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.

```ts
import { describe, test } from 'vitest'

describe.concurrent('suite', () => {
test('concurrent test 1', async () => { /* ... */ })
test('concurrent test 2', async () => { /* ... */ })

describe.sequential('', () => {
test('sequential test 1', async () => { /* ... */ })
test('sequential test 2', async () => { /* ... */ })
})
})
```

## describe.shuffle

- **Alias:** `suite.shuffle`
Expand Down
46 changes: 6 additions & 40 deletions docs/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,13 @@ Prefer using non-nested meta, if possible.

Whether this test run concurrently with other concurrent tests in the suite.

### sequential
Set `concurrent` to `false` to opt out of concurrency inherited from [`describe.concurrent`](/api/describe#describe-concurrent) or [`sequence.concurrent`](/config/sequence#sequence-concurrent):

- **Type:** `boolean`
- **Default:** `true`
- **Alias:** [`test.sequential`](#test-sequential)

::: warning DEPRECATED
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
:::

Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence.
```ts
test('runs sequentially', { concurrent: false }, async () => {
// ...
})
```

### skip

Expand Down Expand Up @@ -457,36 +453,6 @@ test.concurrent('test 2', async ({ expect }) => {

Note that if tests are synchronous, Vitest will still run them sequentially.

## test.sequential <Deprecated /> {#test-sequential}

- **Alias:** `it.sequential`

::: warning DEPRECATED
Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency.
:::

`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.

```ts
import { describe, test } from 'vitest'

// with config option { sequence: { concurrent: true } }
test('concurrent test 1', async () => { /* ... */ })
test('concurrent test 2', async () => { /* ... */ })

test.sequential('sequential test 1', async () => { /* ... */ })
test.sequential('sequential test 2', async () => { /* ... */ })

// within concurrent suite
describe.concurrent('suite', () => {
test('concurrent test 1', async () => { /* ... */ })
test('concurrent test 2', async () => { /* ... */ })

test.sequential('sequential test 1', async () => { /* ... */ })
test.sequential('sequential test 2', async () => { /* ... */ })
})
```

## test.todo

- **Alias:** `it.todo`
Expand Down
23 changes: 23 additions & 0 deletions docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ outline: deep

[Migrating to Vitest 3.0](https://v3.vitest.dev/guide/migration) | [Migrating to Vitest 2.0](https://v2.vitest.dev/guide/migration)

## Migrating to Vitest 5.0 {#vitest-5}

### Removed `test.sequential`, `describe.sequential`, and `sequential` Options

Vitest 5.0 removes the deprecated `test.sequential`, `describe.sequential`, and `sequential` test options. Use `concurrent: false` when you need a test or suite to opt out of inherited or globally configured concurrency.

```ts
test.sequential('example', async () => { /* ... */ }) // [!code --]
test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++]
```

```ts
describe.sequential('suite', () => { /* ... */ }) // [!code --]
describe('suite', { concurrent: false }, () => { /* ... */ }) // [!code ++]
```

The same replacement applies to option objects:

```ts
test('example', { sequential: true }, async () => { /* ... */ }) // [!code --]
test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++]
```

## Migrating to Vitest 4.0 {#vitest-4}

::: warning Prerequisites
Expand Down
13 changes: 13 additions & 0 deletions docs/guide/parallelism.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ describe.concurrent('user API', () => {

If you want *all* tests in your project to run concurrently by default, set [`sequence.concurrent`](/config/sequence#sequence-concurrent) to `true` in your config.

You can opt individual tests or suites out of inherited concurrency with `concurrent: false`:

```ts
test('uses a shared resource', { concurrent: false }, async () => {
// ...
})

describe('shared resource suite', { concurrent: false }, () => {
test('step 1', async () => { /* ... */ })
test('step 2', async () => { /* ... */ })
})
```

### Hooks with Concurrent Tests

When tests run concurrently, lifecycle hooks behave differently. `beforeAll` and `afterAll` still run once for the group, but `beforeEach` and `afterEach` run for each test — potentially at the same time, since the tests themselves overlap.
Expand Down
25 changes: 8 additions & 17 deletions packages/vitest/src/node/ast-collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ interface LocalCallDefinition {
mode: 'run' | 'skip' | 'only' | 'todo' | 'queued'
task: ParsedSuite | ParsedFile | ParsedTest
dynamic: boolean
concurrent: boolean
sequential: boolean
concurrent: boolean | undefined
tags: string[]
}

Expand Down Expand Up @@ -172,8 +171,7 @@ function astParseFile(filepath: string, code: string) {
mode = 'skip'
}
}
let isConcurrent = properties.includes('concurrent')
let isSequential = properties.includes('sequential')
let concurrent = properties.includes('concurrent') || undefined

let start: number
const end = node.end
Expand Down Expand Up @@ -249,15 +247,12 @@ function astParseFile(filepath: string, code: string) {
}
}
}
else if (prop.value?.type === 'Literal' && prop.value.value === true) {
if (keyName === 'skip' || keyName === 'only' || keyName === 'todo') {
else if (prop.value?.type === 'Literal') {
if ((keyName === 'skip' || keyName === 'only' || keyName === 'todo') && prop.value.value === true) {
mode = keyName
}
else if (keyName === 'concurrent') {
isConcurrent = true
}
else if (keyName === 'sequential') {
isSequential = true
else if (keyName === 'concurrent' && typeof prop.value.value === 'boolean') {
concurrent = prop.value.value
Comment thread
hi-ogawa marked this conversation as resolved.
Resolved
}
}
}
Expand All @@ -272,8 +267,7 @@ function astParseFile(filepath: string, code: string) {
mode,
task: null as any,
dynamic: isDynamicEach,
concurrent: isConcurrent,
sequential: isSequential,
concurrent,
tags,
} satisfies LocalCallDefinition)
},
Expand Down Expand Up @@ -416,10 +410,7 @@ function createFileTask(
// Inherit tags from parent suite and merge with own tags
const parentTags = latestSuite.tags || []
const taskTags = unique([...parentTags, ...definition.tags])
// resolve concurrent/sequential: sequential cancels inherited concurrent
const concurrent = definition.sequential
? undefined
: (definition.concurrent || latestSuite.concurrent || undefined)
const concurrent = definition.concurrent ?? latestSuite.concurrent

if (definition.type === 'suite') {
const task: ParsedSuite = {
Expand Down
80 changes: 32 additions & 48 deletions test/cli/test/static-collect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1137,14 +1137,15 @@ test('collects tags with other options', async () => {
`)
})

test('sequential cancels inherited concurrent', async () => {
test('concurrent false cancels inherited concurrent', async () => {
const testModule = await collectTests(`
import { test, describe } from 'vitest'

describe.concurrent('concurrent suite', () => {
test('inherits concurrent', () => {})
test('not concurrent via options', { concurrent: false }, () => {})

describe.sequential('sequential nested', () => {
describe('non-concurrent nested', { concurrent: false }, () => {
test('not concurrent', () => {})
})

Expand All @@ -1165,23 +1166,31 @@ test('sequential cancels inherited concurrent', async () => {
"mode": "run",
"state": "pending",
},
"regular nested": {
"still concurrent": {
"concurrent": true,
"non-concurrent nested": {
"not concurrent": {
"errors": [],
"fullName": "concurrent suite > regular nested > still concurrent",
"fullName": "concurrent suite > non-concurrent nested > not concurrent",
"id": "-1732721377_0_2_0",
"location": "12:8",
"location": "9:8",
"mode": "run",
"state": "pending",
},
},
"sequential nested": {
"not concurrent": {
"not concurrent via options": {
"errors": [],
"fullName": "concurrent suite > not concurrent via options",
"id": "-1732721377_0_1",
"location": "6:6",
"mode": "run",
"state": "pending",
},
"regular nested": {
"still concurrent": {
"concurrent": true,
"errors": [],
"fullName": "concurrent suite > sequential nested > not concurrent",
"id": "-1732721377_0_1_0",
"location": "8:8",
"fullName": "concurrent suite > regular nested > still concurrent",
"id": "-1732721377_0_3_0",
"location": "13:8",
"mode": "run",
"state": "pending",
},
Expand All @@ -1191,40 +1200,6 @@ test('sequential cancels inherited concurrent', async () => {
`)
})

test('collects tests with sequential modifier', async () => {
const testModule = await collectTests(`
import { test, describe } from 'vitest'

describe.sequential('sequential suite', () => {
test('test in sequential suite', () => {})
})

test.sequential('sequential test', () => {})
`)
expect(testModule).toMatchInlineSnapshot(`
{
"sequential suite": {
"test in sequential suite": {
"errors": [],
"fullName": "sequential suite > test in sequential suite",
"id": "-1732721377_0_0",
"location": "5:6",
"mode": "run",
"state": "pending",
},
},
"sequential test": {
"errors": [],
"fullName": "sequential test",
"id": "-1732721377_1",
"location": "8:4",
"mode": "run",
"state": "pending",
},
}
`)
})

test('collects tests with concurrent modifier in different order', async () => {
const testModule = await collectTests(`
import { test, describe } from 'vitest'
Expand Down Expand Up @@ -1269,6 +1244,7 @@ test('collects tests with options object modifiers', async () => {
test('only via options', { only: true }, () => {})
test('todo via options', { todo: true }, () => {})
test('concurrent via options', { concurrent: true }, () => {})
test('not concurrent via options', { concurrent: false }, () => {})
test('skip and concurrent via options', { skip: true, concurrent: true }, () => {})
})
`)
Expand All @@ -1284,6 +1260,14 @@ test('collects tests with options object modifiers', async () => {
"mode": "skip",
"state": "skipped",
},
"not concurrent via options": {
"errors": [],
"fullName": "options tests > not concurrent via options",
"id": "-1732721377_0_4",
"location": "9:6",
"mode": "skip",
"state": "skipped",
},
"only via options": {
"errors": [],
"fullName": "options tests > only via options",
Expand All @@ -1296,8 +1280,8 @@ test('collects tests with options object modifiers', async () => {
"concurrent": true,
"errors": [],
"fullName": "options tests > skip and concurrent via options",
"id": "-1732721377_0_4",
"location": "9:6",
"id": "-1732721377_0_5",
"location": "10:6",
"mode": "skip",
"state": "skipped",
},
Expand Down
Loading
Loading