fix(firefox): assorted fixes to evaluation and remote objects (#511)

This commit is contained in:
Dmitry Gozman 2020-01-16 15:24:37 -08:00 committed by GitHub
parent b4686f1eb9
commit 447d76d6cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 33 additions and 24 deletions

View file

@ -9,7 +9,7 @@
"main": "index.js", "main": "index.js",
"playwright": { "playwright": {
"chromium_revision": "724623", "chromium_revision": "724623",
"firefox_revision": "1013", "firefox_revision": "1014",
"webkit_revision": "1092" "webkit_revision": "1092"
}, },
"scripts": { "scripts": {

View file

@ -30,25 +30,15 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
} }
async evaluate(context: js.ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> { async evaluate(context: js.ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
if (returnByValue) {
try {
const handle = await this.evaluate(context, false /* returnByValue */, pageFunction, ...args as any);
const result = await handle.jsonValue();
await handle.dispose();
return result;
} catch (e) {
if (e.message.includes('cyclic object value') || e.message.includes('Object is not serializable'))
return undefined;
throw e;
}
}
if (helper.isString(pageFunction)) { if (helper.isString(pageFunction)) {
const payload = await this._session.send('Runtime.evaluate', { const payload = await this._session.send('Runtime.evaluate', {
expression: pageFunction.trim(), expression: pageFunction.trim(),
returnByValue,
executionContextId: this._executionContextId, executionContextId: this._executionContextId,
}).catch(rewriteError); }).catch(rewriteError);
checkException(payload.exceptionDetails); checkException(payload.exceptionDetails);
if (returnByValue)
return deserializeValue(payload.result!);
return context._createHandle(payload.result); return context._createHandle(payload.result);
} }
if (typeof pageFunction !== 'function') if (typeof pageFunction !== 'function')
@ -94,6 +84,7 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
callFunctionPromise = this._session.send('Runtime.callFunction', { callFunctionPromise = this._session.send('Runtime.callFunction', {
functionDeclaration: functionText, functionDeclaration: functionText,
args: protocolArgs, args: protocolArgs,
returnByValue,
executionContextId: this._executionContextId executionContextId: this._executionContextId
}); });
} catch (err) { } catch (err) {
@ -103,9 +94,13 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
} }
const payload = await callFunctionPromise.catch(rewriteError); const payload = await callFunctionPromise.catch(rewriteError);
checkException(payload.exceptionDetails); checkException(payload.exceptionDetails);
if (returnByValue)
return deserializeValue(payload.result!);
return context._createHandle(payload.result); return context._createHandle(payload.result);
function rewriteError(error: Error) : never { function rewriteError(error: Error): (Protocol.Runtime.evaluateReturnValue | Protocol.Runtime.callFunctionReturnValue) {
if (error.message.includes('cyclic object value') || error.message.includes('Object is not serializable'))
return {result: {type: 'undefined', value: undefined}};
if (error.message.includes('Failed to find execution context with id') || error.message.includes('Execution context was destroyed!')) if (error.message.includes('Failed to find execution context with id') || error.message.includes('Execution context was destroyed!'))
throw new Error('Execution context was destroyed, most likely because of a navigation.'); throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error; throw error;

View file

@ -306,17 +306,23 @@ export class FFPage implements PageDelegate {
} }
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> { async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
const { frameId } = await this._session.send('Page.contentFrame', { const { contentFrameId } = await this._session.send('Page.describeNode', {
frameId: handle._context.frame._id, frameId: handle._context.frame._id,
objectId: toRemoteObject(handle).objectId!, objectId: toRemoteObject(handle).objectId!,
}); });
if (!frameId) if (!contentFrameId)
return null; return null;
return this._page._frameManager.frame(frameId); return this._page._frameManager.frame(contentFrameId);
} }
async getOwnerFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> { async getOwnerFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
return handle._context.frame; const { ownerFrameId } = await this._session.send('Page.describeNode', {
frameId: handle._context.frame._id,
objectId: toRemoteObject(handle).objectId!,
});
if (!ownerFrameId)
return null;
return this._page._frameManager.frame(ownerFrameId);
} }
isElementHandle(remoteObject: any): boolean { isElementHandle(remoteObject: any): boolean {

View file

@ -134,7 +134,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1')); const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1'));
expect(await elementHandle.ownerFrame()).toBe(frame); expect(await elementHandle.ownerFrame()).toBe(frame);
}); });
it.skip(FFOX)('should work for cross-frame evaluations', async({page,server}) => { it('should work for cross-frame evaluations', async({page,server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame = page.mainFrame(); const frame = page.mainFrame();

View file

@ -61,8 +61,16 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
await page.goto(server.PREFIX + '/global-var.html'); await page.goto(server.PREFIX + '/global-var.html');
expect(await page.evaluate('globalVar')).toBe(123); expect(await page.evaluate('globalVar')).toBe(123);
}); });
it.skip(FFOX)('should return undefined for objects with symbols', async({page, server}) => { it('should return undefined for objects with symbols', async({page, server}) => {
expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined); expect(await page.evaluate(() => [Symbol('foo4')])).toBe(undefined);
expect(await page.evaluate(() => {
const a = { };
a[Symbol('foo4')] = 42;
return a;
})).toEqual({});
expect(await page.evaluate(() => {
return { foo: [{ a: Symbol('foo4') }] };
})).toBe(undefined);
}); });
it('should work with function shorthands', async({page, server}) => { it('should work with function shorthands', async({page, server}) => {
const a = { const a = {
@ -166,7 +174,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
it('should properly serialize undefined fields', async({page}) => { it('should properly serialize undefined fields', async({page}) => {
expect(await page.evaluate(() => ({a: undefined}))).toEqual({}); expect(await page.evaluate(() => ({a: undefined}))).toEqual({});
}); });
it.skip(FFOX)('should properly serialize null arguments', async({page}) => { it('should properly serialize null arguments', async({page}) => {
expect(await page.evaluate(x => x, null)).toEqual(null); expect(await page.evaluate(x => x, null)).toEqual(null);
}); });
it('should properly serialize null fields', async({page}) => { it('should properly serialize null fields', async({page}) => {
@ -184,7 +192,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
}); });
expect(result).toBe(undefined); expect(result).toBe(undefined);
}); });
it.skip(FFOX)('should be able to throw a tricky error', async({page, server}) => { it('should be able to throw a tricky error', async({page, server}) => {
const windowHandle = await page.evaluateHandle(() => window); const windowHandle = await page.evaluateHandle(() => window);
const errorText = await windowHandle.jsonValue().catch(e => e.message); const errorText = await windowHandle.jsonValue().catch(e => e.message);
const error = await page.evaluate(errorText => { const error = await page.evaluate(errorText => {

View file

@ -82,7 +82,7 @@ module.exports.describe = function({testRunner, expect, CHROMIUM, FFOX, WEBKIT})
const json = await aHandle.jsonValue(); const json = await aHandle.jsonValue();
expect(json).toEqual({foo: 'bar'}); expect(json).toEqual({foo: 'bar'});
}); });
it.skip(FFOX)('should not work with dates', async({page, server}) => { it('should not work with dates', async({page, server}) => {
const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z')); const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z'));
const json = await dateHandle.jsonValue(); const json = await dateHandle.jsonValue();
expect(json).toEqual({}); expect(json).toEqual({});