豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 9b90564

Browse files
authored
chore: implement formatting for Error.cause messages (#905)
A follow-up PR will add actual `cause` property resolving of remote `Error` objects.
1 parent ffa00da commit 9b90564

File tree

4 files changed

+200
-10
lines changed

4 files changed

+200
-10
lines changed

src/DevtoolsUtils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,15 @@ export class SymbolizedError {
255255
targetId: string;
256256
includeStackAndCause?: boolean;
257257
resolvedStackTraceForTesting?: DevTools.StackTrace.StackTrace.StackTrace;
258+
resolvedCauseForTesting?: SymbolizedError;
258259
}): Promise<SymbolizedError> {
259260
const message = SymbolizedError.#getMessage(opts.details);
260261
if (!opts.includeStackAndCause || !opts.devTools) {
261-
return new SymbolizedError(message, opts.resolvedStackTraceForTesting);
262+
return new SymbolizedError(
263+
message,
264+
opts.resolvedStackTraceForTesting,
265+
opts.resolvedCauseForTesting,
266+
);
262267
}
263268

264269
let stackTrace: DevTools.StackTrace.StackTrace.StackTrace | undefined;
@@ -278,7 +283,11 @@ export class SymbolizedError {
278283

279284
// TODO: Turn opts.details.exception into a JSHandle and retrieve the 'cause' property.
280285
// If its an Error, recursively create a SymbolizedError.
281-
return new SymbolizedError(message, stackTrace);
286+
let cause: SymbolizedError | undefined;
287+
if (opts.resolvedCauseForTesting) {
288+
cause = opts.resolvedCauseForTesting;
289+
}
290+
return new SymbolizedError(message, stackTrace, cause);
282291
}
283292

284293
static async fromError(opts: {

src/formatters/ConsoleFormatter.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ConsoleFormatterOptions {
1919
devTools?: TargetUniverse;
2020
resolvedArgsForTesting?: unknown[];
2121
resolvedStackTraceForTesting?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
22+
resolvedCauseForTesting?: SymbolizedError;
2223
}
2324

2425
export class ConsoleFormatter {
@@ -30,7 +31,7 @@ export class ConsoleFormatter {
3031
readonly #resolvedArgs: unknown[];
3132

3233
readonly #stack?: DevTools.DevTools.StackTrace.StackTrace.StackTrace;
33-
readonly #cause?: SymbolizedError; // eslint-disable-line no-unused-private-class-members
34+
readonly #cause?: SymbolizedError;
3435

3536
private constructor(params: {
3637
id: number;
@@ -61,6 +62,7 @@ export class ConsoleFormatter {
6162
targetId: msg.targetId,
6263
includeStackAndCause: options?.fetchDetailedData,
6364
resolvedStackTraceForTesting: options?.resolvedStackTraceForTesting,
65+
resolvedCauseForTesting: options?.resolvedCauseForTesting,
6466
});
6567
return new ConsoleFormatter({
6668
id: options.id,
@@ -130,7 +132,10 @@ export class ConsoleFormatter {
130132
`ID: ${this.#id}`,
131133
`Message: ${this.#type}> ${this.#text}`,
132134
this.#formatArgs(),
133-
this.#formatStackTrace(this.#stack),
135+
this.#formatStackTrace(this.#stack, this.#cause, {
136+
includeHeading: true,
137+
includeNote: true,
138+
}),
134139
].filter(line => !!line);
135140
return result.join('\n');
136141
}
@@ -151,7 +156,10 @@ export class ConsoleFormatter {
151156
if (arg instanceof SymbolizedError) {
152157
return [
153158
arg.message,
154-
this.#formatStackTrace(arg.stackTrace, /* includeHeading */ false),
159+
this.#formatStackTrace(arg.stackTrace, arg.cause, {
160+
includeHeading: false,
161+
includeNote: true,
162+
}),
155163
]
156164
.filter(line => !!line)
157165
.join('\n');
@@ -177,19 +185,24 @@ export class ConsoleFormatter {
177185

178186
#formatStackTrace(
179187
stackTrace: DevTools.DevTools.StackTrace.StackTrace.StackTrace | undefined,
180-
includeHeading = true,
188+
cause: SymbolizedError | undefined,
189+
opts: {includeHeading: boolean; includeNote: boolean},
181190
): string {
182191
if (!stackTrace) {
183192
return '';
184193
}
185194

186-
const heading = includeHeading ? ['### Stack trace'] : [];
187195
return [
188-
...heading,
196+
opts.includeHeading ? '### Stack trace' : '',
189197
this.#formatFragment(stackTrace.syncFragment),
190198
...stackTrace.asyncFragments.map(this.#formatAsyncFragment.bind(this)),
191-
'Note: line and column numbers use 1-based indexing',
192-
].join('\n');
199+
this.#formatCause(cause),
200+
opts.includeNote
201+
? 'Note: line and column numbers use 1-based indexing'
202+
: '',
203+
]
204+
.filter(line => !!line)
205+
.join('\n');
193206
}
194207

195208
#formatFragment(
@@ -217,6 +230,23 @@ export class ConsoleFormatter {
217230
}
218231
return result;
219232
}
233+
234+
#formatCause(cause: SymbolizedError | undefined): string {
235+
if (!cause) {
236+
return '';
237+
}
238+
239+
return [
240+
`Caused by: ${cause.message}`,
241+
this.#formatStackTrace(cause.stackTrace, cause.cause, {
242+
includeHeading: false,
243+
includeNote: false,
244+
}),
245+
]
246+
.filter(line => !!line)
247+
.join('\n');
248+
}
249+
220250
toJSON(): object {
221251
return {
222252
type: this.#type,

tests/formatters/ConsoleFormatter.test.js.snapshot

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ at bar (foo.ts:20:2)
3535
Note: line and column numbers use 1-based indexing
3636
`;
3737

38+
exports[`ConsoleFormatter > toStringDetailed > formats a console message with an Error object with cause 1`] = `
39+
ID: 9
40+
Message: log> JSHandle@error
41+
### Arguments
42+
Arg #0: AppError: Compute failed
43+
at foo (foo.ts:10:2)
44+
at bar (foo.ts:20:2)
45+
Caused by: TypeError: Cannot read properties of undefined
46+
at compute (library.js:5:10)
47+
Note: line and column numbers use 1-based indexing
48+
`;
49+
3850
exports[`ConsoleFormatter > toStringDetailed > formats a console.error message 1`] = `
3951
ID: 4
4052
Message: error> Something went wrong
@@ -71,6 +83,19 @@ at schedule (util.ts:5:2)
7183
Note: line and column numbers use 1-based indexing
7284
`;
7385

86+
exports[`ConsoleFormatter > toStringDetailed > formats an UncaughtError with a stack trace and a cause 1`] = `
87+
ID: 10
88+
Message: error> Uncaught TypeError: Cannot read properties of undefined
89+
### Stack trace
90+
at foo (foo.ts:10:2)
91+
at bar (foo.ts:20:2)
92+
--- setTimeout -------------------------
93+
at schedule (util.ts:5:2)
94+
Caused by: TypeError: Cannot read properties of undefined
95+
at compute (library.js:5:8)
96+
Note: line and column numbers use 1-based indexing
97+
`;
98+
7499
exports[`ConsoleFormatter > toStringDetailed > handles \"Execution context is not available\" error in args 1`] = `
75100
ID: 6
76101
Message: log> Processing file:

tests/formatters/ConsoleFormatter.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,132 @@ describe('ConsoleFormatter', () => {
325325
).toStringDetailed();
326326
t.assert.snapshot?.(result);
327327
});
328+
329+
it('formats a console message with an Error object with cause', async t => {
330+
const message = createMockMessage({
331+
type: () => 'log',
332+
text: () => 'JSHandle@error',
333+
});
334+
const stackTrace = {
335+
syncFragment: {
336+
frames: [
337+
{
338+
line: 10,
339+
column: 2,
340+
url: 'foo.ts',
341+
name: 'foo',
342+
},
343+
{
344+
line: 20,
345+
column: 2,
346+
url: 'foo.ts',
347+
name: 'bar',
348+
},
349+
],
350+
},
351+
asyncFragments: [],
352+
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
353+
const error = SymbolizedError.createForTesting(
354+
'AppError: Compute failed',
355+
stackTrace,
356+
SymbolizedError.createForTesting(
357+
'TypeError: Cannot read properties of undefined',
358+
{
359+
syncFragment: {
360+
frames: [
361+
{
362+
line: 5,
363+
column: 10,
364+
url: 'library.js',
365+
name: 'compute',
366+
},
367+
],
368+
},
369+
asyncFragments: [],
370+
} as unknown as DevTools.StackTrace.StackTrace.StackTrace,
371+
),
372+
);
373+
374+
const result = (
375+
await ConsoleFormatter.from(message, {
376+
id: 9,
377+
resolvedArgsForTesting: [error],
378+
})
379+
).toStringDetailed();
380+
t.assert.snapshot?.(result);
381+
});
382+
383+
it('formats an UncaughtError with a stack trace and a cause', async t => {
384+
const stackTrace = {
385+
syncFragment: {
386+
frames: [
387+
{
388+
line: 10,
389+
column: 2,
390+
url: 'foo.ts',
391+
name: 'foo',
392+
},
393+
{
394+
line: 20,
395+
column: 2,
396+
url: 'foo.ts',
397+
name: 'bar',
398+
},
399+
],
400+
},
401+
asyncFragments: [
402+
{
403+
description: 'setTimeout',
404+
frames: [
405+
{
406+
line: 5,
407+
column: 2,
408+
url: 'util.ts',
409+
name: 'schedule',
410+
},
411+
],
412+
},
413+
],
414+
} as unknown as DevTools.StackTrace.StackTrace.StackTrace;
415+
const error = new UncaughtError(
416+
{
417+
exceptionId: 1,
418+
lineNumber: 0,
419+
columnNumber: 5,
420+
exception: {
421+
type: 'object',
422+
description: 'TypeError: Cannot read properties of undefined',
423+
},
424+
text: 'Uncaught',
425+
},
426+
'<mock target ID>',
427+
);
428+
const cause = SymbolizedError.createForTesting(
429+
'TypeError: Cannot read properties of undefined',
430+
{
431+
syncFragment: {
432+
frames: [
433+
{
434+
line: 5,
435+
column: 8,
436+
url: 'library.js',
437+
name: 'compute',
438+
},
439+
],
440+
},
441+
asyncFragments: [],
442+
} as unknown as DevTools.StackTrace.StackTrace.StackTrace,
443+
);
444+
445+
const result = (
446+
await ConsoleFormatter.from(error, {
447+
id: 10,
448+
resolvedStackTraceForTesting: stackTrace,
449+
resolvedCauseForTesting: cause,
450+
})
451+
).toStringDetailed();
452+
t.assert.snapshot?.(result);
453+
});
328454
});
329455
describe('toJSON', () => {
330456
it('formats a console.log message', async () => {

0 commit comments

Comments
 (0)