Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/.vitepress/scripts/cli-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const skipConfig = new Set([
'browser.name',
'browser.fileParallelism',
'clearCache',
'injectReporter',
])

function resolveOptions(options: CLIOptions<any>, parentName?: string) {
Expand Down
28 changes: 24 additions & 4 deletions docs/guide/cli-generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,12 +518,26 @@ Default hook timeout in milliseconds (default: `10000`). Use `0` to disable time

Stop test execution when given number of tests have failed (default: `0`)

### retry
### retry.count

- **CLI:** `--retry <times>`
- **Config:** [retry](/config/retry)
- **CLI:** `--retry.count <times>`
- **Config:** [retry.count](/config/retry#retry-count)

Retry the test specific number of times if it fails (default: `0`)
Number of times to retry a test if it fails (default: `0`)

### retry.delay

- **CLI:** `--retry.delay <ms>`
- **Config:** [retry.delay](/config/retry#retry-delay)

Delay in milliseconds between retry attempts (default: `0`)

### retry.condition

- **CLI:** `--retry.condition <pattern>`
- **Config:** [retry.condition](/config/retry#retry-condition)

Regex pattern to match error messages that should trigger a retry. Only errors matching this pattern will cause a retry (default: retry on all errors)

### diff.aAnnotation

Expand Down Expand Up @@ -798,6 +812,12 @@ Start Vitest without running tests. Tests will be running only on change. This o

Delete all Vitest caches, including `experimental.fsModuleCache`, without running any tests. This will reduce the performance in the subsequent test run.

### injectReporter

- **CLI:** `--injectReporter <name>`

Inject custom reporter(s) into the reporters list without overriding existing ones.

### experimental.fsModuleCache

- **CLI:** `--experimental.fsModuleCache`
Expand Down
14 changes: 14 additions & 0 deletions docs/guide/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ Boolean options can be negated with `no-` prefix. Specifying the value as `false
vitest --no-api
vitest --api=false
```

Since Vitest 4.1, CLI also accepts options via `VITEST_OPTIONS` environmental variable, similar to [`NODE_OPTIONS`](https://nodejs.org/api/cli.html#node_optionsoptions):

```
VITEST_OPTIONS=--reporter=dot vitest
```

This can be useful if you spawn `vitest` manually in a custom script. `VITEST_OPTIONS` will be merged with other CLI options.

Vitest also accepts `VITEST_FILTERS` environment variable to pass down additional filters:

```
VITEST_FILTERS=basic.test.ts vitest advanced.test.ts
```
:::

<!--@include: ./cli-generated.md-->
Expand Down
11 changes: 10 additions & 1 deletion packages/vitest/src/node/cli/cac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { toArray } from '@vitest/utils/helpers'
import cac from 'cac'
import { normalize } from 'pathe'
import c from 'tinyrainbow'
import { mergeConfig } from 'vite'
import { version } from '../../../package.json' with { type: 'json' }
import { benchCliOptionsConfig, cliOptionsConfig, collectCliOptionsConfig } from './cli-config'

Expand Down Expand Up @@ -299,7 +300,15 @@ function normalizeCliOptions(cliFilters: string[], argv: CliOptions): CliOptions
async function start(mode: VitestRunMode, cliFilters: string[], options: CliOptions): Promise<void> {
try {
const { startVitest } = await import('./cli-api')
const ctx = await startVitest(mode, cliFilters.map(normalize), normalizeCliOptions(cliFilters, options))
if (process.env.VITEST_FILTERS) {
cliFilters.push(...process.env.VITEST_FILTERS.split(' '))
}
let normalizedOptions = normalizeCliOptions(cliFilters, options)
if (process.env.VITEST_OPTIONS) {
const vitestConfig = parseCLI(`vitest ${process.env.VITEST_OPTIONS}`).options
normalizedOptions = mergeConfig(normalizedOptions, normalizeCliOptions([], vitestConfig)) as CliOptions
}
const ctx = await startVitest(mode, cliFilters.map(normalize), normalizedOptions)
if (!ctx.shouldKeepServer()) {
await ctx.exit()
}
Expand Down
5 changes: 5 additions & 0 deletions packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,11 @@ export const cliOptionsConfig: VitestCLIOptions = {
clearCache: {
description: 'Delete all Vitest caches, including `experimental.fsModuleCache`, without running any tests. This will reduce the performance in the subsequent test run.',
},
injectReporter: {
description: 'Inject custom reporter(s) into the reporters list without overriding existing ones.',
argument: '<name>',
array: true,
},

experimental: {
description: 'Experimental features.',
Expand Down
9 changes: 9 additions & 0 deletions packages/vitest/src/node/config/resolveConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,15 @@ export function resolveConfig(
}
}

if (resolved.injectReporter) {
resolved.injectReporter.forEach((reporter) => {
const path = /^\.\.?\//.test(reporter)
? resolve(process.cwd(), reporter)
: reporter
resolved.reporters.push([path, {}])
})
}

if (resolved.changed) {
resolved.passWithNoTests ??= true
}
Expand Down
5 changes: 5 additions & 0 deletions packages/vitest/src/node/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,11 @@ export interface UserConfig extends InlineConfig {
* @experimental
*/
clearCache?: boolean

/**
* Inject custom reporter(s) into the reporters list without overriding existing ones.
*/
injectReporter?: string[]
}

export type OnUnhandledErrorCallback = (error: (TestError | Error) & { type: string }) => boolean | void
Expand Down
73 changes: 73 additions & 0 deletions test/cli/test/config-env-option.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { resolve } from 'node:path'
import { runVitestCli, useFS } from '#test-utils'
import { expect, test } from 'vitest'

test('passing down VITEST_FILTERS works', async () => {
const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
useFS(root, {
'basic-1.test.js': `test('basic 1', () => {})`,
'basic-2.test.js': `test('basic 2', () => {})`,
'vitest.config.js': {
test: {
globals: true,
},
},
})

const { stdout, stderr } = await runVitestCli({
nodeOptions: {
env: {
VITEST_FILTERS: 'basic-1',
},
},
}, `--root`, root)

expect(stderr).toBe('')
expect(stdout).toContain('1 passed')
expect(stdout).toContain('basic-1')
expect(stdout).not.toContain('basic-2')
})

test('passing down VITEST_OPTIONS overrides argv', async () => {
const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)
useFS(root, {
'output.test.js': `test('basic 1', () => {})`,
'vitest.config.js': `
export default {
test: {
globals: true,
reporters: [
{
onInit(vitest) {
console.log(JSON.stringify(vitest.getRootProject().serializedConfig))
throw new Error('Stopping tests')
}
}
]
},
}
`,
})

const { stdout } = await runVitestCli({
nodeOptions: {
env: {
VITEST_OPTIONS: '--logHeapUsage --allowOnly --sequence.seed 123 --testTimeout 5321 --pool forks --globals --retry 6 --passWithNoTests --bail 100',
},
},
}, `--root`, root, '--testTimeout', '999', '--pool', 'vmThreads')
const config = JSON.parse(stdout)
expect(config).toMatchObject({
logHeapUsage: true,
allowOnly: true,
sequence: {
seed: 123,
},
testTimeout: 5321, // not 999
pool: 'forks', // not vmThreads
globals: true,
retry: 6,
passWithNoTests: true,
bail: 100,
})
})
67 changes: 48 additions & 19 deletions test/config/test/cli-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { resolve } from 'pathe'
import { expect, it, test } from 'vitest'
import { createVitest } from 'vitest/node'

import { runVitest } from '../../test-utils'

test('can pass down the config as a module', async () => {
const vitest = await createVitest('test', {
config: '@test/test-dep-config',
Expand All @@ -15,25 +13,23 @@ test('can pass down the config as a module', async () => {
})

it('correctly inherit from the cli', async () => {
const { ctx } = await runVitest({
$cliOptions: {
root: 'fixtures/workspace-flags',
logHeapUsage: true,
allowOnly: true,
sequence: {
seed: 123,
},
testTimeout: 5321,
pool: 'forks',
globals: true,
expandSnapshotDiff: true,
retry: 6,
testNamePattern: 'math',
passWithNoTests: true,
bail: 100,
const vitest = await createVitest('test', {
root: 'fixtures/workspace-flags',
logHeapUsage: true,
allowOnly: true,
sequence: {
seed: 123,
},
testTimeout: 5321,
pool: 'forks',
globals: true,
expandSnapshotDiff: true,
retry: 6,
testNamePattern: 'math',
passWithNoTests: true,
bail: 100,
})
const project = ctx!.projects[0]
const project = vitest.projects[0]
const config = project.config
expect(config).toMatchObject({
logHeapUsage: true,
Expand All @@ -51,3 +47,36 @@ it('correctly inherit from the cli', async () => {
})
expect(config.testNamePattern?.test('math')).toBe(true)
})

it('inject reporter injects reporters', async () => {
const vitest = await createVitest('test', {
config: false,
reporters: ['verbose'],
injectReporter: [
'./my-reporter.js',
'@org/another-reporter',
'.reporters/custom.js',
'/absolute/path/to/reporter.js',
],
}, {
plugins: [
{
name: 'import-reporter',
load() {
// vitest always loads a reporter, fake it
return `export default class Reporter {}`
},
},
],
})

expect(vitest.config.reporters).toEqual([
// doesn't override verbose reporter
['verbose', {}],
// correctly resolves relative path
[resolve(process.cwd(), './my-reporter.js'), {}],
['@org/another-reporter', {}],
['.reporters/custom.js', {}],
['/absolute/path/to/reporter.js', {}],
])
})
7 changes: 7 additions & 0 deletions test/core/test/cli-test.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,13 @@ test('clearScreen', async (ctx) => {
`)
})

test('--injectReporter', () => {
expect(getCLIOptions('--injectReporter=./my-reporter.js')).toEqual({ injectReporter: ['./my-reporter.js'] })
expect(getCLIOptions('--injectReporter ./my-reporter.js --injectReporter ./another-reporter.js')).toEqual({
injectReporter: ['./my-reporter.js', './another-reporter.js'],
})
})

test('merge-reports', () => {
expect(getCLIOptions('--merge-reports')).toEqual({ mergeReports: '.vitest-reports' })
expect(getCLIOptions('--merge-reports=different-folder')).toEqual({ mergeReports: 'different-folder' })
Expand Down
Loading