Skip to content

Commit 1da6ceb

Browse files
authoredJul 31, 2024··
fix(browser): print correct stack trace for unhandled errors (#6134)
1 parent c51c67a commit 1da6ceb

File tree

6 files changed

+60
-17
lines changed

6 files changed

+60
-17
lines changed
 

‎packages/browser/src/client/public/error-catcher.js

+10-16
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import { channel, client } from '@vitest/browser/client'
22

3-
function on(event, listener) {
4-
window.addEventListener(event, listener)
5-
return () => window.removeEventListener(event, listener)
6-
}
7-
83
function serializeError(unhandledError) {
94
if (typeof unhandledError !== 'object' || !unhandledError) {
105
return {
@@ -19,41 +14,40 @@ function serializeError(unhandledError) {
1914
}
2015
}
2116

22-
function catchWindowErrors(cb) {
17+
function catchWindowErrors(errorEvent, prop, cb) {
2318
let userErrorListenerCount = 0
2419
function throwUnhandlerError(e) {
25-
if (userErrorListenerCount === 0 && e.error != null) {
20+
if (userErrorListenerCount === 0 && e[prop] != null) {
2621
cb(e)
2722
}
2823
else {
29-
console.error(e.error)
24+
console.error(e[prop])
3025
}
3126
}
3227
const addEventListener = window.addEventListener.bind(window)
3328
const removeEventListener = window.removeEventListener.bind(window)
34-
window.addEventListener('error', throwUnhandlerError)
29+
window.addEventListener(errorEvent, throwUnhandlerError)
3530
window.addEventListener = function (...args) {
36-
if (args[0] === 'error') {
31+
if (args[0] === errorEvent) {
3732
userErrorListenerCount++
3833
}
3934
return addEventListener.apply(this, args)
4035
}
4136
window.removeEventListener = function (...args) {
42-
if (args[0] === 'error' && userErrorListenerCount) {
37+
if (args[0] === errorEvent && userErrorListenerCount) {
4338
userErrorListenerCount--
4439
}
4540
return removeEventListener.apply(this, args)
4641
}
4742
return function clearErrorHandlers() {
48-
window.removeEventListener('error', throwUnhandlerError)
43+
window.removeEventListener(errorEvent, throwUnhandlerError)
4944
}
5045
}
5146

5247
function registerUnexpectedErrors() {
53-
catchWindowErrors(event =>
54-
reportUnexpectedError('Error', event.error),
55-
)
56-
on('unhandledrejection', event =>
48+
catchWindowErrors('error', 'error', event =>
49+
reportUnexpectedError('Error', event.error))
50+
catchWindowErrors('unhandledrejection', 'reason', event =>
5751
reportUnexpectedError('Unhandled Rejection', event.reason))
5852
}
5953

‎packages/browser/src/node/rpc.ts

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { WebSocket } from 'ws'
66
import { WebSocketServer } from 'ws'
77
import type { BrowserCommandContext } from 'vitest/node'
88
import { createDebugger, isFileServingAllowed } from 'vitest/node'
9+
import type { ErrorWithDiff } from 'vitest'
910
import type { WebSocketBrowserEvents, WebSocketBrowserHandlers } from './types'
1011
import type { BrowserServer } from './server'
1112
import { cleanUrl, resolveMock } from './resolveMock'
@@ -67,6 +68,10 @@ export function setupBrowserRpc(
6768
const rpc = createBirpc<WebSocketBrowserEvents, WebSocketBrowserHandlers>(
6869
{
6970
async onUnhandledError(error, type) {
71+
if (error && typeof error === 'object') {
72+
const _error = error as ErrorWithDiff
73+
_error.stacks = server.parseErrorStacktrace(_error)
74+
}
7075
ctx.state.catchError(error, type)
7176
},
7277
async onCollected(files) {

‎packages/browser/src/node/server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class BrowserServer implements IBrowserServer {
8787
resolve(distRoot, 'client/esm-client-injector.js'),
8888
'utf8',
8989
).then(js => (this.injectorJs = js))
90-
this.errorCatcherPath = resolve(distRoot, 'client/error-catcher.js')
90+
this.errorCatcherPath = join('/@fs/', resolve(distRoot, 'client/error-catcher.js'))
9191
this.stateJs = readFile(
9292
resolve(distRoot, 'state.js'),
9393
'utf-8',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { test } from 'vitest';
2+
3+
interface _Unused {
4+
_fake: never
5+
}
6+
7+
test('unhandled exception', () => {
8+
;(async () => {
9+
throw new Error('custom_unhandled_error')
10+
})()
11+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { fileURLToPath } from 'node:url'
2+
import { defineConfig } from 'vitest/config'
3+
4+
const provider = process.env.PROVIDER || 'playwright'
5+
const name =
6+
process.env.BROWSER || (provider === 'playwright' ? 'chromium' : 'chrome')
7+
8+
export default defineConfig({
9+
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
10+
test: {
11+
browser: {
12+
enabled: true,
13+
provider,
14+
name,
15+
headless: true,
16+
},
17+
},
18+
})

‎test/browser/specs/unhandled.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { expect, test } from 'vitest'
2+
import { runBrowserTests } from './utils'
3+
4+
test('prints correct unhandled error stack', async () => {
5+
const { stderr, browser } = await runBrowserTests({
6+
root: './fixtures/unhandled',
7+
})
8+
9+
if (browser === 'webkit') {
10+
expect(stderr).toContain('throw-unhandled-error.test.ts:9:20')
11+
}
12+
else {
13+
expect(stderr).toContain('throw-unhandled-error.test.ts:9:10')
14+
}
15+
})

0 commit comments

Comments
 (0)
Please sign in to comment.