From d911bd8e8e846ecd3f9dd1c68b8d622adc9780a4 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Fri, 20 Feb 2026 12:37:17 +0100 Subject: [PATCH] refactor: cleanup string and structured console formatters --- src/formatters/ConsoleFormatter.ts | 267 ++--- .../ConsoleFormatter.test.js.snapshot | 239 ++++- tests/formatters/ConsoleFormatter.test.ts | 990 +++++++++--------- 3 files changed, 823 insertions(+), 673 deletions(-) diff --git a/src/formatters/ConsoleFormatter.ts b/src/formatters/ConsoleFormatter.ts index 98213230c..4cefab55e 100644 --- a/src/formatters/ConsoleFormatter.ts +++ b/src/formatters/ConsoleFormatter.ts @@ -27,9 +27,21 @@ export type IgnoreCheck = ( frame: DevTools.DevTools.StackTrace.StackTrace.Frame, ) => boolean; -export class ConsoleFormatter { - static readonly #STACK_TRACE_MAX_LINES = 50; +interface ConsoleMessageConcise { + type: string; + text: string; + argsCount: number; + id: number; +} + +interface ConsoleMessageDetailed extends ConsoleMessageConcise { + // pre-formatted args. + args: string[]; + // pre-formatted stacktrace. + stackTrace?: string; +} +export class ConsoleFormatter { readonly #id: number; readonly #type: string; readonly #text: string; @@ -40,7 +52,7 @@ export class ConsoleFormatter { readonly #stack?: DevTools.DevTools.StackTrace.StackTrace.StackTrace; readonly #cause?: SymbolizedError; - readonly #isIgnored: IgnoreCheck; + readonly isIgnored: IgnoreCheck; private constructor(params: { id: number; @@ -59,7 +71,7 @@ export class ConsoleFormatter { this.#resolvedArgs = params.resolvedArgs ?? []; this.#stack = params.stack; this.#cause = params.cause; - this.#isIgnored = params.isIgnored; + this.isIgnored = params.isIgnored; } static async from( @@ -160,20 +172,12 @@ export class ConsoleFormatter { // The short format for a console message. toString(): string { - return `msgid=${this.#id} [${this.#type}] ${this.#text} (${this.#argCount} args)`; + return convertConsoleMessageConciseToString(this.toJSON()); } // The verbose format for a console message, including all details. toStringDetailed(): string { - const result = [ - `ID: ${this.#id}`, - `Message: ${this.#type}> ${this.#text}`, - this.#formatArgs(), - this.#formatStackTrace(this.#stack, this.#cause, { - includeHeading: true, - }), - ].filter(line => !!line); - return result.join('\n'); + return convertConsoleMessageConciseDetailedToString(this.toJSONDetailed()); } #getArgs(): unknown[] { @@ -188,140 +192,159 @@ export class ConsoleFormatter { return []; } - #formatArg(arg: unknown) { - if (arg instanceof SymbolizedError) { - return [ - arg.message, - this.#formatStackTrace(arg.stackTrace, arg.cause, { - includeHeading: false, - }), - ] - .filter(line => !!line) - .join('\n'); - } - return typeof arg === 'object' ? JSON.stringify(arg) : String(arg); + toJSON(): ConsoleMessageConcise { + return { + type: this.#type, + text: this.#text, + argsCount: this.#argCount, + id: this.#id, + }; } - #formatArgs(): string { - const args = this.#getArgs(); + toJSONDetailed(): ConsoleMessageDetailed { + return { + id: this.#id, + type: this.#type, + text: this.#text, + argsCount: this.#argCount, + args: this.#getArgs().map(arg => formatArg(arg, this)), + stackTrace: this.#stack + ? formatStackTrace(this.#stack, this.#cause, this) + : undefined, + }; + } +} - if (!args.length) { - return ''; - } +function convertConsoleMessageConciseToString(msg: ConsoleMessageConcise) { + return `msgid=${msg.id} [${msg.type}] ${msg.text} (${msg.argsCount} args)`; +} - const result = ['### Arguments']; +function convertConsoleMessageConciseDetailedToString( + msg: ConsoleMessageDetailed, +) { + const result = [ + `ID: ${msg.id}`, + `Message: ${msg.type}> ${msg.text}`, + formatArgs(msg), + ...(msg.stackTrace ? ['### Stack trace', msg.stackTrace] : []), + ].filter(line => !!line); + return result.join('\n'); +} - for (const [key, arg] of args.entries()) { - result.push(`Arg #${key}: ${this.#formatArg(arg)}`); - } +function formatArgs(msg: ConsoleMessageDetailed): string { + const args = msg.args; - return result.join('\n'); + if (!args.length) { + return ''; } - #formatStackTrace( - stackTrace: DevTools.DevTools.StackTrace.StackTrace.StackTrace | undefined, - cause: SymbolizedError | undefined, - opts: {includeHeading: boolean}, - ): string { - if (!stackTrace) { - return ''; - } + const result = ['### Arguments']; - const lines = this.#formatStackTraceInner(stackTrace, cause); - const includedLines = lines.slice( - 0, - ConsoleFormatter.#STACK_TRACE_MAX_LINES, - ); - const reminderCount = lines.length - includedLines.length; + for (const [key, arg] of args.entries()) { + result.push(`Arg #${key}: ${arg}`); + } + + return result.join('\n'); +} +function formatArg(arg: unknown, formatter: {isIgnored: IgnoreCheck}) { + if (arg instanceof SymbolizedError) { return [ - opts.includeHeading ? '### Stack trace' : '', - ...includedLines, - reminderCount > 0 ? `... and ${reminderCount} more frames` : '', - 'Note: line and column numbers use 1-based indexing', + arg.message, + arg.stackTrace + ? formatStackTrace(arg.stackTrace, arg.cause, formatter) + : undefined, ] .filter(line => !!line) .join('\n'); } + return typeof arg === 'object' ? JSON.stringify(arg) : String(arg); +} - #formatStackTraceInner( - stackTrace: DevTools.DevTools.StackTrace.StackTrace.StackTrace | undefined, - cause: SymbolizedError | undefined, - ): string[] { - if (!stackTrace) { - return []; - } - - return [ - ...this.#formatFragment(stackTrace.syncFragment), - ...stackTrace.asyncFragments - .map(this.#formatAsyncFragment.bind(this)) - .flat(), - ...this.#formatCause(cause), - ]; - } +const STACK_TRACE_MAX_LINES = 50; + +function formatStackTrace( + stackTrace: DevTools.DevTools.StackTrace.StackTrace.StackTrace, + cause: SymbolizedError | undefined, + formatter: {isIgnored: IgnoreCheck}, +): string { + const lines = formatStackTraceInner(stackTrace, cause, formatter); + const includedLines = lines.slice(0, STACK_TRACE_MAX_LINES); + const reminderCount = lines.length - includedLines.length; + + return [ + ...includedLines, + reminderCount > 0 ? `... and ${reminderCount} more frames` : '', + 'Note: line and column numbers use 1-based indexing', + ] + .filter(line => !!line) + .join('\n'); +} - #formatFragment( - fragment: DevTools.DevTools.StackTrace.StackTrace.Fragment, - ): string[] { - const frames = fragment.frames.filter(frame => !this.#isIgnored(frame)); - return frames.map(this.#formatFrame.bind(this)); +function formatStackTraceInner( + stackTrace: DevTools.DevTools.StackTrace.StackTrace.StackTrace | undefined, + cause: SymbolizedError | undefined, + formatter: {isIgnored: IgnoreCheck}, +): string[] { + if (!stackTrace) { + return []; } - #formatAsyncFragment( - fragment: DevTools.DevTools.StackTrace.StackTrace.AsyncFragment, - ): string[] { - const formattedFrames = this.#formatFragment(fragment); - if (formattedFrames.length === 0) { - return []; - } + return [ + ...formatFragment(stackTrace.syncFragment, formatter), + ...stackTrace.asyncFragments + .map(item => formatAsyncFragment(item, formatter)) + .flat(), + ...formatCause(cause, formatter), + ]; +} - const separatorLineLength = 40; - const prefix = `--- ${fragment.description || 'async'} `; - const separator = prefix + '-'.repeat(separatorLineLength - prefix.length); - return [separator, ...formattedFrames]; - } +function formatFragment( + fragment: DevTools.DevTools.StackTrace.StackTrace.Fragment, + formatter: {isIgnored: IgnoreCheck}, +): string[] { + const frames = fragment.frames.filter(frame => !formatter.isIgnored(frame)); + return frames.map(formatFrame); +} - #formatFrame(frame: DevTools.DevTools.StackTrace.StackTrace.Frame): string { - let result = `at ${frame.name ?? ''}`; - if (frame.uiSourceCode) { - const location = frame.uiSourceCode.uiLocation(frame.line, frame.column); - result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`; - } else if (frame.url) { - result += ` (${frame.url}:${frame.line}:${frame.column})`; - } - return result; +function formatAsyncFragment( + fragment: DevTools.DevTools.StackTrace.StackTrace.AsyncFragment, + formatter: {isIgnored: IgnoreCheck}, +): string[] { + const formattedFrames = formatFragment(fragment, formatter); + if (formattedFrames.length === 0) { + return []; } - #formatCause(cause: SymbolizedError | undefined): string[] { - if (!cause) { - return []; - } + const separatorLineLength = 40; + const prefix = `--- ${fragment.description || 'async'} `; + const separator = prefix + '-'.repeat(separatorLineLength - prefix.length); + return [separator, ...formattedFrames]; +} - return [ - `Caused by: ${cause.message}`, - ...this.#formatStackTraceInner(cause.stackTrace, cause.cause), - ]; +function formatFrame( + frame: DevTools.DevTools.StackTrace.StackTrace.Frame, +): string { + let result = `at ${frame.name ?? ''}`; + if (frame.uiSourceCode) { + const location = frame.uiSourceCode.uiLocation(frame.line, frame.column); + result += ` (${location.linkText(/* skipTrim */ false, /* showColumnNumber */ true)})`; + } else if (frame.url) { + result += ` (${frame.url}:${frame.line}:${frame.column})`; } + return result; +} - toJSON(): object { - return { - type: this.#type, - text: this.#text, - argsCount: this.#argCount, - id: this.#id, - }; +function formatCause( + cause: SymbolizedError | undefined, + formatter: {isIgnored: IgnoreCheck}, +): string[] { + if (!cause) { + return []; } - toJSONDetailed(): object { - return { - id: this.#id, - type: this.#type, - text: this.#text, - args: this.#getArgs().map(arg => - typeof arg === 'object' ? arg : String(arg), - ), - stackTrace: this.#stack, - }; - } + return [ + `Caused by: ${cause.message}`, + ...formatStackTraceInner(cause.stackTrace, cause.cause, formatter), + ]; } diff --git a/tests/formatters/ConsoleFormatter.test.js.snapshot b/tests/formatters/ConsoleFormatter.test.js.snapshot index c836c7087..9f4490278 100644 --- a/tests/formatters/ConsoleFormatter.test.js.snapshot +++ b/tests/formatters/ConsoleFormatter.test.js.snapshot @@ -1,20 +1,67 @@ -exports[`ConsoleFormatter > toString > formats a console.log message 1`] = ` +exports[`ConsoleFormatter > toString/toJSON > formats a console.log message toJSON 1`] = ` +{ + "type": "log", + "text": "Hello, world!", + "argsCount": 0, + "id": 1 +} +`; + +exports[`ConsoleFormatter > toString/toJSON > formats a console.log message toString 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/toJSON > formats a console.log message with multiple arguments toJSON 1`] = ` +{ + "type": "log", + "text": "Processing file:", + "argsCount": 2, + "id": 1 +} +`; + +exports[`ConsoleFormatter > toString/toJSON > formats a console.log message with multiple arguments toString 1`] = ` +msgid=1 [log] Processing file: (2 args) +`; + +exports[`ConsoleFormatter > toString/toJSON > formats a console.log message with one argument toJSON 1`] = ` +{ + "type": "log", + "text": "Processing file:", + "argsCount": 1, + "id": 1 +} `; -exports[`ConsoleFormatter > toString > formats a console.log message with one argument 1`] = ` -msgid=2 [log] Processing file: (1 args) +exports[`ConsoleFormatter > toString/toJSON > formats a console.log message with one argument toString 1`] = ` +msgid=1 [log] Processing file: (1 args) `; -exports[`ConsoleFormatter > toString > formats an UncaughtError 1`] = ` -msgid=4 [error] Uncaught TypeError: Cannot read properties of undefined (0 args) +exports[`ConsoleFormatter > toString/toJSON > formats an UncaughtError toJSON 1`] = ` +{ + "type": "error", + "text": "Uncaught TypeError: Cannot read properties of undefined", + "argsCount": 0, + "id": 1 +} `; -exports[`ConsoleFormatter > toStringDetailed > does not show call frames with ignore listed scripts 1`] = ` +exports[`ConsoleFormatter > toString/toJSON > formats an UncaughtError toString 1`] = ` +msgid=1 [error] Uncaught TypeError: Cannot read properties of undefined (0 args) +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > does not show call frames with ignore listed scripts toJSONDetailed 1`] = ` +{ + "id": 12, + "type": "log", + "text": "Hello stack trace!", + "argsCount": 0, + "args": [], + "stackTrace": "at foo (foo.ts:10:2)\\nat bar (foo.ts:20:2)\\nNote: line and column numbers use 1-based indexing" +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > does not show call frames with ignore listed scripts toStringDetailed 1`] = ` ID: 12 Message: log> Hello stack trace! ### Stack trace @@ -23,7 +70,18 @@ at bar (foo.ts:20:2) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > does not show fragments where all frames are ignore listed 1`] = ` +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > does not show fragments where all frames are ignore listed toJSONDetailed 1`] = ` +{ + "id": 13, + "type": "log", + "text": "Hello stack trace!", + "argsCount": 0, + "args": [], + "stackTrace": "at foo (foo.ts:10:2)\\n--- await ------------------------------\\nat bar (foo.ts:20:2)\\nNote: line and column numbers use 1-based indexing" +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > does not show fragments where all frames are ignore listed toStringDetailed 1`] = ` ID: 13 Message: log> Hello stack trace! ### Stack trace @@ -33,8 +91,19 @@ at bar (foo.ts:20:2) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > formats a console message with a stack trace 1`] = ` -ID: 5 +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console message with a stack trace toJSONDetailed 1`] = ` +{ + "id": 1, + "type": "log", + "text": "Hello stack trace!", + "argsCount": 0, + "args": [], + "stackTrace": "at foo (foo.ts:10:2)\\nat bar (foo.ts:20:2)\\n--- setTimeout -------------------------\\nat schedule (util.ts:5:2)\\nNote: line and column numbers use 1-based indexing" +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console message with a stack trace toStringDetailed 1`] = ` +ID: 1 Message: log> Hello stack trace! ### Stack trace at foo (foo.ts:10:2) @@ -44,7 +113,19 @@ at schedule (util.ts:5:2) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > formats a console message with an Error object argument 1`] = ` +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console message with an Error object argument toJSONDetailed 1`] = ` +{ + "id": 8, + "type": "log", + "text": "JSHandle@error", + "argsCount": 1, + "args": [ + "TypeError: Cannot read properties of undefined\\nat foo (foo.ts:10:2)\\nat bar (foo.ts:20:2)\\nNote: line and column numbers use 1-based indexing" + ] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console message with an Error object argument toStringDetailed 1`] = ` ID: 8 Message: log> JSHandle@error ### Arguments @@ -54,7 +135,19 @@ at bar (foo.ts:20:2) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > formats a console message with an Error object with cause 1`] = ` +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console message with an Error object with cause toJSONDetailed 1`] = ` +{ + "id": 9, + "type": "log", + "text": "JSHandle@error", + "argsCount": 1, + "args": [ + "AppError: Compute failed\\nat foo (foo.ts:10:2)\\nat bar (foo.ts:20:2)\\nCaused by: TypeError: Cannot read properties of undefined\\nat compute (library.js:5:10)\\nNote: line and column numbers use 1-based indexing" + ] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console message with an Error object with cause toStringDetailed 1`] = ` ID: 9 Message: log> JSHandle@error ### Arguments @@ -66,63 +159,153 @@ at compute (library.js:5:10) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > formats a console.error message 1`] = ` -ID: 4 +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.error message toJSONDetailed 1`] = ` +{ + "id": 1, + "type": "error", + "text": "Something went wrong", + "argsCount": 0, + "args": [] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.error message toStringDetailed 1`] = ` +ID: 1 Message: error> Something went wrong `; -exports[`ConsoleFormatter > toStringDetailed > formats a console.log message 1`] = ` +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.log message toJSONDetailed 1`] = ` +{ + "id": 1, + "type": "log", + "text": "Hello, world!", + "argsCount": 0, + "args": [] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.log message toStringDetailed 1`] = ` ID: 1 Message: log> Hello, world! `; -exports[`ConsoleFormatter > toStringDetailed > formats a console.log message with multiple arguments 1`] = ` -ID: 3 +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.log message with multiple arguments toJSONDetailed 1`] = ` +{ + "id": 1, + "type": "log", + "text": "Processing file:", + "argsCount": 2, + "args": [ + "file.txt", + "another file" + ] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.log message with multiple arguments toStringDetailed 1`] = ` +ID: 1 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 +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.log message with one argument toJSONDetailed 1`] = ` +{ + "id": 1, + "type": "log", + "text": "Processing file:", + "argsCount": 1, + "args": [ + "file.txt" + ] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats a console.log message with one argument toStringDetailed 1`] = ` +ID: 1 Message: log> Processing file: ### Arguments Arg #0: file.txt `; -exports[`ConsoleFormatter > toStringDetailed > formats an UncaughtError with a stack trace 1`] = ` -ID: 7 +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats an UncaughtError with a stack trace and a cause toJSONDetailed 1`] = ` +{ + "id": 10, + "type": "error", + "text": "Uncaught TypeError: Cannot read properties of undefined", + "argsCount": 0, + "args": [], + "stackTrace": "at foo (foo.ts:10:2)\\nat bar (foo.ts:20:2)\\n--- setTimeout -------------------------\\nat schedule (util.ts:5:2)\\nCaused by: TypeError: Cannot read properties of undefined\\nat compute (library.js:5:8)\\nNote: line and column numbers use 1-based indexing" +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats an UncaughtError with a stack trace and a cause toStringDetailed 1`] = ` +ID: 10 Message: error> Uncaught TypeError: Cannot read properties of undefined ### Stack trace at foo (foo.ts:10:2) at bar (foo.ts:20:2) --- setTimeout ------------------------- at schedule (util.ts:5:2) +Caused by: TypeError: Cannot read properties of undefined +at compute (library.js:5:8) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > formats an UncaughtError with a stack trace and a cause 1`] = ` -ID: 10 +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats an UncaughtError with a stack trace toJSONDetailed 1`] = ` +{ + "id": 7, + "type": "error", + "text": "Uncaught TypeError: Cannot read properties of undefined", + "argsCount": 0, + "args": [], + "stackTrace": "at foo (foo.ts:10:2)\\nat bar (foo.ts:20:2)\\n--- setTimeout -------------------------\\nat schedule (util.ts:5:2)\\nNote: line and column numbers use 1-based indexing" +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > formats an UncaughtError with a stack trace toStringDetailed 1`] = ` +ID: 7 Message: error> Uncaught TypeError: Cannot read properties of undefined ### Stack trace at foo (foo.ts:10:2) at bar (foo.ts:20:2) --- setTimeout ------------------------- at schedule (util.ts:5:2) -Caused by: TypeError: Cannot read properties of undefined -at compute (library.js:5:8) Note: line and column numbers use 1-based indexing `; -exports[`ConsoleFormatter > toStringDetailed > handles \"Execution context is not available\" error in args 1`] = ` +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > handles \"Execution context is not available\" error in args toJSONDetailed 1`] = ` +{ + "id": 6, + "type": "log", + "text": "Processing file:", + "argsCount": 1, + "args": [ + "" + ] +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > handles \"Execution context is not available\" error in args toStringDetailed 1`] = ` ID: 6 Message: log> Processing file: ### Arguments Arg #0: `; -exports[`ConsoleFormatter > toStringDetailed > limits the number lines for a stack trace 1`] = ` +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > limits the number lines for a stack trace toJSONDetailed 1`] = ` +{ + "id": 11, + "type": "log", + "text": "Hello stack trace!", + "argsCount": 0, + "args": [], + "stackTrace": "at fn0 (main.js:0:0)\\nat fn1 (main.js:1:1)\\nat fn2 (main.js:2:2)\\nat fn3 (main.js:3:3)\\nat fn4 (main.js:4:4)\\nat fn5 (main.js:5:5)\\nat fn6 (main.js:6:6)\\nat fn7 (main.js:7:7)\\nat fn8 (main.js:8:8)\\nat fn9 (main.js:9:9)\\nat fn10 (main.js:10:10)\\nat fn11 (main.js:11:11)\\nat fn12 (main.js:12:12)\\nat fn13 (main.js:13:13)\\nat fn14 (main.js:14:14)\\nat fn15 (main.js:15:15)\\nat fn16 (main.js:16:16)\\nat fn17 (main.js:17:17)\\nat fn18 (main.js:18:18)\\nat fn19 (main.js:19:19)\\nat fn20 (main.js:20:20)\\nat fn21 (main.js:21:21)\\nat fn22 (main.js:22:22)\\nat fn23 (main.js:23:23)\\nat fn24 (main.js:24:24)\\nat fn25 (main.js:25:25)\\nat fn26 (main.js:26:26)\\nat fn27 (main.js:27:27)\\nat fn28 (main.js:28:28)\\nat fn29 (main.js:29:29)\\nat fn30 (main.js:30:30)\\nat fn31 (main.js:31:31)\\nat fn32 (main.js:32:32)\\nat fn33 (main.js:33:33)\\nat fn34 (main.js:34:34)\\nat fn35 (main.js:35:35)\\nat fn36 (main.js:36:36)\\nat fn37 (main.js:37:37)\\nat fn38 (main.js:38:38)\\nat fn39 (main.js:39:39)\\nat fn40 (main.js:40:40)\\nat fn41 (main.js:41:41)\\nat fn42 (main.js:42:42)\\nat fn43 (main.js:43:43)\\nat fn44 (main.js:44:44)\\nat fn45 (main.js:45:45)\\nat fn46 (main.js:46:46)\\nat fn47 (main.js:47:47)\\nat fn48 (main.js:48:48)\\nat fn49 (main.js:49:49)\\n... and 50 more frames\\nNote: line and column numbers use 1-based indexing" +} +`; + +exports[`ConsoleFormatter > toStringDetailed/toJSONDetailed > limits the number lines for a stack trace toStringDetailed 1`] = ` ID: 11 Message: log> Hello stack trace! ### Stack trace diff --git a/tests/formatters/ConsoleFormatter.test.ts b/tests/formatters/ConsoleFormatter.test.ts index 1fbc8c19d..eba9f9d2e 100644 --- a/tests/formatters/ConsoleFormatter.test.ts +++ b/tests/formatters/ConsoleFormatter.test.ts @@ -4,7 +4,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import assert from 'node:assert'; import {describe, it} from 'node:test'; import {SymbolizedError} from '../../src/DevtoolsUtils.js'; @@ -34,56 +33,83 @@ const createMockMessage = ( } as unknown as ConsoleMessage; }; +function formatterTestConcise( + label: string, + setup: (t: it.TestContext) => Promise, +) { + it(label + ' toString', async t => { + const formatter = await setup(t); + t.assert.snapshot?.(formatter.toString()); + }); + it(label + ' toJSON', async t => { + const formatter = await setup(t); + t.assert.snapshot?.(JSON.stringify(formatter.toJSON(), null, 2)); + }); +} + +function formatterTestDetailed( + label: string, + setup: (t: it.TestContext) => Promise, +) { + it(label + ' toStringDetailed', async t => { + const formatter = await setup(t); + t.assert.snapshot?.(formatter.toStringDetailed()); + }); + it(label + ' toJSONDetailed', async t => { + const formatter = await setup(t); + t.assert.snapshot?.(JSON.stringify(formatter.toJSONDetailed(), null, 2)); + }); +} + describe('ConsoleFormatter', () => { - describe('toString', () => { - it('formats a console.log message', async t => { + describe('toString/toJSON', () => { + formatterTestConcise('formats a console.log message', async () => { 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', - remoteObject: () => ({type: 'string'}), - }, - ], - }); - const result = ( - await ConsoleFormatter.from(message, {id: 2, fetchDetailedData: true}) - ).toString(); - t.assert.snapshot?.(result); + return await ConsoleFormatter.from(message, {id: 1}); }); - it('formats a console.log message with multiple arguments', async t => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Processing file:', - args: () => [ - { - jsonValue: async () => 'file.txt', - remoteObject: () => ({type: 'string'}), - }, - { - jsonValue: async () => 'another file', - remoteObject: () => ({type: 'string'}), - }, - ], - }); - const result = ( - await ConsoleFormatter.from(message, {id: 3, fetchDetailedData: true}) - ).toString(); - t.assert.snapshot?.(result); - }); + formatterTestConcise( + 'formats a console.log message with one argument', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ + { + jsonValue: async () => 'file.txt', + remoteObject: () => ({type: 'string'}), + }, + ], + }); + return await ConsoleFormatter.from(message, {id: 1}); + }, + ); + + formatterTestConcise( + 'formats a console.log message with multiple arguments', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ + { + jsonValue: async () => 'file.txt', + remoteObject: () => ({type: 'string'}), + }, + { + jsonValue: async () => 'another file', + remoteObject: () => ({type: 'string'}), + }, + ], + }); + return await ConsoleFormatter.from(message, {id: 1}); + }, + ); - it('formats an UncaughtError', async t => { + formatterTestConcise('formats an UncaughtError', async () => { const error = new UncaughtError( { exceptionId: 1, @@ -97,270 +123,347 @@ describe('ConsoleFormatter', () => { }, '', ); - const result = ( - await ConsoleFormatter.from(error, {id: 4, fetchDetailedData: true}) - ).toString(); - t.assert.snapshot?.(result); + return await ConsoleFormatter.from(error, {id: 1}); }); }); - describe('toStringDetailed', () => { - it('formats a console.log message', async t => { + describe('toStringDetailed/toJSONDetailed', () => { + formatterTestDetailed('formats a console.log message', async () => { 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', - remoteObject: () => ({type: 'string'}), - }, - ], + return await ConsoleFormatter.from(message, { + id: 1, + fetchDetailedData: true, }); - 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', - remoteObject: () => ({type: 'string'}), - }, - { - jsonValue: async () => 'another file', - remoteObject: () => ({type: 'string'}), - }, - ], - }); - const result = ( - await ConsoleFormatter.from(message, {id: 3, fetchDetailedData: true}) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); + formatterTestDetailed( + 'formats a console.log message with one argument', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ + { + jsonValue: async () => 'file.txt', + remoteObject: () => ({type: 'string'}), + }, + ], + }); + return await ConsoleFormatter.from(message, { + id: 1, + fetchDetailedData: true, + }); + }, + ); + + formatterTestDetailed( + 'formats a console.log message with multiple arguments', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ + { + jsonValue: async () => 'file.txt', + remoteObject: () => ({type: 'string'}), + }, + { + jsonValue: async () => 'another file', + remoteObject: () => ({type: 'string'}), + }, + ], + }); + return await ConsoleFormatter.from(message, { + id: 1, + fetchDetailedData: true, + }); + }, + ); - it('formats a console.error message', async t => { + formatterTestDetailed('formats a console.error message', async () => { const message = createMockMessage({ type: () => 'error', text: () => 'Something went wrong', }); - const result = ( - await ConsoleFormatter.from(message, {id: 4}) - ).toStringDetailed(); - t.assert.snapshot?.(result); + return await ConsoleFormatter.from(message, { + id: 1, + fetchDetailedData: true, + }); }); - 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', + formatterTestDetailed( + 'formats a console message with a stack trace', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello stack trace!', + }); + const stackTrace = { + syncFragment: { frames: [ { - line: 5, + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, + { + line: 20, column: 2, - url: 'util.ts', - name: 'schedule', + url: 'foo.ts', + name: 'bar', }, ], }, - ], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - - const result = ( - await ConsoleFormatter.from(message, { - id: 5, - resolvedStackTraceForTesting: stackTrace, - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); - - it('handles "Execution context is not available" error in args', async t => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Processing file:', - args: () => [ - { - jsonValue: async () => { - throw new Error('Execution context is not available'); - }, - remoteObject: () => ({type: 'string'}), - }, - ], - }); - const formatter = await ConsoleFormatter.from(message, { - id: 6, - fetchDetailedData: true, - }); - const result = formatter.toStringDetailed(); - t.assert.snapshot?.(result); - assert.ok(result.includes('')); - }); - - it('formats an UncaughtError with a stack trace', async t => { - const stackTrace = { - syncFragment: { - frames: [ + asyncFragments: [ { - line: 10, - column: 2, - url: 'foo.ts', - name: 'foo', + description: 'setTimeout', + frames: [ + { + line: 5, + column: 2, + url: 'util.ts', + name: 'schedule', + }, + ], }, + ], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + + return await ConsoleFormatter.from(message, { + id: 1, + resolvedStackTraceForTesting: stackTrace, + }); + }, + ); + + formatterTestDetailed( + 'handles "Execution context is not available" error in args', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Processing file:', + args: () => [ { - line: 20, - column: 2, - url: 'foo.ts', - name: 'bar', + jsonValue: async () => { + throw new Error('Execution context is not available'); + }, + remoteObject: () => ({type: 'string'}), }, ], - }, - asyncFragments: [ - { - description: 'setTimeout', + }); + return await ConsoleFormatter.from(message, { + id: 6, + fetchDetailedData: true, + }); + }, + ); + + formatterTestDetailed( + 'formats an UncaughtError with a stack trace', + async () => { + const stackTrace = { + syncFragment: { frames: [ { - line: 5, + line: 10, column: 2, - url: 'util.ts', - name: 'schedule', + url: 'foo.ts', + name: 'foo', + }, + { + line: 20, + column: 2, + url: 'foo.ts', + name: 'bar', }, ], }, - ], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - const error = new UncaughtError( - { - exceptionId: 1, - lineNumber: 0, - columnNumber: 5, - exception: { - type: 'object', - description: 'TypeError: Cannot read properties of undefined', + asyncFragments: [ + { + description: 'setTimeout', + frames: [ + { + line: 5, + column: 2, + url: 'util.ts', + name: 'schedule', + }, + ], + }, + ], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + const error = new UncaughtError( + { + exceptionId: 1, + lineNumber: 0, + columnNumber: 5, + exception: { + type: 'object', + description: 'TypeError: Cannot read properties of undefined', + }, + text: 'Uncaught', }, - text: 'Uncaught', - }, - '', - ); + '', + ); - const result = ( - await ConsoleFormatter.from(error, { + return await ConsoleFormatter.from(error, { id: 7, resolvedStackTraceForTesting: stackTrace, - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); + }); + }, + ); + + formatterTestDetailed( + 'formats a console message with an Error object argument', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'JSHandle@error', + }); + const stackTrace = { + syncFragment: { + frames: [ + { + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, + { + line: 20, + column: 2, + url: 'foo.ts', + name: 'bar', + }, + ], + }, + asyncFragments: [], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + const error = SymbolizedError.createForTesting( + 'TypeError: Cannot read properties of undefined', + stackTrace, + ); - it('formats a console message with an Error object argument', async t => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'JSHandle@error', - }); - const stackTrace = { - syncFragment: { - frames: [ - { - line: 10, - column: 2, - url: 'foo.ts', - name: 'foo', - }, + return await ConsoleFormatter.from(message, { + id: 8, + resolvedArgsForTesting: [error], + }); + }, + ); + + formatterTestDetailed( + 'formats a console message with an Error object with cause', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'JSHandle@error', + }); + const stackTrace = { + syncFragment: { + frames: [ + { + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, + { + line: 20, + column: 2, + url: 'foo.ts', + name: 'bar', + }, + ], + }, + asyncFragments: [], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + const error = SymbolizedError.createForTesting( + 'AppError: Compute failed', + stackTrace, + SymbolizedError.createForTesting( + 'TypeError: Cannot read properties of undefined', { - line: 20, - column: 2, - url: 'foo.ts', - name: 'bar', - }, - ], - }, - asyncFragments: [], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - const error = SymbolizedError.createForTesting( - 'TypeError: Cannot read properties of undefined', - stackTrace, - ); + syncFragment: { + frames: [ + { + line: 5, + column: 10, + url: 'library.js', + name: 'compute', + }, + ], + }, + asyncFragments: [], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace, + ), + ); - const result = ( - await ConsoleFormatter.from(message, { - id: 8, + return await ConsoleFormatter.from(message, { + id: 9, resolvedArgsForTesting: [error], - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); + }); + }, + ); - it('formats a console message with an Error object with cause', async t => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'JSHandle@error', - }); - const stackTrace = { - syncFragment: { - frames: [ - { - line: 10, - column: 2, - url: 'foo.ts', - name: 'foo', - }, + formatterTestDetailed( + 'formats an UncaughtError with a stack trace and a cause', + async () => { + const stackTrace = { + syncFragment: { + frames: [ + { + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, + { + line: 20, + column: 2, + url: 'foo.ts', + name: 'bar', + }, + ], + }, + asyncFragments: [ { - line: 20, - column: 2, - url: 'foo.ts', - name: 'bar', + description: 'setTimeout', + frames: [ + { + line: 5, + column: 2, + url: 'util.ts', + name: 'schedule', + }, + ], }, ], - }, - asyncFragments: [], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - const error = SymbolizedError.createForTesting( - 'AppError: Compute failed', - stackTrace, - SymbolizedError.createForTesting( + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + const error = new UncaughtError( + { + exceptionId: 1, + lineNumber: 0, + columnNumber: 5, + exception: { + type: 'object', + description: 'TypeError: Cannot read properties of undefined', + }, + text: 'Uncaught', + }, + '', + ); + const cause = SymbolizedError.createForTesting( 'TypeError: Cannot read properties of undefined', { syncFragment: { frames: [ { line: 5, - column: 10, + column: 8, url: 'library.js', name: 'compute', }, @@ -368,186 +471,60 @@ describe('ConsoleFormatter', () => { }, asyncFragments: [], } as unknown as DevTools.StackTrace.StackTrace.StackTrace, - ), - ); - - const result = ( - await ConsoleFormatter.from(message, { - id: 9, - resolvedArgsForTesting: [error], - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); + ); - it('formats an UncaughtError with a stack trace and a cause', async t => { - 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 error = new UncaughtError( - { - exceptionId: 1, - lineNumber: 0, - columnNumber: 5, - exception: { - type: 'object', - description: 'TypeError: Cannot read properties of undefined', - }, - text: 'Uncaught', - }, - '', - ); - const cause = SymbolizedError.createForTesting( - 'TypeError: Cannot read properties of undefined', - { - syncFragment: { - frames: [ - { - line: 5, - column: 8, - url: 'library.js', - name: 'compute', - }, - ], - }, - asyncFragments: [], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace, - ); - - const result = ( - await ConsoleFormatter.from(error, { + return await ConsoleFormatter.from(error, { id: 10, resolvedStackTraceForTesting: stackTrace, resolvedCauseForTesting: cause, - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); - - it('limits the number lines for a stack trace', async t => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Hello stack trace!', - }); - const frames: DevTools.StackTrace.StackTrace.Frame[] = []; - for (let i = 0; i < 100; ++i) { - frames.push({ - line: i, - column: i, - url: 'main.js', - name: `fn${i}`, }); - } - const stackTrace = { - syncFragment: {frames}, - asyncFragments: [], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + }, + ); + + formatterTestDetailed( + 'limits the number lines for a stack trace', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello stack trace!', + }); + const frames: DevTools.StackTrace.StackTrace.Frame[] = []; + for (let i = 0; i < 100; ++i) { + frames.push({ + line: i, + column: i, + url: 'main.js', + name: `fn${i}`, + }); + } + const stackTrace = { + syncFragment: {frames}, + asyncFragments: [], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - const result = ( - await ConsoleFormatter.from(message, { + return await ConsoleFormatter.from(message, { id: 11, resolvedStackTraceForTesting: stackTrace, - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); - - it('does not show call frames with ignore listed scripts', 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: 200, - column: 46, - url: './node_modules/some-third-party-package/lib/index.js', - name: 'doThings', - }, - { - line: 250, - column: 12, - url: './node_modules/some-third-party-package/lib/index.js', - name: 'doThings2', - }, - { - line: 20, - column: 2, - url: 'foo.ts', - name: 'bar', - }, - ], - }, - asyncFragments: [], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - - const result = ( - await ConsoleFormatter.from(message, { - id: 12, - resolvedStackTraceForTesting: stackTrace, - isIgnoredForTesting: frame => - Boolean(frame.url?.includes('node_modules')), - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); - - it('does not show fragments where all frames are ignore listed', async t => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Hello stack trace!', - }); - const stackTrace = { - syncFragment: { - frames: [ - { - line: 10, - column: 2, - url: 'foo.ts', - name: 'foo', - }, - ], - }, - asyncFragments: [ - { - description: 'setTimeout', + }); + }, + ); + + formatterTestDetailed( + 'does not show call frames with ignore listed scripts', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello stack trace!', + }); + const stackTrace = { + syncFragment: { frames: [ + { + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, { line: 200, column: 46, @@ -560,11 +537,6 @@ describe('ConsoleFormatter', () => { url: './node_modules/some-third-party-package/lib/index.js', name: 'doThings2', }, - ], - }, - { - description: 'await', - frames: [ { line: 20, column: 2, @@ -573,103 +545,75 @@ describe('ConsoleFormatter', () => { }, ], }, - ], - } as unknown as DevTools.StackTrace.StackTrace.StackTrace; + asyncFragments: [], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - const result = ( - await ConsoleFormatter.from(message, { - id: 13, + return await ConsoleFormatter.from(message, { + id: 12, resolvedStackTraceForTesting: stackTrace, isIgnoredForTesting: frame => Boolean(frame.url?.includes('node_modules')), - }) - ).toStringDetailed(); - t.assert.snapshot?.(result); - }); - }); - describe('toJSON', () => { - it('formats a console.log message', async () => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Hello, world!', - }); - const result = (await ConsoleFormatter.from(message, {id: 1})).toJSON(); - assert.deepStrictEqual(result, { - type: 'log', - text: 'Hello, world!', - argsCount: 0, - id: 1, - }); - }); - - it('formats a console.log message with args', async () => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Processing file:', - args: () => [ - { - jsonValue: async () => 'file.txt', - remoteObject: () => ({type: 'string'}), - }, - { - jsonValue: async () => 'another file', - remoteObject: () => ({type: 'string'}), + }); + }, + ); + + formatterTestDetailed( + 'does not show fragments where all frames are ignore listed', + async () => { + const message = createMockMessage({ + type: () => 'log', + text: () => 'Hello stack trace!', + }); + const stackTrace = { + syncFragment: { + frames: [ + { + line: 10, + column: 2, + url: 'foo.ts', + name: 'foo', + }, + ], }, - ], - }); - const result = (await ConsoleFormatter.from(message, {id: 1})).toJSON(); - assert.deepStrictEqual(result, { - type: 'log', - text: 'Processing file:', - argsCount: 2, - id: 1, - }); - }); - }); - - describe('toJSONDetailed', () => { - it('formats a console.log message', async () => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Hello, world!', - }); - const result = ( - await ConsoleFormatter.from(message, {id: 1}) - ).toJSONDetailed(); - assert.deepStrictEqual(result, { - id: 1, - type: 'log', - text: 'Hello, world!', - args: [], - stackTrace: undefined, - }); - }); + asyncFragments: [ + { + description: 'setTimeout', + frames: [ + { + line: 200, + column: 46, + url: './node_modules/some-third-party-package/lib/index.js', + name: 'doThings', + }, + { + line: 250, + column: 12, + url: './node_modules/some-third-party-package/lib/index.js', + name: 'doThings2', + }, + ], + }, + { + description: 'await', + frames: [ + { + line: 20, + column: 2, + url: 'foo.ts', + name: 'bar', + }, + ], + }, + ], + } as unknown as DevTools.StackTrace.StackTrace.StackTrace; - it('formats a console.log message with args', async () => { - const message = createMockMessage({ - type: () => 'log', - text: () => 'Processing file:', - args: () => [ - { - jsonValue: async () => 'file.txt', - remoteObject: () => ({type: 'string'}), - }, - { - jsonValue: async () => 'another file', - remoteObject: () => ({type: 'string'}), - }, - ], - }); - const result = ( - await ConsoleFormatter.from(message, {id: 2, fetchDetailedData: true}) - ).toJSONDetailed(); - assert.deepStrictEqual(result, { - id: 2, - type: 'log', - text: 'Processing file:', - args: ['file.txt', 'another file'], - stackTrace: undefined, - }); - }); + return await ConsoleFormatter.from(message, { + id: 13, + resolvedStackTraceForTesting: stackTrace, + isIgnoredForTesting: frame => + Boolean(frame.url?.includes('node_modules')), + }); + }, + ); }); });