chore: serialize circular objects (#14045)
This commit is contained in:
parent
f0f65fa247
commit
cf89a36181
|
|
@ -84,7 +84,7 @@ export function serializeArgument(arg: any): channels.SerializedArgument {
|
||||||
if (value instanceof JSHandle)
|
if (value instanceof JSHandle)
|
||||||
return { h: pushHandle(value._channel) };
|
return { h: pushHandle(value._channel) };
|
||||||
return { fallThrough: value };
|
return { fallThrough: value };
|
||||||
}, new Set());
|
});
|
||||||
return { value, handles };
|
return { value, handles };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,8 @@ export type SerializedValue = {
|
||||||
v: SerializedValue,
|
v: SerializedValue,
|
||||||
}[],
|
}[],
|
||||||
h?: number,
|
h?: number,
|
||||||
|
id?: number,
|
||||||
|
ref?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SerializedArgument = {
|
export type SerializedArgument = {
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,10 @@ SerializedValue:
|
||||||
v: SerializedValue
|
v: SerializedValue
|
||||||
# An index in the handles array from SerializedArgument.
|
# An index in the handles array from SerializedArgument.
|
||||||
h: number?
|
h: number?
|
||||||
|
# Index of the object in value-type for circular reference resolution.
|
||||||
|
id: number?
|
||||||
|
# Ref to the object in value-type for circular reference resolution.
|
||||||
|
ref: number?
|
||||||
|
|
||||||
|
|
||||||
# Represents a value with handle references.
|
# Represents a value with handle references.
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import type { SerializedError, SerializedValue } from './channels';
|
||||||
export function serializeError(e: any): SerializedError {
|
export function serializeError(e: any): SerializedError {
|
||||||
if (isError(e))
|
if (isError(e))
|
||||||
return { error: { message: e.message, stack: e.stack, name: e.name } };
|
return { error: { message: e.message, stack: e.stack, name: e.name } };
|
||||||
return { value: serializeValue(e, value => ({ fallThrough: value }), new Set()) };
|
return { value: serializeValue(e, value => ({ fallThrough: value })) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseError(error: SerializedError): Error {
|
export function parseError(error: SerializedError): Error {
|
||||||
|
|
@ -41,6 +41,12 @@ export function parseError(error: SerializedError): Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseSerializedValue(value: SerializedValue, handles: any[] | undefined): any {
|
export function parseSerializedValue(value: SerializedValue, handles: any[] | undefined): any {
|
||||||
|
return innerParseSerializedValue(value, handles, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
function innerParseSerializedValue(value: SerializedValue, handles: any[] | undefined, refs: Map<number, object>): any {
|
||||||
|
if (value.ref !== undefined)
|
||||||
|
return refs.get(value.ref);
|
||||||
if (value.n !== undefined)
|
if (value.n !== undefined)
|
||||||
return value.n;
|
return value.n;
|
||||||
if (value.s !== undefined)
|
if (value.s !== undefined)
|
||||||
|
|
@ -65,12 +71,19 @@ export function parseSerializedValue(value: SerializedValue, handles: any[] | un
|
||||||
return new Date(value.d);
|
return new Date(value.d);
|
||||||
if (value.r !== undefined)
|
if (value.r !== undefined)
|
||||||
return new RegExp(value.r.p, value.r.f);
|
return new RegExp(value.r.p, value.r.f);
|
||||||
if (value.a !== undefined)
|
|
||||||
return value.a.map((a: any) => parseSerializedValue(a, handles));
|
if (value.a !== undefined) {
|
||||||
|
const result: any[] = [];
|
||||||
|
refs.set(value.id!, result);
|
||||||
|
for (const v of value.a)
|
||||||
|
result.push(innerParseSerializedValue(v, handles, refs));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
if (value.o !== undefined) {
|
if (value.o !== undefined) {
|
||||||
const result: any = {};
|
const result: any = {};
|
||||||
|
refs.set(value.id!, result);
|
||||||
for (const { k, v } of value.o)
|
for (const { k, v } of value.o)
|
||||||
result[k] = parseSerializedValue(v, handles);
|
result[k] = innerParseSerializedValue(v, handles, refs);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (value.h !== undefined) {
|
if (value.h !== undefined) {
|
||||||
|
|
@ -82,15 +95,22 @@ export function parseSerializedValue(value: SerializedValue, handles: any[] | un
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HandleOrValue = { h: number } | { fallThrough: any };
|
export type HandleOrValue = { h: number } | { fallThrough: any };
|
||||||
export function serializeValue(value: any, handleSerializer: (value: any) => HandleOrValue, visited: Set<any>): SerializedValue {
|
type VisitorInfo = {
|
||||||
|
visited: Map<object, number>;
|
||||||
|
lastId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function serializeValue(value: any, handleSerializer: (value: any) => HandleOrValue): SerializedValue {
|
||||||
|
return innerSerializeValue(value, handleSerializer, { lastId: 0, visited: new Map() });
|
||||||
|
}
|
||||||
|
|
||||||
|
function innerSerializeValue(value: any, handleSerializer: (value: any) => HandleOrValue, visitorInfo: VisitorInfo): SerializedValue {
|
||||||
const handle = handleSerializer(value);
|
const handle = handleSerializer(value);
|
||||||
if ('fallThrough' in handle)
|
if ('fallThrough' in handle)
|
||||||
value = handle.fallThrough;
|
value = handle.fallThrough;
|
||||||
else
|
else
|
||||||
return handle;
|
return handle;
|
||||||
|
|
||||||
if (visited.has(value))
|
|
||||||
throw new Error('Argument is a circular structure');
|
|
||||||
if (typeof value === 'symbol')
|
if (typeof value === 'symbol')
|
||||||
return { v: 'undefined' };
|
return { v: 'undefined' };
|
||||||
if (Object.is(value, undefined))
|
if (Object.is(value, undefined))
|
||||||
|
|
@ -123,21 +143,26 @@ export function serializeValue(value: any, handleSerializer: (value: any) => Han
|
||||||
return { d: value.toJSON() };
|
return { d: value.toJSON() };
|
||||||
if (isRegExp(value))
|
if (isRegExp(value))
|
||||||
return { r: { p: value.source, f: value.flags } };
|
return { r: { p: value.source, f: value.flags } };
|
||||||
|
|
||||||
|
const id = visitorInfo.visited.get(value);
|
||||||
|
if (id)
|
||||||
|
return { ref: id };
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const a = [];
|
const a = [];
|
||||||
visited.add(value);
|
const id = ++visitorInfo.lastId;
|
||||||
|
visitorInfo.visited.set(value, id);
|
||||||
for (let i = 0; i < value.length; ++i)
|
for (let i = 0; i < value.length; ++i)
|
||||||
a.push(serializeValue(value[i], handleSerializer, visited));
|
a.push(innerSerializeValue(value[i], handleSerializer, visitorInfo));
|
||||||
visited.delete(value);
|
return { a, id };
|
||||||
return { a };
|
|
||||||
}
|
}
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
const o: { k: string, v: SerializedValue }[] = [];
|
const o: { k: string, v: SerializedValue }[] = [];
|
||||||
visited.add(value);
|
const id = ++visitorInfo.lastId;
|
||||||
|
visitorInfo.visited.set(value, id);
|
||||||
for (const name of Object.keys(value))
|
for (const name of Object.keys(value))
|
||||||
o.push({ k: name, v: serializeValue(value[name], handleSerializer, visited) });
|
o.push({ k: name, v: innerSerializeValue(value[name], handleSerializer, visitorInfo) });
|
||||||
visited.delete(value);
|
return { o, id };
|
||||||
return { o };
|
|
||||||
}
|
}
|
||||||
throw new Error('Unexpected value');
|
throw new Error('Unexpected value');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
v: tType('SerializedValue'),
|
v: tType('SerializedValue'),
|
||||||
}))),
|
}))),
|
||||||
h: tOptional(tNumber),
|
h: tOptional(tNumber),
|
||||||
|
id: tOptional(tNumber),
|
||||||
|
ref: tOptional(tNumber),
|
||||||
});
|
});
|
||||||
scheme.SerializedArgument = tObject({
|
scheme.SerializedArgument = tObject({
|
||||||
value: tType('SerializedValue'),
|
value: tType('SerializedValue'),
|
||||||
|
|
|
||||||
|
|
@ -74,5 +74,5 @@ export function parseValue(v: channels.SerializedValue): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeResult(arg: any): channels.SerializedValue {
|
export function serializeResult(arg: any): channels.SerializedValue {
|
||||||
return serializeValue(arg, value => ({ fallThrough: value }), new Set());
|
return serializeValue(arg, value => ({ fallThrough: value }));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,19 @@ export type SerializedValue =
|
||||||
{ v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } |
|
{ v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } |
|
||||||
{ d: string } |
|
{ d: string } |
|
||||||
{ r: { p: string, f: string} } |
|
{ r: { p: string, f: string} } |
|
||||||
{ a: SerializedValue[] } |
|
{ a: SerializedValue[], id: number } |
|
||||||
{ o: { k: string, v: SerializedValue }[] } |
|
{ o: { k: string, v: SerializedValue }[], id: number } |
|
||||||
|
{ ref: number } |
|
||||||
{ h: number };
|
{ h: number };
|
||||||
|
|
||||||
export type HandleOrValue = { h: number } | { fallThrough: any };
|
export type HandleOrValue = { h: number } | { fallThrough: any };
|
||||||
|
|
||||||
export function source(aliasComplexAndCircularObjects: boolean = false) {
|
type VisitorInfo = {
|
||||||
|
visited: Map<object, number>;
|
||||||
|
lastId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function source() {
|
||||||
|
|
||||||
function isRegExp(obj: any): obj is RegExp {
|
function isRegExp(obj: any): obj is RegExp {
|
||||||
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
|
return obj instanceof RegExp || Object.prototype.toString.call(obj) === '[object RegExp]';
|
||||||
|
|
@ -39,10 +45,12 @@ export function source(aliasComplexAndCircularObjects: boolean = false) {
|
||||||
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
|
return obj instanceof Error || (obj && obj.__proto__ && obj.__proto__.name === 'Error');
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEvaluationResultValue(value: SerializedValue, handles: any[] = []): any {
|
function parseEvaluationResultValue(value: SerializedValue, handles: any[] = [], refs: Map<number, object> = new Map()): any {
|
||||||
if (Object.is(value, undefined))
|
if (Object.is(value, undefined))
|
||||||
return undefined;
|
return undefined;
|
||||||
if (typeof value === 'object' && value) {
|
if (typeof value === 'object' && value) {
|
||||||
|
if ('ref' in value)
|
||||||
|
return refs.get(value.ref);
|
||||||
if ('v' in value) {
|
if ('v' in value) {
|
||||||
if (value.v === 'undefined')
|
if (value.v === 'undefined')
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
@ -62,12 +70,18 @@ export function source(aliasComplexAndCircularObjects: boolean = false) {
|
||||||
return new Date(value.d);
|
return new Date(value.d);
|
||||||
if ('r' in value)
|
if ('r' in value)
|
||||||
return new RegExp(value.r.p, value.r.f);
|
return new RegExp(value.r.p, value.r.f);
|
||||||
if ('a' in value)
|
if ('a' in value) {
|
||||||
return value.a.map((a: any) => parseEvaluationResultValue(a, handles));
|
const result: any[] = [];
|
||||||
|
refs.set(value.id, result);
|
||||||
|
for (const a of value.a)
|
||||||
|
result.push(parseEvaluationResultValue(a, handles, refs));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
if ('o' in value) {
|
if ('o' in value) {
|
||||||
const result: any = {};
|
const result: any = {};
|
||||||
|
refs.set(value.id, result);
|
||||||
for (const { k, v } of value.o)
|
for (const { k, v } of value.o)
|
||||||
result[k] = parseEvaluationResultValue(v, handles);
|
result[k] = parseEvaluationResultValue(v, handles, refs);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if ('h' in value)
|
if ('h' in value)
|
||||||
|
|
@ -77,21 +91,10 @@ export function source(aliasComplexAndCircularObjects: boolean = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeAsCallArgument(value: any, handleSerializer: (value: any) => HandleOrValue): SerializedValue {
|
function serializeAsCallArgument(value: any, handleSerializer: (value: any) => HandleOrValue): SerializedValue {
|
||||||
return serialize(value, handleSerializer, new Set());
|
return serialize(value, handleSerializer, { visited: new Map(), lastId: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function serialize(value: any, handleSerializer: (value: any) => HandleOrValue, visited: Set<any>): SerializedValue {
|
function serialize(value: any, handleSerializer: (value: any) => HandleOrValue, visitorInfo: VisitorInfo): SerializedValue {
|
||||||
if (!aliasComplexAndCircularObjects)
|
|
||||||
return innerSerialize(value, handleSerializer, visited);
|
|
||||||
try {
|
|
||||||
const alias = serializeComplexObjectAsAlias(value);
|
|
||||||
return alias || innerSerialize(value, handleSerializer, visited);
|
|
||||||
} catch (error) {
|
|
||||||
return error.stack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeComplexObjectAsAlias(value: any): string | undefined {
|
|
||||||
if (value && typeof value === 'object') {
|
if (value && typeof value === 'object') {
|
||||||
if (globalThis.Window && value instanceof globalThis.Window)
|
if (globalThis.Window && value instanceof globalThis.Window)
|
||||||
return 'ref: <Window>';
|
return 'ref: <Window>';
|
||||||
|
|
@ -100,22 +103,16 @@ export function source(aliasComplexAndCircularObjects: boolean = false) {
|
||||||
if (globalThis.Node && value instanceof globalThis.Node)
|
if (globalThis.Node && value instanceof globalThis.Node)
|
||||||
return 'ref: <Node>';
|
return 'ref: <Node>';
|
||||||
}
|
}
|
||||||
|
return innerSerialize(value, handleSerializer, visitorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
function innerSerialize(value: any, handleSerializer: (value: any) => HandleOrValue, visited: Set<any>): SerializedValue {
|
function innerSerialize(value: any, handleSerializer: (value: any) => HandleOrValue, visitorInfo: VisitorInfo): SerializedValue {
|
||||||
const result = handleSerializer(value);
|
const result = handleSerializer(value);
|
||||||
if ('fallThrough' in result)
|
if ('fallThrough' in result)
|
||||||
value = result.fallThrough;
|
value = result.fallThrough;
|
||||||
else
|
else
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (visited.has(value)) {
|
|
||||||
if (aliasComplexAndCircularObjects) {
|
|
||||||
const alias = serializeComplexObjectAsAlias(value);
|
|
||||||
return alias || '[Circular Ref]';
|
|
||||||
}
|
|
||||||
throw new Error('Argument is a circular structure');
|
|
||||||
}
|
|
||||||
if (typeof value === 'symbol')
|
if (typeof value === 'symbol')
|
||||||
return { v: 'undefined' };
|
return { v: 'undefined' };
|
||||||
if (Object.is(value, undefined))
|
if (Object.is(value, undefined))
|
||||||
|
|
@ -151,18 +148,23 @@ export function source(aliasComplexAndCircularObjects: boolean = false) {
|
||||||
if (isRegExp(value))
|
if (isRegExp(value))
|
||||||
return { r: { p: value.source, f: value.flags } };
|
return { r: { p: value.source, f: value.flags } };
|
||||||
|
|
||||||
|
const id = visitorInfo.visited.get(value);
|
||||||
|
if (id)
|
||||||
|
return { ref: id };
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
const a = [];
|
const a = [];
|
||||||
visited.add(value);
|
const id = ++visitorInfo.lastId;
|
||||||
|
visitorInfo.visited.set(value, id);
|
||||||
for (let i = 0; i < value.length; ++i)
|
for (let i = 0; i < value.length; ++i)
|
||||||
a.push(serialize(value[i], handleSerializer, visited));
|
a.push(serialize(value[i], handleSerializer, visitorInfo));
|
||||||
visited.delete(value);
|
return { a, id };
|
||||||
return { a };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === 'object') {
|
if (typeof value === 'object') {
|
||||||
const o: { k: string, v: SerializedValue }[] = [];
|
const o: { k: string, v: SerializedValue }[] = [];
|
||||||
visited.add(value);
|
const id = ++visitorInfo.lastId;
|
||||||
|
visitorInfo.visited.set(value, id);
|
||||||
for (const name of Object.keys(value)) {
|
for (const name of Object.keys(value)) {
|
||||||
let item;
|
let item;
|
||||||
try {
|
try {
|
||||||
|
|
@ -171,12 +173,11 @@ export function source(aliasComplexAndCircularObjects: boolean = false) {
|
||||||
continue; // native bindings will throw sometimes
|
continue; // native bindings will throw sometimes
|
||||||
}
|
}
|
||||||
if (name === 'toJSON' && typeof item === 'function')
|
if (name === 'toJSON' && typeof item === 'function')
|
||||||
o.push({ k: name, v: { o: [] } });
|
o.push({ k: name, v: { o: [], id: 0 } });
|
||||||
else
|
else
|
||||||
o.push({ k: name, v: serialize(item, handleSerializer, visited) });
|
o.push({ k: name, v: serialize(item, handleSerializer, visitorInfo) });
|
||||||
}
|
}
|
||||||
visited.delete(value);
|
return { o, id };
|
||||||
return { o };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -729,7 +729,7 @@ export class PageBinding {
|
||||||
constructor(name: string, playwrightFunction: frames.FunctionWithSource, needsHandle: boolean) {
|
constructor(name: string, playwrightFunction: frames.FunctionWithSource, needsHandle: boolean) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.playwrightFunction = playwrightFunction;
|
this.playwrightFunction = playwrightFunction;
|
||||||
this.source = `(${addPageBinding.toString()})(${JSON.stringify(name)}, ${needsHandle}, (${source})(true))`;
|
this.source = `(${addPageBinding.toString()})(${JSON.stringify(name)}, ${needsHandle}, (${source})())`;
|
||||||
this.needsHandle = needsHandle;
|
this.needsHandle = needsHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -743,7 +743,7 @@ export class PageBinding {
|
||||||
const handle = await context.evaluateHandle(takeHandle, { name, seq }).catch(e => null);
|
const handle = await context.evaluateHandle(takeHandle, { name, seq }).catch(e => null);
|
||||||
result = await binding.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, handle);
|
result = await binding.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, handle);
|
||||||
} else {
|
} else {
|
||||||
const args = serializedArgs!.map(a => parseEvaluationResultValue(a, []));
|
const args = serializedArgs!.map(a => parseEvaluationResultValue(a));
|
||||||
result = await binding.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args);
|
result = await binding.playwrightFunction({ frame: context.frame, page, context: page._browserContext }, ...args);
|
||||||
}
|
}
|
||||||
context.evaluate(deliverResult, { name, seq, result }).catch(e => debugLogger.log('error', e));
|
context.evaluate(deliverResult, { name, seq, result }).catch(e => debugLogger.log('error', e));
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,9 @@ it('should work with dates', async ({ page }) => {
|
||||||
expect(date.toJSON()).toBe('2017-09-26T00:00:00.000Z');
|
expect(date.toJSON()).toBe('2017-09-26T00:00:00.000Z');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw for circular objects', async ({ page }) => {
|
it('should handle circular objects', async ({ page }) => {
|
||||||
const windowHandle = await page.evaluateHandle('window');
|
const handle = await page.evaluateHandle('const a = {}; a.b = a; a');
|
||||||
let error = null;
|
const a: any = {};
|
||||||
await windowHandle.jsonValue().catch(e => error = e);
|
a.b = a;
|
||||||
expect(error.message).toContain('Argument is a circular structure');
|
expect(await handle.jsonValue()).toEqual(a);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -63,13 +63,6 @@ it('should accept multiple nested handles', async ({ page }) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw for circular objects', async ({ page }) => {
|
|
||||||
const a = { x: 1 };
|
|
||||||
a['y'] = a;
|
|
||||||
const error = await page.evaluate(x => x, a).catch(e => e);
|
|
||||||
expect(error.message).toContain('Argument is a circular structure');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should accept same handle multiple times', async ({ page }) => {
|
it('should accept same handle multiple times', async ({ page }) => {
|
||||||
const foo = await page.evaluateHandle(() => 1);
|
const foo = await page.evaluateHandle(() => 1);
|
||||||
expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo } })).toEqual({ foo: 1, bar: [1], baz: { foo: 1 } });
|
expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo } })).toEqual({ foo: 1, bar: [1], baz: { foo: 1 } });
|
||||||
|
|
|
||||||
|
|
@ -331,17 +331,23 @@ it('should properly serialize null fields', async ({ page }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return undefined for non-serializable objects', async ({ page }) => {
|
it('should return undefined for non-serializable objects', async ({ page }) => {
|
||||||
expect(await page.evaluate(() => window)).toBe(undefined);
|
expect(await page.evaluate(() => function() {})).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail for circular object', async ({ page }) => {
|
it('should alias Window, Document and Node', async ({ page }) => {
|
||||||
|
const object = await page.evaluate('[window, document, document.body]');
|
||||||
|
expect(object).toEqual(['ref: <Window>', 'ref: <Document>', 'ref: <Node>']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for circular object', async ({ page }) => {
|
||||||
const result = await page.evaluate(() => {
|
const result = await page.evaluate(() => {
|
||||||
const a = {} as any;
|
const a = {} as any;
|
||||||
const b = { a };
|
a.b = a;
|
||||||
a.b = b;
|
|
||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
expect(result).toBe(undefined);
|
const a = {} as any;
|
||||||
|
a.b = a;
|
||||||
|
expect(result).toEqual(a);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to throw a tricky error', async ({ page }) => {
|
it('should be able to throw a tricky error', async ({ page }) => {
|
||||||
|
|
|
||||||
|
|
@ -292,11 +292,11 @@ it('should alias Window, Document and Node', async ({ page }) => {
|
||||||
expect(object).toEqual(['ref: <Window>', 'ref: <Document>', 'ref: <Node>']);
|
expect(object).toEqual(['ref: <Window>', 'ref: <Document>', 'ref: <Node>']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trim cycles', async ({ page }) => {
|
it('should serialize cycles', async ({ page }) => {
|
||||||
let object: any;
|
let object: any;
|
||||||
await page.exposeBinding('log', (source, obj) => object = obj);
|
await page.exposeBinding('log', (source, obj) => object = obj);
|
||||||
await page.evaluate('const a = { a: 1 }; a.a = a; window.log(a)');
|
await page.evaluate('const a = {}; a.b = a; window.log(a)');
|
||||||
expect(object).toEqual({
|
const a: any = {};
|
||||||
a: '[Circular Ref]',
|
a.b = a;
|
||||||
});
|
expect(object).toEqual(a);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,7 @@ onChanges.push({
|
||||||
committed: false,
|
committed: false,
|
||||||
inputs: [
|
inputs: [
|
||||||
'packages/playwright-core/src/server/injected/**',
|
'packages/playwright-core/src/server/injected/**',
|
||||||
|
'packages/playwright-core/src/server/isomorphic/**',
|
||||||
'utils/generate_injected.js',
|
'utils/generate_injected.js',
|
||||||
],
|
],
|
||||||
script: 'utils/generate_injected.js',
|
script: 'utils/generate_injected.js',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue