Skip to content

Commit 1da6ceb

Browse files
authored
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

Lines changed: 10 additions & 16 deletions
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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',
Lines changed: 11 additions & 0 deletions
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+
})
Lines changed: 18 additions & 0 deletions
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

Lines changed: 15 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy