From 3f2af6d7e19928fde138b67f9fda028298fb5771 Mon Sep 17 00:00:00 2001 From: ch-z-hc <45302000+ch-z-hc@users.noreply.github.com> Date: Wed, 14 Oct 2026 04:16:49 +0800 Subject: [PATCH 1/2] fix(console): preserve primitive exception messages --- .../__tests__/chromium-cdp.test.ts | 23 +++++++++++++++++++ .../src/browser-driver/chromium-cdp.ts | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/extension/src/browser-driver/__tests__/chromium-cdp.test.ts b/apps/extension/src/browser-driver/__tests__/chromium-cdp.test.ts index 32dfe6ab..aae0c51a 100644 --- a/apps/extension/src/browser-driver/__tests__/chromium-cdp.test.ts +++ b/apps/extension/src/browser-driver/__tests__/chromium-cdp.test.ts @@ -852,6 +852,29 @@ describe("ChromiumCdp", () => { ]); }); + it.each([ + [{ type: "string", value: "connection token expired" }, "connection token expired"], + [{ type: "string", value: "first line\nsecond line" }, "first line"], + [{ type: "number", value: 0, description: "0" }, "0"], + [{ type: "boolean", value: false }, "false"], + [{ type: "object", subtype: "null", value: null }, "null"], + [{ type: "undefined" }, "[undefined]"], + ])("records the thrown primitive %j in console entries", async (exception, text) => { + const { api, onEvent } = fakeApi(); + const cdp = new ChromiumCdp(api); + try { + await cdp.ensureAttached(22); + onEvent.fire({ tabId: 22 }, "Runtime.exceptionThrown", { + exceptionDetails: { text: "Uncaught", exception }, + }); + expect(cdp.consoleEntriesSince(22, 0, 50, 1000, false).entries).toMatchObject([ + { kind: "exception", level: "error", text }, + ]); + } finally { + cdp.dispose(); + } + }); + it("filters console entries by cursor and caps result size", async () => { const { api, onEvent } = fakeApi(); const cdp = new ChromiumCdp(api); diff --git a/apps/extension/src/browser-driver/chromium-cdp.ts b/apps/extension/src/browser-driver/chromium-cdp.ts index fd9cc278..6f60ea30 100644 --- a/apps/extension/src/browser-driver/chromium-cdp.ts +++ b/apps/extension/src/browser-driver/chromium-cdp.ts @@ -1271,7 +1271,7 @@ export function parseExceptionThrown(params: unknown): ParsedConsoleEntry | null const details = (raw.exceptionDetails ?? {}) as Record; const exception = (details.exception ?? {}) as Record; const text = - firstLine(typeof exception.description === "string" ? exception.description : undefined) || + firstLine(remoteObjectToText(exception)) || firstLine(typeof details.text === "string" ? details.text : undefined) || "Uncaught (unknown error)"; return makeConsoleEntry({ From 41ad6c54211e150e21ab3873c1ecd235d34780c8 Mon Sep 17 00:00:00 2001 From: ch-z-hc <45302000+ch-z-hc@users.noreply.github.com> Date: Wed, 14 Oct 2026 04:51:27 +0800 Subject: [PATCH 2/2] fix(evaluate): preserve thrown primitive values --- .../src/tools/__tests__/evaluate.test.ts | 22 +++++++++++++++++++ apps/extension/src/tools/evaluate.ts | 10 ++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/apps/extension/src/tools/__tests__/evaluate.test.ts b/apps/extension/src/tools/__tests__/evaluate.test.ts index 7b9d7c92..2888a7f1 100644 --- a/apps/extension/src/tools/__tests__/evaluate.test.ts +++ b/apps/extension/src/tools/__tests__/evaluate.test.ts @@ -221,6 +221,28 @@ describe("handleEvaluate", () => { expect(res.error?.column).toBe(8); }); + it.each([ + [{ type: "string", value: "connection token expired" }, "Uncaught connection token expired"], + [{ type: "boolean", value: false }, "Uncaught false"], + [{ type: "object", subtype: "null", value: null }, "Uncaught null"], + [{ type: "number", value: 0, description: "0" }, "0"], + ])("preserves a thrown primitive %j ahead of the generic header", async (exception, text) => { + const sm = new SessionManager({ agentWindow: fakeAgentWindow([100]) }); + await sm.start("aa11"); + const fake = makeFakeCdp({ + evaluate: () => ({ + result: exception, + exceptionDetails: { text: "Uncaught", lineNumber: 0, columnNumber: 0, exception }, + }), + }); + const res = await handleEvaluate( + sm, + { session_id: "aa11", expression: "throw value" }, + { cdp: fake.cdp, tabsApi: fake.tabsApi }, + ); + expect(res).toMatchObject({ ok: false, error: { text, line: 1, column: 0 } }); + }); + it("stringifies unserializableValue (Infinity / NaN / BigInt)", async () => { const sm = new SessionManager({ agentWindow: fakeAgentWindow([100]) }); await sm.start("aa11"); diff --git a/apps/extension/src/tools/evaluate.ts b/apps/extension/src/tools/evaluate.ts index 45354d9a..6bc9d4af 100644 --- a/apps/extension/src/tools/evaluate.ts +++ b/apps/extension/src/tools/evaluate.ts @@ -75,19 +75,17 @@ function abortedError(signal: AbortSignal | undefined): RpcError | null { /** * Pull a human-readable error text out of CDP's exception payload. - * Prefers `exception.description` (the full stack) and falls back to - * `exceptionDetails.text` (the short header). Both are best-effort — - * Chrome sometimes ships an empty string when the throw value is - * `null` / `undefined` / a non-Error primitive. + * Prefers `exception.description` (the full stack), then a thrown + * primitive's value, before falling back to the generic short header. */ function extractErrorText(details: CdpExceptionDetails): string { const desc = details.exception?.description?.trim(); if (desc) return desc; - const text = details.text?.trim(); - if (text) return text; if (details.exception?.value !== undefined) { return `Uncaught ${String(details.exception.value)}`; } + const text = details.text?.trim(); + if (text) return text; return "Uncaught (unknown error)"; }