Skip to content

Commit 5eba60a

Browse files
AriPerkkiospamshaker
andauthoredMar 17, 2025··
feat(reporter): always render test time (#7529)
Co-authored-by: Michał Grzegorzewski <4864089+spamshaker@users.noreply.github.com>
1 parent df34770 commit 5eba60a

File tree

6 files changed

+87
-79
lines changed

6 files changed

+87
-79
lines changed
 

Diff for: ‎packages/vitest/src/node/reporters/base.ts

+5-8
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export abstract class BaseReporter implements Reporter {
131131
for (const test of tests) {
132132
const { duration, retryCount, repeatCount } = test.result || {}
133133
const padding = this.getTestIndentation(test)
134-
let suffix = ''
134+
let suffix = this.getDurationPrefix(test)
135135

136136
if (retryCount != null && retryCount > 0) {
137137
suffix += c.yellow(` (retry x${retryCount})`)
@@ -142,7 +142,7 @@ export abstract class BaseReporter implements Reporter {
142142
}
143143

144144
if (test.result?.state === 'fail') {
145-
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test, c.dim(' > '))}${this.getDurationPrefix(test)}`) + suffix)
145+
this.log(c.red(` ${padding}${taskFail} ${this.getTestName(test, c.dim(' > '))}`) + suffix)
146146

147147
// print short errors, full errors will be at the end in summary
148148
test.result?.errors?.forEach((error) => {
@@ -156,10 +156,7 @@ export abstract class BaseReporter implements Reporter {
156156

157157
// also print slow tests
158158
else if (duration && duration > this.ctx.config.slowTestThreshold) {
159-
this.log(
160-
` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test, c.dim(' > '))}`
161-
+ ` ${c.yellow(Math.round(duration) + c.dim('ms'))}${suffix}`,
162-
)
159+
this.log(` ${padding}${c.yellow(c.dim(F_CHECK))} ${this.getTestName(test, c.dim(' > '))} ${suffix}`)
163160
}
164161

165162
else if (this.ctx.config.hideSkippedTests && (test.mode === 'skip' || test.result?.state === 'skip')) {
@@ -194,14 +191,14 @@ export abstract class BaseReporter implements Reporter {
194191
return ' '
195192
}
196193

197-
private getDurationPrefix(task: Task) {
194+
protected getDurationPrefix(task: Task): string {
198195
if (!task.result?.duration) {
199196
return ''
200197
}
201198

202199
const color = task.result.duration > this.ctx.config.slowTestThreshold
203200
? c.yellow
204-
: c.gray
201+
: c.green
205202

206203
return color(` ${Math.round(task.result.duration)}${c.dim('ms')}`)
207204
}

Diff for: ‎packages/vitest/src/node/reporters/benchmark/tableRender.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,10 @@ export function renderTable(
137137
suffix += c.dim(c.gray(' [skipped]'))
138138
}
139139

140-
if (duration != null && duration > options.slowTestThreshold) {
141-
suffix += c.yellow(` ${Math.round(duration)}${c.dim('ms')}`)
140+
if (duration != null) {
141+
const color = duration > options.slowTestThreshold ? c.yellow : c.green
142+
143+
suffix += color(` ${Math.round(duration)}${c.dim('ms')}`)
142144
}
143145

144146
if (options.showHeap && task.result?.heap != null) {

Diff for: ‎packages/vitest/src/node/reporters/verbose.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,14 @@ export class VerboseReporter extends DefaultReporter {
1818
return
1919
}
2020

21-
const duration = task.result.duration
2221
let title = ` ${getStateSymbol(task)} `
2322

2423
if (task.file.projectName) {
2524
title += formatProjectName(task.file.projectName)
2625
}
2726

2827
title += getFullName(task, c.dim(' > '))
29-
30-
if (duration != null && duration > this.ctx.config.slowTestThreshold) {
31-
title += c.yellow(` ${Math.round(duration)}${c.dim('ms')}`)
32-
}
28+
title += super.getDurationPrefix(task)
3329

3430
if (this.ctx.config.logHeapUsage && task.result.heap != null) {
3531
title += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)

Diff for: ‎test/reporters/tests/default.test.ts

+37-31
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,37 @@ describe('default reporter', async () => {
2222
},
2323
})
2424

25-
const rows = stdout.replace(/\d+ms/g, '[...]ms').split('\n')
26-
rows.splice(0, rows.findIndex(row => row.includes('b1.test.ts')))
27-
rows.splice(rows.findIndex(row => row.includes('Test Files')))
28-
29-
expect(rows.join('\n').trim()).toMatchInlineSnapshot(`
25+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
3026
"❯ b1.test.ts (13 tests | 1 failed) [...]ms
31-
✓ b1 passed > b1 test
32-
✓ b1 passed > b2 test
33-
✓ b1 passed > b3 test
34-
✓ b1 passed > nested b > nested b1 test
35-
✓ b1 passed > nested b > nested b2 test
36-
✓ b1 passed > nested b > nested b3 test
37-
✓ b1 failed > b1 test
38-
✓ b1 failed > b2 test
39-
✓ b1 failed > b3 test
27+
✓ b1 passed > b1 test [...]ms
28+
✓ b1 passed > b2 test [...]ms
29+
✓ b1 passed > b3 test [...]ms
30+
✓ b1 passed > nested b > nested b1 test [...]ms
31+
✓ b1 passed > nested b > nested b2 test [...]ms
32+
✓ b1 passed > nested b > nested b3 test [...]ms
33+
✓ b1 failed > b1 test [...]ms
34+
✓ b1 failed > b2 test [...]ms
35+
✓ b1 failed > b3 test [...]ms
4036
× b1 failed > b failed test [...]ms
4137
→ expected 1 to be 2 // Object.is equality
42-
✓ b1 failed > nested b > nested b1 test
43-
✓ b1 failed > nested b > nested b2 test
44-
✓ b1 failed > nested b > nested b3 test
38+
✓ b1 failed > nested b > nested b1 test [...]ms
39+
✓ b1 failed > nested b > nested b2 test [...]ms
40+
✓ b1 failed > nested b > nested b3 test [...]ms
4541
❯ b2.test.ts (13 tests | 1 failed) [...]ms
46-
✓ b2 passed > b1 test
47-
✓ b2 passed > b2 test
48-
✓ b2 passed > b3 test
49-
✓ b2 passed > nested b > nested b1 test
50-
✓ b2 passed > nested b > nested b2 test
51-
✓ b2 passed > nested b > nested b3 test
52-
✓ b2 failed > b1 test
53-
✓ b2 failed > b2 test
54-
✓ b2 failed > b3 test
42+
✓ b2 passed > b1 test [...]ms
43+
✓ b2 passed > b2 test [...]ms
44+
✓ b2 passed > b3 test [...]ms
45+
✓ b2 passed > nested b > nested b1 test [...]ms
46+
✓ b2 passed > nested b > nested b2 test [...]ms
47+
✓ b2 passed > nested b > nested b3 test [...]ms
48+
✓ b2 failed > b1 test [...]ms
49+
✓ b2 failed > b2 test [...]ms
50+
✓ b2 failed > b3 test [...]ms
5551
× b2 failed > b failed test [...]ms
5652
→ expected 1 to be 2 // Object.is equality
57-
✓ b2 failed > nested b > nested b1 test
58-
✓ b2 failed > nested b > nested b2 test
59-
✓ b2 failed > nested b > nested b3 test"
53+
✓ b2 failed > nested b > nested b1 test [...]ms
54+
✓ b2 failed > nested b > nested b2 test [...]ms
55+
✓ b2 failed > nested b > nested b3 test [...]ms"
6056
`)
6157
})
6258

@@ -164,7 +160,7 @@ describe('default reporter', async () => {
164160
})
165161

166162
expect(stdout).toContain('1 passed')
167-
expect(stdout).toContain('✓ pass after retries (retry x3)')
163+
expect(trimReporterOutput(stdout)).toContain('✓ pass after retries [...]ms (retry x3)')
168164
})
169165

170166
test('prints repeat count', async () => {
@@ -175,7 +171,7 @@ describe('default reporter', async () => {
175171
})
176172

177173
expect(stdout).toContain('1 passed')
178-
expect(stdout).toContain('✓ repeat couple of times (repeat x3)')
174+
expect(trimReporterOutput(stdout)).toContain('✓ repeat couple of times [...]ms (repeat x3)')
179175
})
180176

181177
test('prints 0-based index and 1-based index of the test case', async () => {
@@ -194,3 +190,13 @@ describe('default reporter', async () => {
194190
expect(stdout).toContain('✓ passed > 1-based index of the test case is 3')
195191
})
196192
}, 120000)
193+
194+
function trimReporterOutput(report: string) {
195+
const rows = report.replace(/\d+ms/g, '[...]ms').split('\n')
196+
197+
// Trim start and end, capture just rendered tree
198+
rows.splice(0, 1 + rows.findIndex(row => row.includes('RUN v')))
199+
rows.splice(rows.findIndex(row => row.includes('Test Files')))
200+
201+
return rows.join('\n').trim()
202+
}

Diff for: ‎test/reporters/tests/merge-reports.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ test('merge reports', async () => {
9191
test 1-2
9292
9393
❯ first.test.ts (2 tests | 1 failed) <time>
94-
✓ test 1-1
94+
✓ test 1-1 <time>
9595
× test 1-2 <time>
9696
→ expected 1 to be 2 // Object.is equality
9797
stdout | second.test.ts > test 2-1
@@ -100,8 +100,8 @@ test('merge reports', async () => {
100100
❯ second.test.ts (3 tests | 1 failed) <time>
101101
× test 2-1 <time>
102102
→ expected 1 to be 2 // Object.is equality
103-
✓ group > test 2-2
104-
✓ group > test 2-3
103+
✓ group > test 2-2 <time>
104+
✓ group > test 2-3 <time>
105105
106106
Test Files 2 failed (2)
107107
Tests 2 failed | 3 passed (5)

Diff for: ‎test/reporters/tests/verbose.test.ts

+37-30
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ test('duration', async () => {
99
env: { CI: '1' },
1010
})
1111

12-
expect(trimReporterOutput(stdout)).toContain(`
13-
✓ basic.test.ts > fast
14-
✓ basic.test.ts > slow [...]ms`,
15-
)
12+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
13+
"✓ basic.test.ts > fast [...]ms
14+
✓ basic.test.ts > slow [...]ms"
15+
`)
1616
})
1717

