diff --git a/docs/guide/cli.md b/docs/guide/cli.md
index ebd67860a6fe..c831539f5a83 100644
--- a/docs/guide/cli.md
+++ b/docs/guide/cli.md
@@ -100,6 +100,7 @@ Run only [benchmark](https://vitest.dev/guide/features.html#benchmarking-experim
| `--typecheck [options]` | Custom options for typecheck pool. If passed without options, enables typechecking |
| `--typecheck.enabled` | Enable typechecking alongside tests (default: `false`) |
| `--typecheck.only` | Run only typecheck tests. This automatically enables typecheck (default: `false`) |
+| `--project` | The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: `--project=1 --project=2` |
| `-h, --help` | Display available CLI options |
::: tip
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 4cd1d78bf42c..fadddb5b8c77 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -19,18 +19,18 @@ You can try Vitest online on [StackBlitz](https://vitest.new). It runs Vitest di
Learn how to install by Video
::: code-group
- ```bash [npm]
- npm install -D vitest
- ```
- ```bash [yarn]
- yarn add -D vitest
- ```
- ```bash [pnpm]
- pnpm add -D vitest
- ```
- ```bash [bun]
- bun add -D vitest
- ```
+```bash [npm]
+npm install -D vitest
+```
+```bash [yarn]
+yarn add -D vitest
+```
+```bash [pnpm]
+pnpm add -D vitest
+```
+```bash [bun]
+bun add -D vitest
+```
:::
:::tip
diff --git a/docs/guide/workspace.md b/docs/guide/workspace.md
index 9212ebe46f3d..2df427dcd2c4 100644
--- a/docs/guide/workspace.md
+++ b/docs/guide/workspace.md
@@ -99,6 +99,49 @@ export default defineProject({
```
:::
+## Running tests
+
+To run tests inside the workspace, define a script in your root `package.json`:
+
+```json
+{
+ "scripts": {
+ "test": "vitest"
+ }
+}
+```
+
+Now tests can be run using your package manager:
+
+::: code-group
+```bash [npm]
+npm run test
+```
+```bash [yarn]
+yarn test
+```
+```bash [pnpm]
+pnpm run test
+```
+```bash [bun]
+bun test
+```
+:::
+
+If you need to run tests only inside a single project, use the `--project` CLI option:
+
+```bash
+npm run test --project e2e
+```
+
+::: tip
+CLI option `--project` can be used multiple times to filter out several projects:
+
+```bash
+npm run test --project e2e --project unit
+```
+:::
+
## Configuration
None of the configuration options are inherited from the root-level config file. You can create a shared config file and merge it with the project config yourself:
diff --git a/packages/vitest/src/node/cli.ts b/packages/vitest/src/node/cli.ts
index 415959c5ffbe..49bc4a5cbf5b 100644
--- a/packages/vitest/src/node/cli.ts
+++ b/packages/vitest/src/node/cli.ts
@@ -60,6 +60,7 @@ cli
.option('--typecheck [options]', 'Custom options for typecheck pool')
.option('--typecheck.enabled', 'Enable typechecking alongside tests (default: false)')
.option('--typecheck.only', 'Run only typecheck tests. This automatically enables typecheck (default: false)')
+ .option('--project ', 'The name of the project to run if you are using Vitest workspace feature. This can be repeated for multiple projects: --project=1 --project=2')
.help()
cli
diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts
index 8b605142885e..bc36be4c30d3 100644
--- a/packages/vitest/src/node/core.ts
+++ b/packages/vitest/src/node/core.ts
@@ -56,6 +56,7 @@ export class Vitest {
private coreWorkspaceProject!: WorkspaceProject
+ private resolvedProjects: WorkspaceProject[] = []
public projects: WorkspaceProject[] = []
private projectsTestFiles = new Map>()
@@ -151,7 +152,12 @@ export class Vitest {
await Promise.all(this._onSetServer.map(fn => fn()))
- this.projects = await this.resolveWorkspace(cliOptions)
+ const projects = await this.resolveWorkspace(cliOptions)
+ this.projects = projects
+ this.resolvedProjects = projects
+ const filteredProjects = toArray(resolved.project)
+ if (filteredProjects.length)
+ this.projects = this.projects.filter(p => filteredProjects.includes(p.getName()))
if (!this.coreWorkspaceProject)
this.coreWorkspaceProject = WorkspaceProject.createBasicProject(this)
@@ -512,6 +518,17 @@ export class Vitest {
await this.report('onWatcherStart', this.state.getFiles(files))
}
+ async changeProjectName(pattern: string) {
+ if (pattern === '')
+ delete this.configOverride.project
+ else
+ this.configOverride.project = pattern
+
+ this.projects = this.resolvedProjects.filter(p => p.getName() === pattern)
+ const files = (await this.globTestFiles()).map(([, file]) => file)
+ await this.rerunFiles(files, 'change project filter')
+ }
+
async changeNamePattern(pattern: string, files: string[] = this.state.getFilepaths(), trigger?: string) {
// Empty test name pattern should reset filename pattern as well
if (pattern === '')
diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts
index bb40205bb42e..8a3e736f1726 100644
--- a/packages/vitest/src/node/logger.ts
+++ b/packages/vitest/src/node/logger.ts
@@ -3,6 +3,7 @@ import c from 'picocolors'
import { version } from '../../../../package.json'
import type { ErrorWithDiff } from '../types'
import type { TypeCheckError } from '../typecheck/typechecker'
+import { toArray } from '../utils'
import { divider } from './reporters/renderers/utils'
import { RandomSequencer } from './sequencers/RandomSequencer'
import type { Vitest } from './core'
@@ -95,6 +96,9 @@ export class Logger {
const comma = c.dim(', ')
if (filters?.length)
this.console.error(c.dim('filter: ') + c.yellow(filters.join(comma)))
+ const projectsFilter = toArray(config.project)
+ if (projectsFilter.length)
+ this.console.error(c.dim('projects: ') + c.yellow(projectsFilter.join(comma)))
this.ctx.projects.forEach((project) => {
const config = project.config
const name = project.getName()
diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts
index 73a1c754b710..bde767865e39 100644
--- a/packages/vitest/src/node/reporters/base.ts
+++ b/packages/vitest/src/node/reporters/base.ts
@@ -1,7 +1,7 @@
import { performance } from 'node:perf_hooks'
import c from 'picocolors'
import type { ErrorWithDiff, File, Reporter, Task, TaskResultPack, UserConsoleLog } from '../../types'
-import { getFullName, getSafeTimers, getSuites, getTests, hasFailed, hasFailedSnapshot, isCI, isNode, relativePath } from '../../utils'
+import { getFullName, getSafeTimers, getSuites, getTests, hasFailed, hasFailedSnapshot, isCI, isNode, relativePath, toArray } from '../../utils'
import type { Vitest } from '../../node'
import { F_RIGHT } from '../../utils/figures'
import { UNKNOWN_TEST_ID } from '../../runtime/console'
@@ -169,16 +169,17 @@ export abstract class BaseReporter implements Reporter {
const TRIGGER = trigger ? c.dim(` ${this.relative(trigger)}`) : ''
const FILENAME_PATTERN = this.ctx.filenamePattern ? `${BADGE_PADDING} ${c.dim('Filename pattern: ')}${c.blue(this.ctx.filenamePattern)}\n` : ''
const TESTNAME_PATTERN = this.ctx.configOverride.testNamePattern ? `${BADGE_PADDING} ${c.dim('Test name pattern: ')}${c.blue(String(this.ctx.configOverride.testNamePattern))}\n` : ''
+ const PROJECT_FILTER = this.ctx.configOverride.project ? `${BADGE_PADDING} ${c.dim('Project name: ')}${c.blue(toArray(this.ctx.configOverride.project).join(', '))}\n` : ''
- if (files.length > 1) {
+ if (files.length > 1 || !files.length) {
// we need to figure out how to handle rerun all from stdin
- this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER}\n${FILENAME_PATTERN}${TESTNAME_PATTERN}`)
+ this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER}\n${PROJECT_FILTER}${FILENAME_PATTERN}${TESTNAME_PATTERN}`)
this._lastRunCount = 0
}
else if (files.length === 1) {
const rerun = this._filesInWatchMode.get(files[0]) ?? 1
this._lastRunCount = rerun
- this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER} ${c.blue(`x${rerun}`)}\n${FILENAME_PATTERN}${TESTNAME_PATTERN}`)
+ this.ctx.logger.clearFullScreen(`\n${BADGE}${TRIGGER} ${c.blue(`x${rerun}`)}\n${PROJECT_FILTER}${FILENAME_PATTERN}${TESTNAME_PATTERN}`)
}
this._timeStart = new Date()
diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts
index 636960092aa5..a9c4a3129edb 100644
--- a/packages/vitest/src/node/stdin.ts
+++ b/packages/vitest/src/node/stdin.ts
@@ -2,6 +2,7 @@ import readline from 'node:readline'
import c from 'picocolors'
import prompt from 'prompts'
import { isWindows, stdout } from '../utils'
+import { toArray } from '../utils/base'
import type { Vitest } from './core'
const keys = [
@@ -11,6 +12,7 @@ const keys = [
['u', 'update snapshot'],
['p', 'filter by a filename'],
['t', 'filter by a test name regex pattern'],
+ ['w', 'filter by a project name'],
['q', 'quit'],
]
const cancelKeys = ['space', 'c', 'h', ...keys.map(key => key[0]).flat()]
@@ -76,6 +78,9 @@ export function registerConsoleShortcuts(ctx: Vitest) {
// rerun only failed tests
if (name === 'f')
return ctx.rerunFailed()
+ // change project filter
+ if (name === 'w')
+ return inputProjectName()
// change testNamePattern
if (name === 't')
return inputNamePattern()
@@ -100,6 +105,18 @@ export function registerConsoleShortcuts(ctx: Vitest) {
await ctx.changeNamePattern(filter.trim(), undefined, 'change pattern')
}
+ async function inputProjectName() {
+ off()
+ const { filter = '' }: { filter: string } = await prompt([{
+ name: 'filter',
+ type: 'text',
+ message: 'Input a single project name',
+ initial: toArray(ctx.configOverride.project)[0] || '',
+ }])
+ on()
+ await ctx.changeProjectName(filter.trim())
+ }
+
async function inputFilePattern() {
off()
const { filter = '' }: { filter: string } = await prompt([{
diff --git a/packages/vitest/src/types/config.ts b/packages/vitest/src/types/config.ts
index aaf2c210a585..df434c519bb1 100644
--- a/packages/vitest/src/types/config.ts
+++ b/packages/vitest/src/types/config.ts
@@ -716,6 +716,11 @@ export interface UserConfig extends InlineConfig {
* @example --shard=2/3
*/
shard?: string
+
+ /**
+ * Name of the project or projects to run.
+ */
+ project?: string | string[]
}
export interface ResolvedConfig extends Omit, 'config' | 'filters' | 'browser' | 'coverage' | 'testNamePattern' | 'related' | 'api' | 'reporters' | 'resolveSnapshotPath' | 'benchmark' | 'shard' | 'cache' | 'sequence' | 'typecheck' | 'runner' | 'poolOptions' | 'pool'> {