diff --git a/packages/browser/src/client/public/error-catcher.js b/packages/browser/src/client/public/error-catcher.js index e38b3f3feae9..e5900b39fefa 100644 --- a/packages/browser/src/client/public/error-catcher.js +++ b/packages/browser/src/client/public/error-catcher.js @@ -31,7 +31,11 @@ function catchWindowErrors(errorEvent, prop, cb) { cb(e) } else { - console.error(e[prop]) + // `ErrorEvent` doesn't necessary have `ErrotEvent.error` defined + // but some has `ErrorEvent.message` defined, e.g. ResizeObserver error. + // https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent/error + // https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver#observation_errors + console.error(e.message ? new Error(e.message) : e) } } const addEventListener = window.addEventListener.bind(window) diff --git a/test/browser/fixtures/unhandled-non-error/basic.test.ts b/test/browser/fixtures/unhandled-non-error/basic.test.ts new file mode 100644 index 000000000000..96fa2baf3425 --- /dev/null +++ b/test/browser/fixtures/unhandled-non-error/basic.test.ts @@ -0,0 +1,24 @@ +import { test } from "vitest"; + +test("ResizeObserver error", async () => { + const divElem = document.createElement("div"); + divElem.style.width = "100px"; + divElem.style.height = "100px"; + document.body.appendChild(divElem); + + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + (entry.target as HTMLElement).style.width = + `${entry.contentBoxSize[0].inlineSize + 10}px`; + } + }); + const promise = Promise.withResolvers(); + window.addEventListener("error", (event) => { + if (event.message.includes("ResizeObserver loop")) { + promise.resolve(null); + } + }); + resizeObserver.observe(divElem); + await promise.promise; + resizeObserver.unobserve(divElem); +}); diff --git a/test/browser/fixtures/unhandled-non-error/vitest.config.ts b/test/browser/fixtures/unhandled-non-error/vitest.config.ts new file mode 100644 index 000000000000..c98221e84305 --- /dev/null +++ b/test/browser/fixtures/unhandled-non-error/vitest.config.ts @@ -0,0 +1,15 @@ +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitest/config' +import { instances, provider } from '../../settings' + +export default defineConfig({ + cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)), + test: { + browser: { + enabled: true, + provider, + instances, + headless: true, + }, + }, +}) diff --git a/test/browser/settings.ts b/test/browser/settings.ts index a23a43185ce2..94232133748a 100644 --- a/test/browser/settings.ts +++ b/test/browser/settings.ts @@ -20,7 +20,9 @@ const devInstances: BrowserInstanceOption[] = [ const playwrightInstances: BrowserInstanceOption[] = [ { browser: 'chromium' }, { browser: 'firefox' }, - { browser: 'webkit' }, + // hard to setup playwright webkit on some machines (e.g. ArchLinux) + // this allows skipping it locally by BROWSER_NO_WEBKIT=true + ...(process.env.BROWSER_NO_WEBKIT ? [] : [{ browser: 'webkit' as const }]), ] const webdriverioInstances: BrowserInstanceOption[] = [ diff --git a/test/browser/specs/unhandled.test.ts b/test/browser/specs/unhandled.test.ts index 79aeaf0e98a1..3168bf524806 100644 --- a/test/browser/specs/unhandled.test.ts +++ b/test/browser/specs/unhandled.test.ts @@ -37,3 +37,17 @@ test('disables tracking', async () => { }) expect(stderr).toBe('') }) + +test('print unhandled non error', async () => { + const { testTree, stderr } = await runBrowserTests({ + root: './fixtures/unhandled-non-error', + }) + expect(stderr).toContain('[Error: ResizeObserver loop completed with undelivered notifications.]') + expect(testTree()).toMatchInlineSnapshot(` + { + "basic.test.ts": { + "ResizeObserver error": "passed", + }, + } + `) +})