1818
test('prints error properties', async () => {
@@ -32,9 +32,11 @@ test('prints skipped tests by default', async () => {
3232
config: false,
3333
})
3434

35-
expect(stdout).toContain('✓ fixtures/all-passing-or-skipped.test.ts (2 tests | 1 skipped)')
36-
expect(stdout).toContain('✓ 2 + 3 = 5')
37-
expect(stdout).toContain('↓ 3 + 3 = 6')
35+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
36+
"✓ fixtures/all-passing-or-skipped.test.ts (2 tests | 1 skipped) [...]ms
37+
✓ 2 + 3 = 5 [...]ms
38+
↓ 3 + 3 = 6"
39+
`)
3840
})
3941

4042
test('hides skipped tests when --hideSkippedTests', async () => {
@@ -45,9 +47,10 @@ test('hides skipped tests when --hideSkippedTests', async () => {
4547
config: false,
4648
})
4749

48-
expect(stdout).toContain('✓ fixtures/all-passing-or-skipped.test.ts (2 tests | 1 skipped)')
49-
expect(stdout).toContain('✓ 2 + 3 = 5')
50-
expect(stdout).not.toContain('↓ 3 + 3 = 6')
50+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
51+
"✓ fixtures/all-passing-or-skipped.test.ts (2 tests | 1 skipped) [...]ms
52+
✓ 2 + 3 = 5 [...]ms"
53+
`)
5154
})
5255

