From 73f87620c473260e7afd5fb623464da0a0999b81 Mon Sep 17 00:00:00 2001 From: john hight Date: Fri, 5 Jun 2026 10:48:22 -0700 Subject: [PATCH 1/2] fix(firefox): guard against missing location in uncaughtError protocol event Firefox can omit the location field in Page.uncaughtError events when an uncaught error fires during mid-navigation (e.g. interrupted by a WAF challenge). Reading pageError.location.url unconditionally crashes the driver process. - Make uncaughtErrorPayload.location optional in protocol.d.ts - Widen PageError.location and addPageError() to accept undefined - Use optional-chain + empty-string/zero fallbacks in the dispatcher and tracing recorder (consistent with Chromium's stackTraceToLocation) Fixes: https://github.com/microsoft/playwright/issues/41169 --- .../dispatchers/browserContextDispatcher.ts | 6 +++--- .../src/server/firefox/protocol.d.ts | 2 +- packages/playwright-core/src/server/page.ts | 4 ++-- .../src/server/trace/recorder/tracing.ts | 6 +++--- tests/page/page-event-pageerror.spec.ts | 15 +++++++++++++++ 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index e0763acdf7809..66c5d278dac7c 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -114,9 +114,9 @@ export class BrowserContextDispatcher extends Dispatcher { @@ -432,7 +432,7 @@ export class Page extends SdkObject { return marked === -1 ? this._consoleMessages : this._consoleMessages.slice(marked + 1); } - addPageError(error: Error, location: types.ConsoleMessageLocation) { + addPageError(error: Error, location: types.ConsoleMessageLocation | undefined) { const pageError: PageError = { error, location }; this._pageErrors.push(pageError); ensureArrayLimit(this._pageErrors, 200); // Avoid unbounded memory growth. diff --git a/packages/playwright-core/src/server/trace/recorder/tracing.ts b/packages/playwright-core/src/server/trace/recorder/tracing.ts index 5351b2c2b25e5..80709b3c91852 100644 --- a/packages/playwright-core/src/server/trace/recorder/tracing.ts +++ b/packages/playwright-core/src/server/trace/recorder/tracing.ts @@ -643,9 +643,9 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps params: { error: serializeError(pageError.error), location: { - url: pageError.location.url, - line: pageError.location.lineNumber, - column: pageError.location.columnNumber, + url: pageError.location?.url ?? '', + line: pageError.location?.lineNumber ?? 0, + column: pageError.location?.columnNumber ?? 0, }, }, pageId: page.guid, diff --git a/tests/page/page-event-pageerror.spec.ts b/tests/page/page-event-pageerror.spec.ts index 116805b63f515..84c33c1f48df4 100644 --- a/tests/page/page-event-pageerror.spec.ts +++ b/tests/page/page-event-pageerror.spec.ts @@ -230,3 +230,18 @@ it('should fire illegal character error', { else expect(error.message).toContain('illegal character'); }); + +it('should emit weberror on context when pageerror fires on firefox', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41169' }, +}, async ({ page, browserName }) => { + // Regression guard: Firefox may omit location in Page.uncaughtError protocol events + // (e.g. during navigation interruption). The driver must dispatch the event without crashing. + it.skip(browserName !== 'firefox', 'Firefox-only regression guard'); + const webErrors: any[] = []; + page.context().on('weberror', e => webErrors.push(e)); + await Promise.all([ + page.waitForEvent('pageerror'), + page.goto(server.PREFIX + '/error.html'), + ]); + expect(webErrors.length).toBeGreaterThan(0); +}); From 35a307de69a18148dace2df0326498a84455a2ea Mon Sep 17 00:00:00 2001 From: john hight Date: Fri, 5 Jun 2026 11:23:32 -0700 Subject: [PATCH 2/2] test(firefox): inject synthetic uncaughtError without location to verify no crash Replace the weak end-to-end guard with a test that injects a synthetic Page.uncaughtError event with no location field directly into the Firefox protocol session. This exercises the exact code path that crashed before the fix and will fail on the unfixed code. Fixes: https://github.com/microsoft/playwright/issues/41169 --- tests/library/browsercontext-events.spec.ts | 25 +++++++++++++++++++++ tests/page/page-event-pageerror.spec.ts | 15 ------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/tests/library/browsercontext-events.spec.ts b/tests/library/browsercontext-events.spec.ts index 9afc08def24d2..d9c973c08a8a8 100644 --- a/tests/library/browsercontext-events.spec.ts +++ b/tests/library/browsercontext-events.spec.ts @@ -278,3 +278,28 @@ test('download event should work @smoke', async ({ page, server }) => { expect(download.suggestedFilename()).toBe('file.txt'); expect(download.page()).toBe(page); }); + +test('weberror event should not crash when Firefox omits location in uncaughtError', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41169' }, +}, async ({ page, toImpl, browserName }) => { + // Firefox can omit the location field in Page.uncaughtError protocol events + // when an uncaught error fires during mid-navigation. Inject a synthetic event + // without location to verify the driver does not crash. + test.skip(browserName !== 'firefox', 'Firefox-only: tests FF protocol behaviour'); + const webErrors: Error[] = []; + page.context().on('weberror', e => webErrors.push(e.error())); + const pageErrors: Error[] = []; + page.on('pageerror', e => pageErrors.push(e)); + + (toImpl(page) as any).delegate._session.emit('Page.uncaughtError', { + frameId: '', + message: 'Error: no-location-error', + stack: '', + // location intentionally absent — simulates the protocol omitting it + }); + + await page.waitForTimeout(500); + expect(pageErrors).toHaveLength(1); + expect(pageErrors[0].message).toBe('no-location-error'); + expect(webErrors).toHaveLength(1); +}); diff --git a/tests/page/page-event-pageerror.spec.ts b/tests/page/page-event-pageerror.spec.ts index 84c33c1f48df4..116805b63f515 100644 --- a/tests/page/page-event-pageerror.spec.ts +++ b/tests/page/page-event-pageerror.spec.ts @@ -230,18 +230,3 @@ it('should fire illegal character error', { else expect(error.message).toContain('illegal character'); }); - -it('should emit weberror on context when pageerror fires on firefox', { - annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41169' }, -}, async ({ page, browserName }) => { - // Regression guard: Firefox may omit location in Page.uncaughtError protocol events - // (e.g. during navigation interruption). The driver must dispatch the event without crashing. - it.skip(browserName !== 'firefox', 'Firefox-only regression guard'); - const webErrors: any[] = []; - page.context().on('weberror', e => webErrors.push(e)); - await Promise.all([ - page.waitForEvent('pageerror'), - page.goto(server.PREFIX + '/error.html'), - ]); - expect(webErrors.length).toBeGreaterThan(0); -});