diff --git a/packages/playwright-core/src/protocol/serializers.ts b/packages/playwright-core/src/protocol/serializers.ts index d43747237f760..3e4134d62c36e 100644 --- a/packages/playwright-core/src/protocol/serializers.ts +++ b/packages/playwright-core/src/protocol/serializers.ts @@ -75,6 +75,10 @@ function innerParseSerializedValue(value: SerializedValue, handles: any[] | unde return BigInt(value.bi); if (value.r !== undefined) return new RegExp(value.r.p, value.r.f); + if (value.m !== undefined) + return new Map(innerParseSerializedValue(value.m, handles, refs)); + if (value.se !== undefined) + return new Set(innerParseSerializedValue(value.se, handles, refs)); if (value.a !== undefined) { const result: any[] = []; @@ -145,6 +149,10 @@ function innerSerializeValue(value: any, handleSerializer: (value: any) => Handl } return { s: `${error.name}: ${error.message}\n${error.stack}` }; } + if (isMap(value)) + return { m: innerSerializeValue(Array.from(value), handleSerializer, visitorInfo) }; + if (isSet(value)) + return { se: innerSerializeValue(Array.from(value), handleSerializer, visitorInfo) }; if (isDate(value)) return { d: value.toJSON() }; if (isURL(value)) @@ -175,6 +183,14 @@ function innerSerializeValue(value: any, handleSerializer: (value: any) => Handl throw new Error('Unexpected value'); } +function isMap(obj: any): obj is Map { + return obj instanceof Map || Object.prototype.toString.call(obj) === '[object Map]'; +} + +function isSet(obj: any): obj is Set { + return obj instanceof Set || Object.prototype.toString.call(obj) === '[object Set]'; +} + function isRegExp(obj: any): obj is RegExp { return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]'; } diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 87bb6e0fd9708..816a4fb81e517 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -58,6 +58,8 @@ scheme.SerializedValue = tObject({ d: tOptional(tString), u: tOptional(tString), bi: tOptional(tString), + m: tOptional(tType('SerializedValue')), + se: tOptional(tType('SerializedValue')), 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 6ba427eb2b439..0a49410110b8d 100644 --- a/packages/playwright-core/src/server/isomorphic/utilityScriptSerializers.ts +++ b/packages/playwright-core/src/server/isomorphic/utilityScriptSerializers.ts @@ -20,6 +20,8 @@ export type SerializedValue = { d: string } | { u: string } | { bi: string } | + { m: SerializedValue } | + { se: SerializedValue } | { r: { p: string, f: string} } | { a: SerializedValue[], id: number } | { o: { k: string, v: SerializedValue }[], id: number } | @@ -35,6 +37,14 @@ type VisitorInfo = { export function source() { + function isMap(obj: any): obj is Map { + return obj instanceof Map || Object.prototype.toString.call(obj) === '[object Map]'; + } + + function isSet(obj: any): obj is Set { + return obj instanceof Set || Object.prototype.toString.call(obj) === '[object Set]'; + } + function isRegExp(obj: any): obj is RegExp { try { return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]'; @@ -94,6 +104,10 @@ export function source() { return new URL(value.u); if ('bi' in value) return BigInt(value.bi); + if ('m' in value) + return new Map(parseEvaluationResultValue(value.m)); + if ('se' in value) + return new Set(parseEvaluationResultValue(value.se)); if ('r' in value) return new RegExp(value.r.p, value.r.f); if ('a' in value) { @@ -163,6 +177,11 @@ export function source() { if (typeof value === 'bigint') return { bi: value.toString() }; + if (isMap(value)) + return { m: serialize(Array.from(value), handleSerializer, visitorInfo) }; + if (isSet(value)) + return { se: serialize(Array.from(value), handleSerializer, visitorInfo) }; + if (isError(value)) { const error = value; if (error.stack?.startsWith(error.name + ': ' + error.message)) { diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index f55a4a5de9261..aee5fc944898e 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -180,6 +180,8 @@ export type SerializedValue = { d?: string, u?: string, bi?: string, + m?: SerializedValue, + se?: SerializedValue, r?: { p: string, f: string, diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 0ca61c7cb2085..d371813cbeef5 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -82,6 +82,10 @@ SerializedValue: u: string? # String representation of BigInt. bi: string? + # JS representation of Map: [[key1, value1], [key2, value2], ...]. + m: SerializedValue? + # JS representation of Set: [item1, item2, ...]. + se: SerializedValue? # 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 db1120d6fe6b4..2f3d7f18d9767 100644 --- a/tests/page/page-evaluate.spec.ts +++ b/tests/page/page-evaluate.spec.ts @@ -99,9 +99,14 @@ it('should transfer bigint', async ({ page }) => { expect(await page.evaluate(a => a, 17n)).toBe(17n); }); -it('should transfer maps as empty objects', async ({ page }) => { - const result = await page.evaluate(a => a.x.constructor.name + ' ' + JSON.stringify(a.x), { x: new Map([[1, 2]]) }); - expect(result).toBe('Object {}'); +it('should transfer maps', async ({ page }) => { + expect(await page.evaluate(() => new Map([[1, { test: 42n }]]))).toEqual(new Map([[1, { test: 42n }]])); + expect(await page.evaluate(a => a, new Map([[1, { test: 17n }]]))).toEqual(new Map([[1, { test: 17n }]])); +}); + +it('should transfer sets', async ({ page }) => { + expect(await page.evaluate(() => new Set([1, { test: 42n }]))).toEqual(new Set([1, { test: 42n }])); + expect(await page.evaluate(a => a, new Set([1, { test: 17n }]))).toEqual(new Set([1, { test: 17n }])); }); it('should modify global environment', async ({ page }) => {