Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reporter): --silent=passed-only to log failed tasks only #7530

Merged
merged 2 commits into from
Mar 17, 2025
Merged
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
6 changes: 4 additions & 2 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1089,11 +1089,13 @@ Default timeout to wait for close when Vitest shuts down, in milliseconds

### silent<NonProjectOption />

- **Type:** `boolean`
- **Type:** `boolean | 'passed-only'`
- **Default:** `false`
- **CLI:** `--silent`, `--silent=false`

Silent console output from tests
Silent console output from tests.

Use `'passed-only'` to see logs from failing tests only. Logs from failing tests are printed after a test has finished.

### setupFiles

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/cli-generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ Set to true to exit if port is already in use, instead of automatically trying t

### silent

- **CLI:** `--silent`
- **CLI:** `--silent [value]`
- **Config:** [silent](/config/#silent)

Silent console output from tests
Silent console output from tests. Use `'passed-only'` to see logs from failing tests only.

### hideSkippedTests

Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ export const cliOptionsConfig: VitestCLIOptions = {
subcommands: apiConfig(defaultPort),
},
silent: {
description: 'Silent console output from tests',
description: 'Silent console output from tests. Use `\'passed-only\'` to see logs from failing tests only.',
argument: '[value]',
},
hideSkippedTests: {
description: 'Hide logs for skipped tests',
Expand Down
40 changes: 36 additions & 4 deletions packages/vitest/src/node/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { File, Task, TaskResultPack } from '@vitest/runner'
import type { ErrorWithDiff, UserConsoleLog } from '../../types/general'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
import type { TestCase, TestModule, TestResult, TestSuite } from './reported-tasks'
import { performance } from 'node:perf_hooks'
import { getFullName, getSuites, getTestName, getTests, hasFailed } from '@vitest/runner/utils'
import { toArray } from '@vitest/utils'
Expand Down Expand Up @@ -66,6 +67,32 @@ export abstract class BaseReporter implements Reporter {
}
}

onTestCaseResult(testCase: TestCase): void {
if (testCase.result().state === 'failed') {
this.logFailedTask(testCase.task)
}
}

onTestSuiteResult(testSuite: TestSuite): void {
if (testSuite.state() === 'failed') {
this.logFailedTask(testSuite.task)
}
}

onTestModuleEnd(testModule: TestModule): void {
if (testModule.state() === 'failed') {
this.logFailedTask(testModule.task)
}
}

private logFailedTask(task: Task) {
if (this.ctx.config.silent === 'passed-only') {
for (const log of task.logs || []) {
this.onUserConsoleLog(log, 'failed')
}
}
}

onTaskUpdate(packs: TaskResultPack[]): void {
for (const pack of packs) {
const task = this.ctx.state.idMap.get(pack[0])
Expand Down Expand Up @@ -275,8 +302,8 @@ export abstract class BaseReporter implements Reporter {
this.start = performance.now()
}

onUserConsoleLog(log: UserConsoleLog): void {
if (!this.shouldLog(log)) {
onUserConsoleLog(log: UserConsoleLog, taskState?: TestResult['state']): void {
if (!this.shouldLog(log, taskState)) {
return
}

Expand Down Expand Up @@ -337,10 +364,15 @@ export abstract class BaseReporter implements Reporter {
this.log(c.yellow('Test removed...') + (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ''))
}

shouldLog(log: UserConsoleLog): boolean {
if (this.ctx.config.silent) {
shouldLog(log: UserConsoleLog, taskState?: TestResult['state']): boolean {
if (this.ctx.config.silent === true) {
return false
}

if (this.ctx.config.silent === 'passed-only' && taskState !== 'failed') {
return false
}

const shouldLog = this.ctx.config.onConsoleLog?.(log.content, log.type)
if (shouldLog === false) {
return shouldLog
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/src/node/reporters/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class DefaultReporter extends BaseReporter {
}

onTestModuleEnd(module: TestModule): void {
super.onTestModuleEnd(module)
this.summary?.onTestModuleEnd(module)
}

Expand All @@ -50,6 +51,7 @@ export class DefaultReporter extends BaseReporter {
}

onTestCaseResult(test: TestCase): void {
super.onTestCaseResult(test)
this.summary?.onTestCaseResult(test)
}

Expand Down
6 changes: 5 additions & 1 deletion packages/vitest/src/node/reporters/dot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,16 @@ export class DotReporter extends BaseReporter {
}

onTestCaseResult(test: TestCase): void {
super.onTestCaseResult(test)

this.finishedTests.add(test.id)
this.tests.set(test.id, test.result().state || 'skipped')
this.renderer?.schedule()
}

onTestModuleEnd(): void {
onTestModuleEnd(testModule: TestModule): void {
super.onTestModuleEnd(testModule)

if (!this.isTTY) {
return
}
Expand Down
4 changes: 3 additions & 1 deletion packages/vitest/src/node/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,11 @@ export interface InlineConfig {
/**
* Silent mode
*
* Use `'passed-only'` to see logs from failing tests only.
*
* @default false
*/
silent?: boolean
silent?: boolean | 'passed-only'

/**
* Hide logs for skipped tests
Expand Down
37 changes: 37 additions & 0 deletions test/reporters/fixtures/console-some-failing.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { beforeAll, describe, expect, test } from "vitest"

console.log("Log from failed file")

test("passed test #1", () => {
console.log("Log from passed test")
})

test("failed test #1", () => {
console.log("Log from failed test")
expect(1).toBe(2)
})

describe("failed suite #1", () => {
beforeAll(() => {
console.log("Log from failed suite")
})

test("passed test #2", () => {
console.log("Log from passed test")
})

test("failed test #2", () => {
console.log("Log from failed test")
expect(2).toBe(3)
})
})

describe("passed suite #2", () => {
beforeAll(() => {
console.log("Log from passed suite")
})

test("passed test #3", () => {
console.log("Log from passed test")
})
})
3 changes: 1 addition & 2 deletions test/reporters/tests/github-actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { runVitest } from '../../test-utils'

test(GithubActionsReporter, async () => {
let { stdout, stderr } = await runVitest(
{ reporters: new GithubActionsReporter(), root: './fixtures' },
['some-failing.test.ts'],
{ reporters: new GithubActionsReporter(), root: './fixtures', include: ['**/some-failing.test.ts'] },
)
stdout = stdout.replace(resolve(__dirname, '..').replace(/:/g, '%3A'), '__TEST_DIR__')
expect(stdout).toMatchInlineSnapshot(`
Expand Down
9 changes: 5 additions & 4 deletions test/reporters/tests/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('json reporter', async () => {
const { stdout } = await runVitest({
reporters: 'json',
root,
include: ['**/json-fail-import.test.ts', '**/json-fail.test.ts'],
includeTaskLocation: true,
}, ['json-fail'])

Expand Down Expand Up @@ -126,11 +127,11 @@ describe('json reporter', async () => {
})

it.each([
['passed', 'all-passing-or-skipped'],
['passed', 'all-skipped'],
['failed', 'some-failing'],
['passed', 'all-passing-or-skipped.test.ts'],
['passed', 'all-skipped.test.ts'],
['failed', 'some-failing.test.ts'],
])('resolves to "%s" status for test file "%s"', async (expected, file) => {
const { stdout } = await runVitest({ reporters: 'json', root }, [file])
const { stdout } = await runVitest({ reporters: 'json', root, include: [`**/${file}`] })

const data = JSON.parse(stdout)

Expand Down
105 changes: 105 additions & 0 deletions test/reporters/tests/silent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { expect, test } from 'vitest'
import { DefaultReporter } from 'vitest/reporters'
import { runVitest } from '../../test-utils'

test('{ silent: true } hides all console logs', async () => {
const { stdout } = await runVitest({
include: ['./fixtures/console-some-failing.test.ts'],
silent: true,
reporters: [new LogReporter()],
})

expect(stdout).not.toContain('stdout')
expect(stdout).not.toContain('Log from')
expect(stdout).toContain('Test Files 1 failed | 1 passed')
})

test('default value of silence shows all console logs', async () => {
const { stdout } = await runVitest({
include: ['./fixtures/console-some-failing.test.ts'],
reporters: [new LogReporter()],
})

expect(stdout.match(/stdout/g)).toHaveLength(8)

expect(stdout).toContain(`\
stdout | fixtures/console-some-failing.test.ts
Log from failed file

stdout | fixtures/console-some-failing.test.ts > passed test #1
Log from passed test

stdout | fixtures/console-some-failing.test.ts > failed test #1
Log from failed test

stdout | fixtures/console-some-failing.test.ts > failed suite #1
Log from failed suite

stdout | fixtures/console-some-failing.test.ts > failed suite #1 > passed test #2
Log from passed test

stdout | fixtures/console-some-failing.test.ts > failed suite #1 > failed test #2
Log from failed test

stdout | fixtures/console-some-failing.test.ts > passed suite #2
Log from passed suite

stdout | fixtures/console-some-failing.test.ts > passed suite #2 > passed test #3
Log from passed test`,
)
})

test('{ silent: "passed-only" } shows all console logs from failed tests only', async () => {
const { stdout } = await runVitest({
include: ['./fixtures/console-some-failing.test.ts'],
silent: 'passed-only',
reporters: [new LogReporter()],
})

expect(stdout).toContain(`\
stdout | fixtures/console-some-failing.test.ts > failed test #1
Log from failed test

stdout | fixtures/console-some-failing.test.ts > failed suite #1 > failed test #2
Log from failed test

stdout | fixtures/console-some-failing.test.ts > failed suite #1
Log from failed suite

stdout | fixtures/console-some-failing.test.ts
Log from failed file`,
)

expect(stdout).not.toContain('Log from passed')
expect(stdout.match(/stdout/g)).toHaveLength(4)
})

test('{ silent: "passed-only" } logs are filtered by custom onConsoleLog', async () => {
const { stdout } = await runVitest({
include: ['./fixtures/console-some-failing.test.ts'],
silent: 'passed-only',
onConsoleLog(log) {
if (log.includes('suite')) {
return true
}

return false
},
reporters: [new LogReporter()],
})

expect(stdout).toContain(`\
stdout | fixtures/console-some-failing.test.ts > failed suite #1
Log from failed suite`,
)

expect(stdout).not.toContain('Log from passed')
expect(stdout).not.toContain('Log from failed test')
expect(stdout).not.toContain('Log from failed file')
expect(stdout.match(/stdout/g)).toHaveLength(1)
})

class LogReporter extends DefaultReporter {
isTTY = true
onTaskUpdate() {}
}