5356
test('prints retry count', async () => {
@@ -58,8 +61,10 @@ test('prints retry count', async () => {
5861
config: false,
5962
})
6063

61-
expect(stdout).toContain('1 passed')
62-
expect(stdout).toContain('✓ pass after retries (retry x3)')
64+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
65+
"✓ fixtures/retry.test.ts (1 test) [...]ms
66+
✓ pass after retries [...]ms (retry x3)"
67+
`)
6368
})
6469

6570
test('prints repeat count', async () => {
@@ -69,8 +74,10 @@ test('prints repeat count', async () => {
6974
config: false,
7075
})
7176

72-
expect(stdout).toContain('1 passed')
73-
expect(stdout).toContain('✓ repeat couple of times (repeat x3)')
77+
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
78+
"✓ fixtures/repeats.test.ts (1 test) [...]ms
79+
✓ repeat couple of times [...]ms (repeat x3)"
80+
`)
7481
})
7582

7683
test('renders tree when in TTY', async () => {
@@ -94,14 +101,14 @@ test('renders tree when in TTY', async () => {
94101

95102
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
96103
"❯ fixtures/verbose/example-1.test.ts (10 tests | 1 failed | 4 skipped) [...]ms
97-
✓ test pass in root
104+
✓ test pass in root [...]ms
98105
↓ test skip in root
99106
❯ suite in root (5)
100-
✓ test pass in 1. suite #1
101-
✓ test pass in 1. suite #2
107+
✓ test pass in 1. suite #1 [...]ms
108+
✓ test pass in 1. suite #2 [...]ms
102109
❯ suite in suite (3)
103-
✓ test pass in nested suite #1
104-
✓ test pass in nested suite #2
110+
✓ test pass in nested suite #1 [...]ms
111+
✓ test pass in nested suite #2 [...]ms
105112
❯ suite in nested suite (1)
106113
× test failure in 2x nested suite [...]ms
107114
↓ suite skip in root (3)
@@ -110,10 +117,10 @@ test('renders tree when in TTY', async () => {
110117
↓ test in nested suite
111118
↓ test failure in nested suite of skipped suite
112119
✓ fixtures/verbose/example-2.test.ts (3 tests | 1 skipped) [...]ms
113-
✓ test 0.1
120+
✓ test 0.1 [...]ms
114121
↓ test 0.2
115122
✓ suite 1.1 (1)
116-
✓ test 1.1"
123+
✓ test 1.1 [...]ms"
117124
`)
118125
})
119126

@@ -137,23 +144,23 @@ test('does not render tree when in non-TTY', async () => {
137144
})
138145

139146
expect(trimReporterOutput(stdout)).toMatchInlineSnapshot(`
140-
"✓ fixtures/verbose/example-1.test.ts > test pass in root
141-
✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #1
142-
✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #2
143-
✓ fixtures/verbose/example-1.test.ts > suite in root > suite in suite > test pass in nested suite #1
144-
✓ fixtures/verbose/example-1.test.ts > suite in root > suite in suite > test pass in nested suite #2
145-
× fixtures/verbose/example-1.test.ts > suite in root > suite in suite > suite in nested suite > test failure in 2x nested suite
147+
"✓ fixtures/verbose/example-1.test.ts > test pass in root [...]ms
148+
✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #1 [...]ms
149+
✓ fixtures/verbose/example-1.test.ts > suite in root > test pass in 1. suite #2 [...]ms
150+
✓ fixtures/verbose/example-1.test.ts > suite in root > suite in suite > test pass in nested suite #1 [...]ms
151+
✓ fixtures/verbose/example-1.test.ts > suite in root > suite in suite > test pass in nested suite #2 [...]ms
152+
× fixtures/verbose/example-1.test.ts > suite in root > suite in suite > suite in nested suite > test failure in 2x nested suite [...]ms
146153
→ expected 'should fail' to be 'as expected' // Object.is equality
147-
✓ fixtures/verbose/example-2.test.ts > test 0.1
148-
✓ fixtures/verbose/example-2.test.ts > suite 1.1 > test 1.1"
154+
✓ fixtures/verbose/example-2.test.ts > test 0.1 [...]ms
155+
✓ fixtures/verbose/example-2.test.ts > suite 1.1 > test 1.1 [...]ms"
149156
`)
150157
})
151158

152159
function trimReporterOutput(report: string) {
153160
const rows = report.replace(/\d+ms/g, '[...]ms').split('\n')
154161

155162
// Trim start and end, capture just rendered tree
156-
rows.splice(0, rows.findIndex(row => row.includes('fixtures/verbose/example-')))
163+
rows.splice(0, 1 + rows.findIndex(row => row.includes('RUN v')))
157164
rows.splice(rows.findIndex(row => row.includes('Test Files')))
158165

159166
return rows.join('\n').trim()

0 commit comments

Comments
 (0)
Please sign in to comment.