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

feat(reporters): add `agent` reporter to reduce ai agent token usage (#9779)

authored by

Christoph Nakazawa and committed by
GitHub
(Mar 9, 2026, 10:28 AM +0100) 3e9e096a b0b83e21

+187 -18
+9 -4
pnpm-lock.yaml
··· 103 103 specifier: ^3.0.2 104 104 version: 3.0.2 105 105 std-env: 106 - specifier: ^3.10.0 107 - version: 3.10.0 106 + specifier: ^4.0.0-rc.1 107 + version: 4.0.0-rc.1 108 108 strip-literal: 109 109 specifier: ^3.1.0 110 110 version: 3.1.0 ··· 701 701 version: 2.1.1 702 702 std-env: 703 703 specifier: 'catalog:' 704 - version: 3.10.0 704 + version: 4.0.0-rc.1 705 705 tinyrainbow: 706 706 specifier: 'catalog:' 707 707 version: 3.0.3 ··· 1046 1046 version: 4.0.3 1047 1047 std-env: 1048 1048 specifier: 'catalog:' 1049 - version: 3.10.0 1049 + version: 4.0.0-rc.1 1050 1050 tinybench: 1051 1051 specifier: ^2.9.0 1052 1052 version: 2.9.0 ··· 9191 9191 9192 9192 std-env@3.10.0: 9193 9193 resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} 9194 + 9195 + std-env@4.0.0-rc.1: 9196 + resolution: {integrity: sha512-2gE+MEGsqvDEjl7LqbrCEB3Lo6+Pmt8ULCIsutKTesFBzSuNIkiWPSy65sa7WrlLebc9LcfnS5eyj69mutUp1A==} 9194 9197 9195 9198 stop-iteration-iterator@1.1.0: 9196 9199 resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} ··· 18295 18298 statuses@2.0.2: {} 18296 18299 18297 18300 std-env@3.10.0: {} 18301 + 18302 + std-env@4.0.0-rc.1: {} 18298 18303 18299 18304 stop-iteration-iterator@1.1.0: 18300 18305 dependencies:
+1 -1
pnpm-workspace.yaml
··· 78 78 pathe: ^2.0.3 79 79 playwright: ^1.58.2 80 80 sirv: ^3.0.2 81 - std-env: ^3.10.0 81 + std-env: ^4.0.0-rc.1 82 82 strip-literal: ^3.1.0 83 83 tinyexec: ^1.0.2 84 84 tinyglobby: ^0.2.15
+1
docs/config/reporters.md
··· 42 42 - [`tap-flat`](/guide/reporters#tap-flat-reporter) 43 43 - [`hanging-process`](/guide/reporters#hanging-process-reporter) 44 44 - [`github-actions`](/guide/reporters#github-actions-reporter) 45 + - [`agent`](/guide/reporters#agent-reporter) 45 46 - [`blob`](/guide/reporters#blob-reporter) 46 47 47 48 ## Example
+1 -1
docs/guide/cli-generated.md
··· 102 102 - **CLI:** `--reporter <name>` 103 103 - **Config:** [reporters](/config/reporters) 104 104 105 - Specify reporters (default, blob, verbose, dot, json, tap, tap-flat, junit, tree, hanging-process, github-actions) 105 + Specify reporters (default, agent, blob, verbose, dot, json, tap, tap-flat, junit, tree, hanging-process, github-actions) 106 106 107 107 ### outputFile 108 108
-1
docs/guide/cli.md
··· 237 237 ```sh 238 238 vitest --merge-reports --reporter=junit 239 239 ``` 240 -
+24
docs/guide/reporters.md
··· 98 98 99 99 By default (i.e. if no reporter is specified), Vitest will display summary of running tests and their status at the bottom. Once a suite passes, its status will be reported on top of the summary. 100 100 101 + ::: tip 102 + When Vitest detects it is running inside an AI coding agent, the [`agent`](#agent-reporter) reporter is used instead to reduce output and minimize token usage. You can override this by explicitly configuring the [`reporters`](/config/reporters) option. 103 + ::: 104 + 101 105 You can disable the summary by configuring the reporter: 102 106 103 107 :::code-group ··· 636 640 }, 637 641 }) 638 642 ``` 643 + 644 + ### Agent Reporter 645 + 646 + Outputs a minimal report optimized for AI coding assistants and LLM-based workflows. Only failed tests and their error messages are displayed. Console logs from passing tests and the summary section are suppressed to reduce token usage. 647 + 648 + This reporter is automatically enabled when no `reporters` option is configured and Vitest detects it is running inside an AI coding agent. If you configure custom reporters, you can explicitly add `agent`: 649 + 650 + :::code-group 651 + ```bash [CLI] 652 + npx vitest --reporter=agent 653 + ``` 654 + 655 + ```ts [vitest.config.ts] 656 + export default defineConfig({ 657 + test: { 658 + reporters: ['agent'] 659 + }, 660 + }) 661 + ``` 662 + ::: 639 663 640 664 ### Blob Reporter 641 665
+8 -1
test/test-utils/index.ts
··· 172 172 ...cliOptions, 173 173 env: { 174 174 NO_COLOR: 'true', 175 + AI_AGENT: '', 175 176 ...rest.env, 176 177 ...cliOptions?.env, 177 178 }, ··· 281 282 args.push('--maxWorkers=1') 282 283 } 283 284 284 - const subprocess = x(command, args, options as Options).process! 285 + const subprocess = x(command, args, { 286 + ...options as Options, 287 + nodeOptions: { 288 + ...(options as Options)?.nodeOptions, 289 + env: { ...process.env, AI_AGENT: '', ...(options as Options)?.nodeOptions?.env }, 290 + }, 291 + }).process! 285 292 const cli = new Cli({ 286 293 stdin: subprocess.stdin!, 287 294 stdout: subprocess.stdout!,
-2
docs/guide/browser/visual-regression-testing.md
··· 606 606 607 607 The cleanest approach is using [Test Projects](/guide/projects): 608 608 609 - 610 609 ```ts [vitest.config.ts] 611 610 import { env } from 'node:process' 612 611 import { defineConfig } from 'vitest/config' ··· 662 661 }, 663 662 }) 664 663 ``` 665 - 666 664 667 665 Follow the [official guide to create a Playwright Workspace](https://learn.microsoft.com/en-us/azure/app-testing/playwright-workspaces/quickstart-run-end-to-end-tests?tabs=playwrightcli&pivots=playwright-test-runner#create-a-workspace). 668 666
+2
test/core/test/exports.test.ts
··· 86 86 "takeCoverageInsideWorker": "function", 87 87 }, 88 88 "./node": { 89 + "AgentReporter": "function", 89 90 "BaseCoverageProvider": "function", 90 91 "BaseSequencer": "function", 91 92 "BenchmarkReporter": "function", ··· 140 141 "viteVersion": "string", 141 142 }, 142 143 "./reporters": { 144 + "AgentReporter": "function", 143 145 "BenchmarkReporter": "function", 144 146 "BenchmarkReportsMap": "object", 145 147 "DefaultReporter": "function",
+1
packages/vitest/src/public/node.ts
··· 43 43 export type { SerializedTestProject, TestProject } from '../node/project' 44 44 45 45 export { 46 + AgentReporter, 46 47 BenchmarkReporter, 47 48 BenchmarkReportsMap, 48 49 DefaultReporter,
+1
packages/vitest/src/public/reporters.ts
··· 1 1 export { 2 + AgentReporter, 2 3 BenchmarkReporter, 3 4 BenchmarkReportsMap, 4 5 DefaultReporter,
+1 -1
packages/vitest/src/utils/env.ts
··· 12 12 export const isWindows: boolean = (isNode || isDeno) && process.platform === 'win32' 13 13 export const isBrowser: boolean = typeof window !== 'undefined' 14 14 export const isTTY: boolean = ((isNode || isDeno) && process.stdout?.isTTY && !isCI) 15 - export { isCI, provider as stdProvider } from 'std-env' 15 + export { isAgent, isCI, provider as stdProvider } from 'std-env'
+1 -1
test/cli/test/config/browser-configs.test.ts
··· 649 649 { 650 650 nodeOptions: { 651 651 env: { 652 - CI: 'false', 652 + CI: '', 653 653 GITHUB_ACTIONS: undefined, 654 654 }, 655 655 },
+92
test/cli/test/reporters/agent.test.ts
··· 1 + import { runVitest, StableTestFileOrderSorter } from '#test-utils' 2 + import { describe, expect, test } from 'vitest' 3 + import { DefaultReporter } from 'vitest/node' 4 + import { trimReporterOutput } from './utils' 5 + 6 + describe('agent reporter', async () => { 7 + test('hides passed module headers, shows only failed tests, and prints end summary', async () => { 8 + const { stdout } = await runVitest({ 9 + include: ['b1.test.ts', 'b2.test.ts'], 10 + root: 'fixtures/reporters/default', 11 + reporters: [['agent', {}]], 12 + fileParallelism: false, 13 + sequence: { 14 + sequencer: StableTestFileOrderSorter, 15 + }, 16 + }) 17 + 18 + const output = trimReporterOutput(stdout) 19 + expect(output).toMatchInlineSnapshot(` 20 + "❯ b1.test.ts (13 tests | 1 failed) [...]ms 21 + × b failed test [...]ms 22 + ❯ b2.test.ts (13 tests | 1 failed) [...]ms 23 + × b failed test [...]ms" 24 + `) 25 + 26 + const summary = stdout.replace(/\d+ms/g, '[...]ms').split('\n').filter(line => /Test Files|^\s*Tests\b/.test(line)).map(line => line.trim()).join('\n') 27 + expect(summary).toMatchInlineSnapshot(` 28 + "Test Files 2 failed (2) 29 + Tests 2 failed | 24 passed (26)" 30 + `) 31 + }) 32 + 33 + test('hides all output for passed-only modules', async () => { 34 + const { stdout } = await runVitest({ 35 + include: ['b1.test.ts', 'b2.test.ts'], 36 + root: 'fixtures/reporters/default', 37 + reporters: [['agent', {}]], 38 + fileParallelism: false, 39 + testNamePattern: 'passed', 40 + sequence: { 41 + sequencer: StableTestFileOrderSorter, 42 + }, 43 + }) 44 + 45 + const output = trimReporterOutput(stdout) 46 + expect(output).toMatchInlineSnapshot(`""`) 47 + }) 48 + 49 + test('shows console logs only from failed file, suite, and tests', async () => { 50 + const { stdout } = await runVitest({ 51 + config: false, 52 + include: ['./fixtures/reporters/console-some-failing.test.ts'], 53 + reporters: [['agent', { isTTY: true }]], 54 + }) 55 + 56 + const logs = stdout.split('\n').filter(line => line.includes('Log from')).join('\n') 57 + expect(logs).toMatchInlineSnapshot(` 58 + "Log from failed test 59 + Log from failed test 60 + Log from failed suite 61 + Log from failed file" 62 + `) 63 + }) 64 + 65 + test('does not change silent behavior for other reporters', async () => { 66 + const { stdout } = await runVitest({ 67 + config: false, 68 + include: ['./fixtures/reporters/console-some-failing.test.ts'], 69 + reporters: [new LogReporter(), ['agent', { isTTY: true }]], 70 + }) 71 + 72 + const logs = stdout.split('\n').filter(line => line.includes('Log from')).join('\n') 73 + expect(logs).toMatchInlineSnapshot(` 74 + "Log from failed file 75 + Log from passed test 76 + Log from failed test 77 + Log from failed suite 78 + Log from passed test 79 + Log from failed test 80 + Log from passed suite 81 + Log from passed test 82 + Log from failed test 83 + Log from failed test 84 + Log from failed suite 85 + Log from failed file" 86 + `) 87 + }) 88 + }, 120000) 89 + 90 + class LogReporter extends DefaultReporter { 91 + isTTY = true 92 + }
+2 -2
packages/vitest/src/node/config/resolveConfig.ts
··· 23 23 defaultPort, 24 24 } from '../../constants' 25 25 import { benchmarkConfigDefaults, configDefaults } from '../../defaults' 26 - import { isCI, stdProvider } from '../../utils/env' 26 + import { isAgent, isCI, stdProvider } from '../../utils/env' 27 27 import { getWorkersCountByPercentage } from '../../utils/workers' 28 28 import { BaseSequencer } from '../sequencers/BaseSequencer' 29 29 import { RandomSequencer } from '../sequencers/RandomSequencer' ··· 729 729 } 730 730 731 731 if (!resolved.reporters.length) { 732 - resolved.reporters.push(['default', {}]) 732 + resolved.reporters.push([isAgent ? 'agent' : 'default', {}]) 733 733 734 734 // also enable github-actions reporter as a default 735 735 if (process.env.GITHUB_ACTIONS === 'true') {
+31
packages/vitest/src/node/reporters/agent.ts
··· 1 + import type { TestSpecification } from '../test-specification' 2 + import type { DefaultReporterOptions } from './default' 3 + import type { TestCase, TestModule, TestModuleState } from './reported-tasks' 4 + import { DefaultReporter } from './default' 5 + 6 + export class AgentReporter extends DefaultReporter { 7 + renderSucceed = false 8 + 9 + constructor(options: DefaultReporterOptions = {}) { 10 + super({ silent: 'passed-only', ...options, summary: false }) 11 + } 12 + 13 + onTestRunStart(specifications: ReadonlyArray<TestSpecification>): void { 14 + super.onTestRunStart(specifications) 15 + this.renderSucceed = false 16 + } 17 + 18 + protected printTestModule(testModule: TestModule): void { 19 + if (testModule.state() !== 'failed') { 20 + return 21 + } 22 + super.printTestModule(testModule) 23 + } 24 + 25 + protected printTestCase(moduleState: TestModuleState, test: TestCase): void { 26 + const testResult = test.result() 27 + if (testResult.state === 'failed') { 28 + super.printTestCase(moduleState, test) 29 + } 30 + } 31 + }
+8 -4
packages/vitest/src/node/reporters/base.ts
··· 37 37 38 38 export interface BaseOptions { 39 39 isTTY?: boolean 40 + silent?: boolean | 'passed-only' 40 41 } 41 42 42 43 export abstract class BaseReporter implements Reporter { ··· 49 50 renderSucceed = false 50 51 51 52 protected verbose = false 53 + protected silent?: boolean | 'passed-only' 52 54 53 55 private _filesInWatchMode = new Map<string, number>() 54 56 private _timeStart = formatTimeString(new Date()) 55 57 56 58 constructor(options: BaseOptions = {}) { 57 59 this.isTTY = options.isTTY ?? isTTY 60 + this.silent = options.silent 58 61 } 59 62 60 63 onInit(ctx: Vitest): void { 61 64 this.ctx = ctx 65 + this.silent ??= this.ctx.config.silent 62 66 63 67 this.ctx.logger.printBanner() 64 68 } ··· 117 121 this.printTestModule(testModule) 118 122 } 119 123 120 - private logFailedTask(task: Task) { 121 - if (this.ctx.config.silent === 'passed-only') { 124 + protected logFailedTask(task: Task): void { 125 + if (this.silent === 'passed-only') { 122 126 for (const log of task.logs || []) { 123 127 this.onUserConsoleLog(log, 'failed') 124 128 } ··· 504 508 } 505 509 506 510 shouldLog(log: UserConsoleLog, taskState?: TestResult['state']): boolean { 507 - if (this.ctx.config.silent === true) { 511 + if (this.silent === true) { 508 512 return false 509 513 } 510 514 511 - if (this.ctx.config.silent === 'passed-only' && taskState !== 'failed') { 515 + if (this.silent === 'passed-only' && taskState !== 'failed') { 512 516 return false 513 517 } 514 518
+4
packages/vitest/src/node/reporters/index.ts
··· 6 6 import type { HTMLOptions } from './html' 7 7 import type { JsonOptions } from './json' 8 8 import type { JUnitOptions } from './junit' 9 + import { AgentReporter } from './agent' 9 10 import { BlobReporter } from './blob' 10 11 import { DefaultReporter } from './default' 11 12 import { DotReporter } from './dot' ··· 19 20 import { VerboseReporter } from './verbose' 20 21 21 22 export { 23 + AgentReporter, 22 24 DefaultReporter, 23 25 DotReporter, 24 26 GithubActionsReporter, ··· 46 48 47 49 export const ReportersMap = { 48 50 'default': DefaultReporter as typeof DefaultReporter, 51 + 'agent': AgentReporter as typeof AgentReporter, 49 52 'blob': BlobReporter as typeof BlobReporter, 50 53 'verbose': VerboseReporter as typeof VerboseReporter, 51 54 'dot': DotReporter as typeof DotReporter, ··· 62 65 63 66 export interface BuiltinReporterOptions { 64 67 'default': DefaultReporterOptions 68 + 'agent': DefaultReporterOptions 65 69 'verbose': DefaultReporterOptions 66 70 'dot': BaseOptions 67 71 'tree': BaseOptions