diff --git a/packages/playwright-core/src/protocol/serializers.ts b/packages/playwright-core/src/protocol/serializers.ts index b6880572b7..559d4a062e 100644 --- a/packages/playwright-core/src/protocol/serializers.ts +++ b/packages/playwright-core/src/protocol/serializers.ts @@ -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)) diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 32877078e4..c873c0d289 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -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, diff --git a/packages/playwright-core/src/server/isomorphic/utilityScriptSerializers.ts b/packages/playwright-core/src/server/isomorphic/utilityScriptSerializers.ts index 01092e0ed8..0f3895fac5 100644 --- a/packages/playwright-core/src/server/isomorphic/utilityScriptSerializers.ts +++ b/packages/playwright-core/src/server/isomorphic/utilityScriptSerializers.ts @@ -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)) diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index ed48ef2b6c..412f3c8e3c 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -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, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 987261c173..f0616a7a25 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -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? diff --git a/tests/page/page-evaluate.spec.ts b/tests/page/page-evaluate.spec.ts index 8bc7a57d17..f0a97cf326 100644 --- a/tests/page/page-evaluate.spec.ts +++ b/tests/page/page-evaluate.spec.ts @@ -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 }) => {