chore: allow evaluating Error objects (#31691)

Previously, Error objects were replaced with strings.
Now, Error objects are reconstructed back from the serialized value.
This commit is contained in:
Dmitry Gozman 2024-07-15 05:47:40 -07:00 committed by GitHub
parent 074cc7d467
commit 1686e5174d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 21 deletions

View File

@ -49,6 +49,12 @@ function innerParseSerializedValue(value: SerializedValue, handles: any[] | unde
return new URL(value.u);
if (value.bi !== undefined)
return BigInt(value.bi);
if (value.e !== undefined) {
const error = new Error(value.e.m);
error.name = value.e.n;
error.stack = value.e.s;
return error;
}
if (value.r !== undefined)
return new RegExp(value.r.p, value.r.f);
@ -113,14 +119,8 @@ function innerSerializeValue(value: any, handleSerializer: (value: any) => Handl
return { s: value };
if (typeof value === 'bigint')
return { bi: value.toString() };
if (isError(value)) {
const error = value;
if ('captureStackTrace' in globalThis.Error) {
// v8
return { s: error.stack || '' };
}
return { s: `${error.name}: ${error.message}\n${error.stack}` };
}
if (isError(value))
return { e: { n: value.name, m: value.message, s: value.stack || '' } };
if (isDate(value))
return { d: value.toJSON() };
if (isURL(value))

View File

@ -58,6 +58,11 @@ scheme.SerializedValue = tObject({
d: tOptional(tString),
u: tOptional(tString),
bi: tOptional(tString),
e: tOptional(tObject({
m: tString,
n: tString,
s: tString,
})),
r: tOptional(tObject({
p: tString,
f: tString,

View File

@ -20,7 +20,8 @@ export type SerializedValue =
{ d: string } |
{ u: string } |
{ bi: string } |
{ r: { p: string, f: string} } |
{ e: { n: string, m: string, s: string } } |
{ r: { p: string, f: string } } |
{ a: SerializedValue[], id: number } |
{ o: { k: string, v: SerializedValue }[], id: number } |
{ ref: number } |
@ -94,6 +95,12 @@ export function source() {
return new URL(value.u);
if ('bi' in value)
return BigInt(value.bi);
if ('e' in value) {
const error = new Error(value.e.m);
error.name = value.e.n;
error.stack = value.e.s;
return error;
}
if ('r' in value)
return new RegExp(value.r.p, value.r.f);
if ('a' in value) {
@ -163,14 +170,8 @@ export function source() {
if (typeof value === 'bigint')
return { bi: value.toString() };
if (isError(value)) {
const error = value;
if (error.stack?.startsWith(error.name + ': ' + error.message)) {
// v8
return error.stack;
}
return `${error.name}: ${error.message}\n${error.stack}`;
}
if (isError(value))
return { e: { n: value.name, m: value.message, s: value.stack || '' } };
if (isDate(value))
return { d: value.toJSON() };
if (isURL(value))

View File

@ -177,6 +177,11 @@ export type SerializedValue = {
d?: string,
u?: string,
bi?: string,
e?: {
m: string,
n: string,
s: string,
},
r?: {
p: string,
f: string,

View File

@ -82,6 +82,13 @@ SerializedValue:
u: string?
# String representation of BigInt.
bi: string?
# Serialized Error object.
e:
type: object?
properties:
m: string
n: string
s: string
# Regular expression pattern and flags.
r:
type: object?

View File

@ -585,13 +585,37 @@ it('should evaluate exception with a function on the stack', async ({ page }) =>
return new Error('error message');
})();
});
expect(error).toContain('Error: error message');
expect(error).toContain('functionOnStack');
expect(error.message).toBe('error message');
expect(error.stack).toContain('functionOnStack');
});
it('should evaluate exception', async ({ page }) => {
const error = await page.evaluate(`new Error('error message')`);
expect(error).toContain('Error: error message');
const error = await page.evaluate(() => {
function innerFunction() {
const e = new Error('error message');
e.name = 'foobar';
return e;
}
return innerFunction();
});
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe('error message');
expect((error as Error).name).toBe('foobar');
expect((error as Error).stack).toContain('innerFunction');
});
it('should pass exception argument', async ({ page }) => {
function innerFunction() {
const e = new Error('error message');
e.name = 'foobar';
return e;
}
const received = await page.evaluate(e => {
return { message: e.message, name: e.name, stack: e.stack };
}, innerFunction());
expect(received.message).toBe('error message');
expect(received.name).toBe('foobar');
expect(received.stack).toContain('innerFunction');
});
it('should evaluate date', async ({ page }) => {