diff --git a/src/McpResponse.ts b/src/McpResponse.ts index 4f628c7af..d3f386862 100644 --- a/src/McpResponse.ts +++ b/src/McpResponse.ts @@ -4,12 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {createStackTraceForConsoleMessage} from './DevtoolsUtils.js'; -import type {ConsoleMessageData} from './formatters/consoleFormatter.js'; -import { - formatConsoleEventShort, - formatConsoleEventVerbose, -} from './formatters/consoleFormatter.js'; +import {ConsoleFormatter} from './formatters/ConsoleFormatter.js'; import {IssueFormatter} from './formatters/IssueFormatter.js'; import {NetworkFormatter} from './formatters/NetworkFormatter.js'; import {SnapshotFormatter} from './formatters/SnapshotFormatter.js'; @@ -234,7 +229,7 @@ export class McpResponse implements Response { detailedNetworkRequest = formatter; } - let consoleData: ConsoleMessageData | IssueFormatter | undefined; + let consoleData: ConsoleFormatter | IssueFormatter | undefined; if (this.#attachedConsoleMessageId) { const message = context.getConsoleMessageById( @@ -244,26 +239,11 @@ export class McpResponse implements Response { if ('args' in message) { const consoleMessage = message as ConsoleMessage; const devTools = context.getDevToolsUniverse(); - const stackTrace = devTools - ? await createStackTraceForConsoleMessage(devTools, consoleMessage) - : undefined; - - consoleData = { - consoleMessageStableId, - type: consoleMessage.type(), - message: consoleMessage.text(), - args: await Promise.all( - consoleMessage.args().map(async arg => { - const stringArg = await arg.jsonValue().catch(() => { - // Ignore errors. - }); - return typeof stringArg === 'object' - ? JSON.stringify(stringArg) - : String(stringArg); - }), - ), - stackTrace, - }; + consoleData = await ConsoleFormatter.from(consoleMessage, { + id: consoleMessageStableId, + fetchDetailedData: true, + devTools: devTools ?? undefined, + }); } else if (message instanceof DevTools.AggregatedIssue) { const formatter = new IssueFormatter(message, { id: consoleMessageStableId, @@ -277,16 +257,13 @@ export class McpResponse implements Response { } consoleData = formatter; } else { - consoleData = { - consoleMessageStableId, - type: 'error', - message: (message as Error).message, - args: [], - }; + consoleData = await ConsoleFormatter.from(message as Error, { + id: consoleMessageStableId, + }); } } - let consoleListData: Array | undefined; + let consoleListData: Array | undefined; if (this.#consoleDataOptions?.include) { let messages = context.getConsoleData( this.#consoleDataOptions.includePreservedMessages, @@ -308,36 +285,17 @@ export class McpResponse implements Response { consoleListData = ( await Promise.all( messages.map( - async ( - item, - ): Promise => { + async (item): Promise => { const consoleMessageStableId = context.getConsoleMessageStableId(item); if ('args' in item) { const consoleMessage = item as ConsoleMessage; const devTools = context.getDevToolsUniverse(); - const stackTrace = devTools - ? await createStackTraceForConsoleMessage( - devTools, - consoleMessage, - ) - : undefined; - return { - consoleMessageStableId, - type: consoleMessage.type(), - message: consoleMessage.text(), - args: await Promise.all( - consoleMessage.args().map(async arg => { - const stringArg = await arg.jsonValue().catch(() => { - // Ignore errors. - }); - return typeof stringArg === 'object' - ? JSON.stringify(stringArg) - : String(stringArg); - }), - ), - stackTrace, - }; + return await ConsoleFormatter.from(consoleMessage, { + id: consoleMessageStableId, + fetchDetailedData: true, + devTools: devTools ?? undefined, + }); } if (item instanceof DevTools.AggregatedIssue) { const formatter = new IssueFormatter(item, { @@ -348,12 +306,9 @@ export class McpResponse implements Response { } return formatter; } - return { - consoleMessageStableId, - type: 'error', - message: (item as Error).message, - args: [], - }; + return await ConsoleFormatter.from(item as Error, { + id: consoleMessageStableId, + }); }, ), ) @@ -411,8 +366,8 @@ export class McpResponse implements Response { toolName: string, context: McpContext, data: { - consoleData: ConsoleMessageData | IssueFormatter | undefined; - consoleListData: Array | undefined; + consoleData: ConsoleFormatter | IssueFormatter | undefined; + consoleListData: Array | undefined; snapshot: SnapshotFormatter | string | undefined; detailedNetworkRequest?: NetworkFormatter; networkRequests?: NetworkFormatter[]; @@ -551,7 +506,7 @@ Call ${handleDialog.name} to handle it before continuing.`); if (message instanceof IssueFormatter) { return message.toString(); } - return formatConsoleEventShort(message); + return message.toString(); }), ); } else { @@ -604,7 +559,7 @@ Call ${handleDialog.name} to handle it before continuing.`); #formatConsoleData( context: McpContext, - data: ConsoleMessageData | IssueFormatter | undefined, + data: ConsoleFormatter | IssueFormatter | undefined, ): string[] { const response: string[] = []; if (!data) { @@ -614,7 +569,7 @@ Call ${handleDialog.name} to handle it before continuing.`); if (data instanceof IssueFormatter) { response.push(data.toStringDetailed()); } else { - response.push(formatConsoleEventVerbose(data, context)); + response.push(data.toStringDetailed()); } return response; } diff --git a/src/formatters/ConsoleFormatter.ts b/src/formatters/ConsoleFormatter.ts new file mode 100644 index 000000000..b31d713cc --- /dev/null +++ b/src/formatters/ConsoleFormatter.ts @@ -0,0 +1,174 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + createStackTraceForConsoleMessage, + type TargetUniverse, +} from '../DevtoolsUtils.js'; +import type * as DevTools from '../third_party/index.js'; +import type {ConsoleMessage} from '../third_party/index.js'; + +export interface ConsoleFormatterOptions { + fetchDetailedData?: boolean; + id?: number; + devTools?: TargetUniverse; + resolvedStackTraceForTesting?: DevTools.DevTools.StackTrace.StackTrace.StackTrace; +} + +export class ConsoleFormatter { + #msg: ConsoleMessage | Error; + #resolvedArgs: unknown[] = []; + #resolvedStackTrace?: DevTools.DevTools.StackTrace.StackTrace.StackTrace; + #id?: number; + + private constructor( + msg: ConsoleMessage | Error, + options?: ConsoleFormatterOptions, + ) { + this.#msg = msg; + this.#id = options?.id; + this.#resolvedStackTrace = options?.resolvedStackTraceForTesting; + } + + static async from( + msg: ConsoleMessage | Error, + options?: ConsoleFormatterOptions, + ): Promise { + const formatter = new ConsoleFormatter(msg, options); + if (options?.fetchDetailedData) { + await formatter.#loadDetailedData(options?.devTools); + } + return formatter; + } + + async #loadDetailedData(devTools?: TargetUniverse): Promise { + if (this.#msg instanceof Error) { + return; + } + + this.#resolvedArgs = await Promise.all( + this.#msg.args().map(arg => arg.jsonValue()), + ); + + if (devTools) { + this.#resolvedStackTrace = await createStackTraceForConsoleMessage( + devTools, + this.#msg, + ); + } + } + + // The short format for a console message. + toString(): string { + const type = this.#getType(); + const text = this.#getText(); + const argsCount = + this.#msg instanceof Error + ? 0 + : this.#resolvedArgs.length || this.#msg.args().length; + const idPart = this.#id !== undefined ? `msgid=${this.#id} ` : ''; + return `${idPart}[${type}] ${text} (${argsCount} args)`; + } + + // The verbose format for a console message, including all details. + toStringDetailed(): string { + const result = [ + this.#id !== undefined ? `ID: ${this.#id}` : '', + `Message: ${this.#getType()}> ${this.#getText()}`, + this.#formatArgs(), + this.#formatStackTrace(this.#resolvedStackTrace), + ].filter(line => !!line); + return result.join('\n'); + } + + #getType(): string { + if (this.#msg instanceof Error) { + return 'error'; + } + return this.#msg.type(); + } + + #getText(): string { + if (this.#msg instanceof Error) { + return this.#msg.message; + } + return this.#msg.text(); + } + + #getArgs(): unknown[] { + if (this.#msg instanceof Error) { + return []; + } + if (this.#resolvedArgs.length > 0) { + const args = [...this.#resolvedArgs]; + // If there is no text, the first argument serves as text (see formatMessage). + if (!this.#msg.text()) { + args.shift(); + } + return args; + } + return []; + } + + #formatArg(arg: unknown) { + return typeof arg === 'object' ? JSON.stringify(arg) : String(arg); + } + + #formatArgs(): string { + const args = this.#getArgs(); + + if (!args.length) { + return ''; + } + + const result = ['### Arguments']; + + for (const [key, arg] of args.entries()) { + result.push(`Arg #${key}: ${this.#formatArg(arg)}`); + } + + return result.join('\n'); + } + + #formatStackTrace( + stackTrace: DevTools.DevTools.StackTrace.StackTrace.StackTrace | undefined, + ): string { + if (!stackTrace) { + return ''; + } + + return [ + '### Stack trace', + this.#formatFragment(stackTrace.syncFragment), + ...stackTrace.asyncFragments.map(this.#formatAsyncFragment.bind(this)), + ].join('\n'); + } + + #formatFragment( + fragment: DevTools.DevTools.StackTrace.StackTrace.Fragment, + ): string { + return fragment.frames.map(this.#formatFrame.bind(this)).join('\n'); + } + + #formatAsyncFragment( + fragment: DevTools.DevTools.StackTrace.StackTrace.AsyncFragment, + ): string { + const separatorLineLength = 40; + const prefix = `--- ${fragment.description || 'async'} `; + const separator = prefix + '-'.repeat(separatorLineLength - prefix.length); + return separator + '\n' + this.#formatFragment(fragment); + } + + #formatFrame(frame: DevTools.DevTools.StackTrace.StackTrace.Frame): string { + let result = `at ${frame.name ?? ''}`; + if (frame.uiSourceCode) { + result += ` (${frame.uiSourceCode.displayName()}:${frame.line}:${frame.column})`; + } else if (frame.url) { + result += ` (${frame.url}:${frame.line}:${frame.column})`; + } + return result; + } +} diff --git a/src/formatters/consoleFormatter.ts b/src/formatters/consoleFormatter.ts deleted file mode 100644 index a3b6ec6c6..000000000 --- a/src/formatters/consoleFormatter.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import type {McpContext} from '../McpContext.js'; -import type {DevTools} from '../third_party/index.js'; - -import {IssueFormatter} from './IssueFormatter.js'; - -export interface ConsoleMessageData { - consoleMessageStableId: number; - type?: string; - item?: DevTools.AggregatedIssue; - message?: string; - count?: number; - description?: string; - args?: string[]; - stackTrace?: DevTools.StackTrace.StackTrace.StackTrace; -} - -// The short format for a console message, based on a previous format. -export function formatConsoleEventShort(msg: ConsoleMessageData): string { - if (msg.item) { - const formatter = new IssueFormatter(msg.item, { - id: msg.consoleMessageStableId, - }); - return formatter.toString(); - } - return `msgid=${msg.consoleMessageStableId} [${msg.type}] ${msg.message} (${msg.args?.length ?? 0} args)`; -} - -function getArgs(msg: ConsoleMessageData) { - const args = [...(msg.args ?? [])]; - - // If there is no text, the first argument serves as text (see formatMessage). - if (!msg.message) { - args.shift(); - } - - return args; -} - -// The verbose format for a console message, including all details. -export function formatConsoleEventVerbose( - msg: ConsoleMessageData, - context?: McpContext, -): string { - const aggregatedIssue = msg.item; - if (aggregatedIssue) { - return new IssueFormatter(aggregatedIssue, { - id: msg.consoleMessageStableId, - requestIdResolver: context - ? context.resolveCdpRequestId.bind(context) - : undefined, - elementIdResolver: context - ? context.resolveCdpElementId.bind(context) - : undefined, - }).toStringDetailed(); - } - - const result = [ - `ID: ${msg.consoleMessageStableId}`, - `Message: ${msg.type}> ${msg.message}`, - formatArgs(msg), - formatStackTrace(msg.stackTrace), - ].filter(line => !!line); - return result.join('\n'); -} - -function formatArg(arg: unknown) { - return typeof arg === 'object' ? JSON.stringify(arg) : String(arg); -} - -function formatArgs(consoleData: ConsoleMessageData): string { - const args = getArgs(consoleData); - - if (!args.length) { - return ''; - } - - const result = ['### Arguments']; - - for (const [key, arg] of args.entries()) { - result.push(`Arg #${key}: ${formatArg(arg)}`); - } - - return result.join('\n'); -} - -function formatStackTrace( - stackTrace: DevTools.StackTrace.StackTrace.StackTrace | undefined, -): string { - if (!stackTrace) { - return ''; - } - - return [ - '### Stack trace', - formatFragment(stackTrace.syncFragment), - ...stackTrace.asyncFragments.map(formatAsyncFragment), - ].join('\n'); -} - -function formatFragment( - fragment: DevTools.StackTrace.StackTrace.Fragment, -): string { - return fragment.frames.map(formatFrame).join('\n'); -} - -function formatAsyncFragment( - fragment: DevTools.StackTrace.StackTrace.AsyncFragment, -): string { - const separatorLineLength = 40; - const prefix = `--- ${fragment.description || 'async'} `; - const separator = prefix + '-'.repeat(separatorLineLength - prefix.length); - return separator + '\n' + formatFragment(fragment); -} - -function formatFrame(frame: DevTools.StackTrace.StackTrace.Frame): string { - let result = `at ${frame.name ?? ''}`; - if (frame.uiSourceCode) { - result += ` (${frame.uiSourceCode.displayName()}:${frame.line}:${frame.column})`; - } else if (frame.url) { - result += ` (${frame.url}:${frame.line}:${frame.column})`; - } - return result; -} diff --git a/tests/formatters/ConsoleFormatter.test.js.snapshot b/tests/formatters/ConsoleFormatter.test.js.snapshot new file mode 100644 index 000000000..76bbb0a74 --- /dev/null +++ b/tests/formatters/ConsoleFormatter.test.js.snapshot @@ -0,0 +1,46 @@ +exports[`ConsoleFormatter > toString > formats a console.log message 1`] = ` +msgid=1 [log] Hello, world! (0 args) +`; + +exports[`ConsoleFormatter > toString > formats a console.log message with multiple arguments 1`] = ` +msgid=3 [log] Processing file: (2 args) +`; + +exports[`ConsoleFormatter > toString > formats a console.log message with one argument 1`] = ` +msgid=2 [log] Processing file: (1 args) +`; + +exports[`ConsoleFormatter > toStringDetailed > formats a console message with a stack trace 1`] = ` +ID: 5 +Message: log> Hello stack trace! +### Stack trace +at foo (foo.ts:10:2) +at bar (foo.ts:20:2) +--- setTimeout ------------------------- +at schedule (util.ts:5:2) +`; + +exports[`ConsoleFormatter > toStringDetailed > formats a console.error message 1`] = ` +ID: 4 +Message: error> Something went wrong +`; + +exports[`ConsoleFormatter > toStringDetailed > formats a console.log message 1`] = ` +ID: 1 +Message: log> Hello, world! +`; + +exports[`ConsoleFormatter > toStringDetailed > formats a console.log message with multiple arguments 1`] = ` +ID: 3 +Message: log> Processing file: +### Arguments +Arg #0: file.txt +Arg #1: another file +`; + +exports[`ConsoleFormatter > toStringDetailed > formats a console.log message with one argument 1`] = ` +ID: 2 +Message: log> Processing file: +### Arguments +Arg #0: file.txt +`; diff --git a/tests/formatters/ConsoleFormatter.test.ts b/tests/formatters/ConsoleFormatter.test.ts new file mode 100644 index 000000000..e72713541 --- /dev/null +++ b/tests/formatters/ConsoleFormatter.test.ts @@ -0,0 +1,166 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {describe, it} from 'node:test'; + +import {ConsoleFormatter} from '../../src/formatters/ConsoleFormatter.js'; +import type {ConsoleMessage} from '../../src/third_party/index.js'; +import type {DevTools} from '../../src/third_party/index.js'; + +interface MockConsoleMessage { + type: () => string; + text: () => string; + args: () => Array<{jsonValue: () => Promise}>; + stackTrace?: DevTools.StackTrace.StackTrace.StackTrace; +} + +const createMockMessage = ( + data: Partial = {}, +): ConsoleMessage => { + return { + type: () => data.type?.() ?? 'log', + text: () => data.text?.() ?? '', + args: () => data.args?.() ?? [], + ...data, + } as unknown as ConsoleMessage; +}; + +describe('ConsoleFormatter', () => { + describe('toString', () => { + it('formats a console.log message', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello, world!', + }); + const result = (await ConsoleFormatter.from(message, {id: 1})).toString(); + t.assert.snapshot?.(result); + }); + + it('formats a console.log message with one argument', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [{jsonValue: async () => 'file.txt'}], + }); + const result = ( + await ConsoleFormatter.from(message, {id: 2, fetchDetailedData: true}) + ).toString(); + t.assert.snapshot?.(result); + }); + + it('formats a console.log message with multiple arguments', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ + {jsonValue: async () => 'file.txt'}, + {jsonValue: async () => 'another file'}, + ], + }); + const result = ( + await ConsoleFormatter.from(message, {id: 3, fetchDetailedData: true}) + ).toString(); + t.assert.snapshot?.(result); + }); + }); + + describe('toStringDetailed', () => { + it('formats a console.log message', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello, world!', + }); + const result = ( + await ConsoleFormatter.from(message, {id: 1}) + ).toStringDetailed(); + t.assert.snapshot?.(result); + }); + + it('formats a console.log message with one argument', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [{jsonValue: async () => 'file.txt'}], + }); + const result = ( + await ConsoleFormatter.from(message, {id: 2, fetchDetailedData: true}) + ).toStringDetailed(); + t.assert.snapshot?.(result); + }); + + it('formats a console.log message with multiple arguments', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ + {jsonValue: async () => 'file.txt'}, + {jsonValue: async () => 'another file'}, + ], + }); + const result = ( + await ConsoleFormatter.from(message, {id: 3, fetchDetailedData: true}) + ).toStringDetailed(); + t.assert.snapshot?.(result); + }); + + it('formats a console.error message', async t => { + const message = createMockMessage({ + type: () => 'error', + text: () => 'Something went wrong', + }); + const result = ( + await ConsoleFormatter.from(message, {id: 4}) + ).toStringDetailed(); + t.assert.snapshot?.(result); + }); + + it('formats a console message with a stack trace', async t => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello stack trace!', + }); + const stackTrace = { + syncFragment: { + frames: [ + { + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, + { + line: 20, + column: 2, + url: 'foo.ts', + name: 'bar', + }, + ], + }, + asyncFragments: [ + { + description: 'setTimeout', + frames: [ + { + line: 5, + column: 2, + url: 'util.ts', + name: 'schedule', + }, + ], + }, + ], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + + const result = ( + await ConsoleFormatter.from(message, { + id: 5, + resolvedStackTraceForTesting: stackTrace, + }) + ).toStringDetailed(); + t.assert.snapshot?.(result); + }); + }); +}); diff --git a/tests/formatters/consoleFormatter.test.js.snapshot b/tests/formatters/consoleFormatter.test.js.snapshot deleted file mode 100644 index 411b28e53..000000000 --- a/tests/formatters/consoleFormatter.test.js.snapshot +++ /dev/null @@ -1,56 +0,0 @@ -exports[`consoleFormatter > formatConsoleEventShort > formats a console.log message 1`] = ` -msgid=1 [log] Hello, world! (0 args) -`; - -exports[`consoleFormatter > formatConsoleEventShort > formats a console.log message with multiple arguments 1`] = ` -msgid=3 [log] Processing file: (2 args) -`; - -exports[`consoleFormatter > formatConsoleEventShort > formats a console.log message with one argument 1`] = ` -msgid=2 [log] Processing file: (1 args) -`; - -exports[`consoleFormatter > formatConsoleEventVerbose > formats a console message with a stack trace 1`] = ` -ID: 5 -Message: log> Hello stack trace! -### Stack trace -at foo (foo.ts:10:2) -at bar (foo.ts:20:2) ---- setTimeout ------------------------- -at schedule (util.ts:5:2) -`; - -exports[`consoleFormatter > formatConsoleEventVerbose > formats a console.error message 1`] = ` -ID: 4 -Message: error> Something went wrong -`; - -exports[`consoleFormatter > formatConsoleEventVerbose > formats a console.log message 1`] = ` -ID: 1 -Message: log> Hello, world! -`; - -exports[`consoleFormatter > formatConsoleEventVerbose > formats a console.log message with multiple arguments 1`] = ` -ID: 3 -Message: log> Processing file: -### Arguments -Arg #0: file.txt -Arg #1: another file -`; - -exports[`consoleFormatter > formatConsoleEventVerbose > formats a console.log message with one argument 1`] = ` -ID: 2 -Message: log> Processing file: -### Arguments -Arg #0: file.txt -`; - -exports[`consoleFormatter > formats a console.log message with issue type 1`] = ` -ID: 5 -Message: issue> Mock Issue Title - -This is a mock issue description -Learn more: -[Learn more](http://example.com/learnmore) -[Learn more 2](http://example.com/another-learnmore) -`; diff --git a/tests/formatters/consoleFormatter.test.ts b/tests/formatters/consoleFormatter.test.ts deleted file mode 100644 index 26ced09d7..000000000 --- a/tests/formatters/consoleFormatter.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import {describe, it} from 'node:test'; - -import type {ConsoleMessageData} from '../../src/formatters/consoleFormatter.js'; -import { - formatConsoleEventShort, - formatConsoleEventVerbose, -} from '../../src/formatters/consoleFormatter.js'; -import type {DevTools} from '../../src/third_party/index.js'; - -describe('consoleFormatter', () => { - describe('formatConsoleEventShort', () => { - it('formats a console.log message', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 1, - type: 'log', - message: 'Hello, world!', - args: [], - }; - const result = formatConsoleEventShort(message); - t.assert.snapshot?.(result); - }); - - it('formats a console.log message with one argument', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 2, - type: 'log', - message: 'Processing file:', - args: ['file.txt'], - }; - const result = formatConsoleEventShort(message); - t.assert.snapshot?.(result); - }); - - it('formats a console.log message with multiple arguments', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 3, - type: 'log', - message: 'Processing file:', - args: ['file.txt', 'another file'], - }; - const result = formatConsoleEventShort(message); - t.assert.snapshot?.(result); - }); - }); - - describe('formatConsoleEventVerbose', () => { - it('formats a console.log message', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 1, - type: 'log', - message: 'Hello, world!', - args: [], - }; - const result = formatConsoleEventVerbose(message); - t.assert.snapshot?.(result); - }); - - it('formats a console.log message with one argument', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 2, - type: 'log', - message: 'Processing file:', - args: ['file.txt'], - }; - const result = formatConsoleEventVerbose(message); - t.assert.snapshot?.(result); - }); - - it('formats a console.log message with multiple arguments', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 3, - type: 'log', - message: 'Processing file:', - args: ['file.txt', 'another file'], - }; - const result = formatConsoleEventVerbose(message); - t.assert.snapshot?.(result); - }); - - it('formats a console.error message', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 4, - type: 'error', - message: 'Something went wrong', - }; - const result = formatConsoleEventVerbose(message); - t.assert.snapshot?.(result); - }); - - it('formats a console message with a stack trace', t => { - const message: ConsoleMessageData = { - consoleMessageStableId: 5, - type: 'log', - message: 'Hello stack trace!', - args: [], - stackTrace: { - syncFragment: { - frames: [ - { - line: 10, - column: 2, - url: 'foo.ts', - name: 'foo', - }, - { - line: 20, - column: 2, - url: 'foo.ts', - name: 'bar', - }, - ], - }, - asyncFragments: [ - { - description: 'setTimeout', - frames: [ - { - line: 5, - column: 2, - url: 'util.ts', - name: 'schedule', - }, - ], - }, - ], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace, - }; - const result = formatConsoleEventVerbose(message); - t.assert.snapshot?.(result); - }); - }); -});