diff --git a/src/browser/CdpCaptureEventHelpers.ts b/src/browser/CdpCaptureEventHelpers.ts index e786ba09de..091d4ddec2 100644 --- a/src/browser/CdpCaptureEventHelpers.ts +++ b/src/browser/CdpCaptureEventHelpers.ts @@ -243,6 +243,12 @@ export const consolePrimitive = ( : undefined; case "undefined": return { type, text: "undefined" }; + case "object": + // The protocol delivers `null` as an object with a `null` subtype. Its + // sentinel is already in the event, unlike an unexpanded remote object. + return cdpStringValue(value.subtype) === "null" + ? { type, text: "null" } + : undefined; default: return undefined; } diff --git a/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts b/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts index fa67368c96..db8c669a19 100644 --- a/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts +++ b/tests/boundary/browser/cdpBrowserProvider.sensitive_data.test.ts @@ -224,6 +224,7 @@ describe("CdpBrowserProvider: explicitly requested text and payload shapes", () const browser = await startFakeCdpBrowser({ sensitiveShapes: true, binaryWebSocketEvent: true, + nullConsoleArgument: true, eventsBeforeReply: ["Network.enable"], }); trackBrowser(browser); @@ -240,6 +241,12 @@ describe("CdpBrowserProvider: explicitly requested text and payload shapes", () ); if (!result.ok) throw result.error; + expect(result.value.console.events[0]?.argument_types).toEqual([ + "string", + "number", + "object", + "object", + ]); expect(result.value.console.events[0]?.text_capture).toEqual({ status: "included", values: [ @@ -249,10 +256,12 @@ describe("CdpBrowserProvider: explicitly requested text and payload shapes", () text: "authorization=Bearer console-secret", }, { argument_index: 1, type: "number", text: "42" }, + { argument_index: 3, type: "object", text: "null" }, ], retained_bytes: Buffer.byteLength("authorization=Bearer console-secret") + - Buffer.byteLength("42"), + Buffer.byteLength("42") + + Buffer.byteLength("null"), }); expect(result.value.network.requests[0]?.body_shapes).toMatchObject({ status: "included", diff --git a/tests/fixtures/fakeCdpBrowserEvents.ts b/tests/fixtures/fakeCdpBrowserEvents.ts index 06c1d500c1..16dd2879cf 100644 --- a/tests/fixtures/fakeCdpBrowserEvents.ts +++ b/tests/fixtures/fakeCdpBrowserEvents.ts @@ -674,6 +674,9 @@ const emitRuntimeEvents = ( }, ] : []), + ...(options.nullConsoleArgument === true + ? [{ type: "object", subtype: "null", value: null }] + : []), ], stackTrace: { callFrames: [ diff --git a/tests/fixtures/fakeCdpBrowserTypes.ts b/tests/fixtures/fakeCdpBrowserTypes.ts index 13593b0826..08ee1a6346 100644 --- a/tests/fixtures/fakeCdpBrowserTypes.ts +++ b/tests/fixtures/fakeCdpBrowserTypes.ts @@ -98,6 +98,8 @@ export interface FakeOptions { readonly sessionRedirectResourceType?: "Document" | "Fetch" | "Script"; readonly closeAfterMethod?: string; readonly sensitiveShapes?: boolean; + /** Deliver a console argument the protocol spells as `{ type: "object", subtype: "null" }`. */ + readonly nullConsoleArgument?: boolean; readonly cachedResponseBody?: string; readonly invalidResponseBodyBase64?: boolean; readonly webMcpTools?: boolean;