Skip to content

Commit b825ef8

Browse files
authored
fix: make sure test errors always have stacks property in Node.js context (#8392)
1 parent 05b4178 commit b825ef8

File tree

12 files changed

+82
-24
lines changed

12 files changed

+82
-24
lines changed

packages/ui/client/components/views/ViewReport.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ describe('ViewReport', () => {
104104
errors: [
105105
{
106106
name: 'Do some test',
107+
stacks: [],
107108
stack: '\x1B[33mtest/plain-stack-trace.ts\x1B[0m',
108109
message: 'Error: Transform failed with 1 error:',
109110
diff,
@@ -163,6 +164,7 @@ describe('ViewReport', () => {
163164
{
164165
name: 'Do some test',
165166
stack: '\x1B[33mtest/plain-stack-trace.ts\x1B[0m',
167+
stacks: [],
166168
message: '\x1B[44mError: Transform failed with 1 error:\x1B[0m',
167169
diff,
168170
},

packages/ui/client/composables/error.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export function parseError(e: unknown) {
4040
message: String(error).split(/\n/g)[0],
4141
stack: String(error),
4242
name: '',
43+
stacks: [],
4344
}
4445
}
4546

@@ -49,6 +50,7 @@ export function parseError(e: unknown) {
4950
message: err.message,
5051
stack: err.stack,
5152
name: '',
53+
stacks: [],
5254
}
5355
}
5456

packages/utils/src/source-map.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface StackTraceParserOptions {
2323
const CHROME_IE_STACK_REGEXP = /^\s*at .*(?:\S:\d+|\(native\))/m
2424
const SAFARI_NATIVE_CODE_REGEXP = /^(?:eval@)?(?:\[native code\])?$/
2525

26-
const stackIgnorePatterns = [
26+
const stackIgnorePatterns: (string | RegExp)[] = [
2727
'node:internal',
2828
/\/packages\/\w+\/dist\//,
2929
/\/@vitest\/\w+\/dist\//,
@@ -47,6 +47,8 @@ const stackIgnorePatterns = [
4747
/\/deps\/vitest_/,
4848
]
4949

50+
export { stackIgnorePatterns as defaultStackIgnorePatterns }
51+
5052
function extractLocation(urlLike: string) {
5153
// Fail-fast but return locations like "(native)"
5254
if (!urlLike.includes(':')) {

packages/utils/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export interface ParsedStack {
3030

3131
export interface SerializedError {
3232
message: string
33+
stacks?: ParsedStack[]
3334
stack?: string
3435
name?: string
35-
stacks?: ParsedStack[]
3636
cause?: SerializedError
3737
[key: string]: unknown
3838
}

packages/vitest/src/api/setup.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ import type {
1515
WebSocketRPC,
1616
} from './types'
1717
import { existsSync, promises as fs } from 'node:fs'
18-
import { isPrimitive, noop } from '@vitest/utils'
18+
import { noop } from '@vitest/utils'
1919
import { createBirpc } from 'birpc'
2020
import { parse, stringify } from 'flatted'
2121
import { WebSocketServer } from 'ws'
2222
import { API_PATH } from '../constants'
2323
import { getModuleGraph } from '../utils/graph'
2424
import { stringifyReplace } from '../utils/serialization'
25-
import { parseErrorStacktrace } from '../utils/source-map'
2625
import { isValidApiRequest } from './check'
2726

2827
export function setup(ctx: Vitest, _server?: ViteDevServer): void {
@@ -201,25 +200,6 @@ export class WebSocketReporter implements Reporter {
201200
return
202201
}
203202

204-
packs.forEach(([taskId, result]) => {
205-
const task = this.ctx.state.idMap.get(taskId)
206-
const isBrowser = task && task.file.pool === 'browser'
207-
208-
result?.errors?.forEach((error) => {
209-
if (isPrimitive(error)) {
210-
return
211-
}
212-
213-
if (isBrowser) {
214-
const project = this.ctx.getProjectByName(task!.file.projectName || '')
215-
error.stacks = project.browser?.parseErrorStacktrace(error)
216-
}
217-
else {
218-
error.stacks = parseErrorStacktrace(error)
219-
}
220-
})
221-
})
222-
223203
this.clients.forEach((client) => {
224204
client.onTaskUpdate?.(packs, events)?.catch?.(noop)
225205
})

packages/vitest/src/node/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ export class Logger {
256256
)
257257
}
258258

259-
printUnhandledErrors(errors: unknown[]): void {
259+
printUnhandledErrors(errors: ReadonlyArray<unknown>): void {
260260
const errorMessage = c.red(
261261
c.bold(
262262
`\nVitest caught ${errors.length} unhandled error${

packages/vitest/src/node/printError.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { normalize, relative } from 'pathe'
1212
import c from 'tinyrainbow'
1313
import { TypeCheckError } from '../typecheck/typechecker'
1414
import {
15+
defaultStackIgnorePatterns,
1516
lineSplitRE,
1617
parseErrorStacktrace,
1718
positionToOffset,
@@ -75,10 +76,26 @@ export function printError(
7576
screenshotPaths: options.screenshotPaths,
7677
printProperties: options.verbose,
7778
parseErrorStacktrace(error) {
79+
if (error.stacks) {
80+
const stacks = [...error.stacks.filter(stack =>
81+
project.config.onStackTrace?.(error, stack) !== false,
82+
)]
83+
84+
if (options.fullStack) {
85+
return stacks
86+
}
87+
else {
88+
return stacks.filter((stack) => {
89+
return !defaultStackIgnorePatterns.some(p => stack.file.match(p))
90+
})
91+
}
92+
}
93+
7894
// browser stack trace needs to be processed differently,
7995
// so there is a separate method for that
8096
if (options.task?.file.pool === 'browser' && project.browser) {
8197
return project.browser.parseErrorStacktrace(error, {
98+
frameFilter: project.config.onStackTrace,
8299
ignoreStackEntries: options.fullStack ? [] : undefined,
83100
})
84101
}

packages/vitest/src/node/test-run.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import type { TestRunEndReason } from './types/reporter'
1717
import assert from 'node:assert'
1818
import { createHash } from 'node:crypto'
1919
import { copyFile, mkdir } from 'node:fs/promises'
20+
import { isPrimitive } from '@vitest/utils'
2021
import { serializeError } from '@vitest/utils/error'
22+
import { parseErrorStacktrace } from '@vitest/utils/source-map'
2123
import mime from 'mime/lite'
2224
import { basename, dirname, extname, resolve } from 'pathe'
2325

@@ -68,6 +70,7 @@ export class TestRun {
6870
}
6971

7072
async updated(update: TaskResultPack[], events: TaskEventPack[]): Promise<void> {
73+
this.syncUpdateStacks(update)
7174
this.vitest.state.updateTasks(update)
7275

7376
for (const [id, event, data] of events) {
@@ -113,6 +116,27 @@ export class TestRun {
113116
return modules.some(m => !m.ok())
114117
}
115118

119+
private syncUpdateStacks(update: TaskResultPack[]): void {
120+
update.forEach(([taskId, result]) => {
121+
const task = this.vitest.state.idMap.get(taskId)
122+
const isBrowser = task && task.file.pool === 'browser'
123+
124+
result?.errors?.forEach((error) => {
125+
if (isPrimitive(error)) {
126+
return
127+
}
128+
129+
const project = this.vitest.getProjectByName(task!.file.projectName || '')
130+
if (isBrowser) {
131+
error.stacks = project.browser?.parseErrorStacktrace(error, { frameFilter: project.config.onStackTrace }) || []
132+
}
133+
else {
134+
error.stacks = parseErrorStacktrace(error, { frameFilter: project.config.onStackTrace })
135+
}
136+
})
137+
})
138+
}
139+
116140
private async reportEvent(id: string, event: TaskUpdateEvent, data: TaskEventData | undefined) {
117141
const task = this.vitest.state.idMap.get(id)
118142
const entity = task && this.vitest.state.getReportedEntity(task)

packages/vitest/src/utils/source-map.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {
44
positionToOffset,
55
} from '@vitest/utils'
66
export {
7+
defaultStackIgnorePatterns,
78
parseErrorStacktrace,
89
parseSingleStack,
910
parseStacktrace,

test/browser/specs/runner.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,29 @@ error with a stack
170170
})
171171

172172
test(`stack trace points to correct file in every browser when failed`, async () => {
173+
expect.assertions(15)
173174
const { stderr } = await runBrowserTests({
174175
root: './fixtures/failing',
176+
reporters: [
177+
'default',
178+
{
179+
onTestCaseReady(testCase) {
180+
if (testCase.name !== 'correctly fails and prints a diff') {
181+
return
182+
}
183+
if (testCase.project.name === 'chromium' || testCase.project.name === 'chrome') {
184+
expect(testCase.result().errors[0].stacks).toEqual([
185+
{
186+
line: 11,
187+
column: 12,
188+
file: testCase.module.moduleId,
189+
method: '',
190+
},
191+
])
192+
}
193+
},
194+
},
195+
],
175196
})
176197

177198
expect(stderr).toContain('expected 1 to be 2')

0 commit comments

Comments
 (0)