feat(evaluate): survive null JSON in WK and FF (#352)

This commit is contained in:
Pavel Feldman 2020-01-02 15:06:28 -08:00 committed by GitHub
parent 682e2be15f
commit 0fab90be79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 67 additions and 60 deletions

View File

@ -9,7 +9,7 @@
"main": "index.js",
"playwright": {
"chromium_revision": "724623",
"firefox_revision": "1008",
"firefox_revision": "1009",
"webkit_revision": "1055"
},
"scripts": {

View File

@ -69,7 +69,6 @@ export class CRBrowser extends browser.Browser {
}
_createBrowserContext(contextId: string | null, options: BrowserContextOptions): BrowserContext {
let overrides: CROverrides | null = null;
const context = new BrowserContext({
pages: async (): Promise<Page[]> => {
const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page');
@ -81,28 +80,7 @@ export class CRBrowser extends browser.Browser {
const { targetId } = await this._client.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined });
const target = this._targets.get(targetId);
assert(await target._initializedPromise, 'Failed to create target for page');
const page = await target.page();
const crPage = target._crPage;
const session = crPage._client;
const promises: Promise<any>[] = [ overrides._applyOverrides(crPage) ];
if (options.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (options.ignoreHTTPSErrors)
promises.push(session.send('Security.setIgnoreCertificateErrors', { ignore: true }));
if (options.viewport)
promises.push(crPage.setViewport(options.viewport));
if (options.javaScriptEnabled === false)
promises.push(session.send('Emulation.setScriptExecutionDisabled', { value: true }));
if (options.userAgent)
crPage._networkManager.setUserAgent(options.userAgent);
if (options.mediaType || options.colorScheme) {
const features = options.colorScheme ? [{ name: 'prefers-color-scheme', value: options.colorScheme }] : [];
promises.push(session.send('Emulation.setEmulatedMedia', { media: options.mediaType || '', features }));
}
if (options.timezoneId)
promises.push(emulateTimezone(session, options.timezoneId));
await Promise.all(promises);
return page;
return target.page();
},
close: async (): Promise<void> => {
@ -162,8 +140,7 @@ export class CRBrowser extends browser.Browser {
}
}, options);
overrides = new CROverrides(context);
(context as any).overrides = overrides;
(context as any).overrides = new CROverrides(context);
return context;
}
@ -317,13 +294,3 @@ export class CRBrowser extends browser.Browser {
return !this._connection._closed;
}
}
async function emulateTimezone(session: CRSession, timezoneId: string) {
try {
await session.send('Emulation.setTimezoneOverride', { timezoneId: timezoneId });
} catch (exception) {
if (exception.message.includes('Invalid timezone'))
throw new Error(`Invalid timezone ID: ${timezoneId}`);
throw exception;
}
}

View File

@ -38,6 +38,7 @@ import { BrowserContext } from '../browserContext';
import * as types from '../types';
import * as input from '../input';
import { ConsoleMessage } from '../console';
import { CROverrides } from './features/crOverrides';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -86,13 +87,32 @@ export class CRPage implements PageDelegate {
this._client.send('Page.getFrameTree'),
]);
this._handleFrameTree(frameTree);
await Promise.all([
const promises: Promise<any>[] = [
this._client.send('Log.enable', {}),
this._client.send('Page.setInterceptFileChooserDialog', {enabled: true}),
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
this._networkManager.initialize(),
]);
((this._page.browserContext() as any).overrides as CROverrides)._applyOverrides(this),
];
const options = this._page.browserContext()._options;
if (options.bypassCSP)
promises.push(this._client.send('Page.setBypassCSP', { enabled: true }));
if (options.ignoreHTTPSErrors)
promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true }));
if (options.viewport)
promises.push(this.setViewport(options.viewport));
if (options.javaScriptEnabled === false)
promises.push(this._client.send('Emulation.setScriptExecutionDisabled', { value: true }));
if (options.userAgent)
this._networkManager.setUserAgent(options.userAgent);
if (options.mediaType || options.colorScheme) {
const features = options.colorScheme ? [{ name: 'prefers-color-scheme', value: options.colorScheme }] : [];
promises.push(this._client.send('Emulation.setEmulatedMedia', { media: options.mediaType || '', features }));
}
if (options.timezoneId)
promises.push(emulateTimezone(this._client, options.timezoneId));
await Promise.all(promises);
}
didClose() {
@ -488,3 +508,13 @@ export class ChromiumPage extends Page {
function toRemoteObject(handle: dom.ElementHandle): Protocol.Runtime.RemoteObject {
return handle._remoteObject as Protocol.Runtime.RemoteObject;
}
async function emulateTimezone(session: CRSession, timezoneId: string) {
try {
await session.send('Emulation.setTimezoneOverride', { timezoneId: timezoneId });
} catch (exception) {
if (exception.message.includes('Invalid timezone'))
throw new Error(`Invalid timezone ID: ${timezoneId}`);
throw exception;
}
}

View File

@ -155,21 +155,7 @@ export class FFBrowser extends browser.Browser {
browserContextId: browserContextId || undefined
});
const target = this._targets.get(targetId);
const page = await target.page();
const session = target._ffPage._session;
const promises: Promise<any>[] = [];
if (options.viewport)
promises.push(page._delegate.setViewport(options.viewport));
if (options.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (options.javaScriptEnabled === false)
promises.push(session.send('Page.setJavascriptEnabled', { enabled: false }));
if (options.userAgent)
promises.push(session.send('Page.setUserAgent', { userAgent: options.userAgent }));
if (options.mediaType || options.colorScheme)
promises.push(session.send('Page.setEmulatedMedia', { type: options.mediaType, colorScheme: options.colorScheme }));
await Promise.all(promises);
return page;
return target.page();
},
close: async (): Promise<void> => {

View File

@ -68,12 +68,24 @@ export class FFPage implements PageDelegate {
}
async _initialize() {
await Promise.all([
const promises: Promise<any>[] = [
this._session.send('Runtime.enable'),
this._session.send('Network.enable'),
this._session.send('Page.enable'),
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true })
]);
];
const options = this._page.browserContext()._options;
if (options.viewport)
promises.push(this.setViewport(options.viewport));
if (options.bypassCSP)
promises.push(this._session.send('Page.setBypassCSP', { enabled: true }));
if (options.javaScriptEnabled === false)
promises.push(this._session.send('Page.setJavascriptEnabled', { enabled: false }));
if (options.userAgent)
promises.push(this._session.send('Page.setUserAgent', { userAgent: options.userAgent }));
if (options.mediaType || options.colorScheme)
promises.push(this._session.send('Page.setEmulatedMedia', { type: options.mediaType, colorScheme: options.colorScheme }));
await Promise.all(promises);
}
_onExecutionContextCreated({executionContextId, auxData}) {

View File

@ -30,6 +30,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
_contextId: number;
private _contextDestroyedCallback: () => void;
private _executionContextDestroyedPromise: Promise<unknown>;
_jsonObjectId: Protocol.Runtime.RemoteObjectId | undefined;
constructor(client: WKTargetSession, contextPayload: Protocol.Runtime.ExecutionContextDescription) {
this._session = client;
@ -218,7 +219,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
}
private _returnObjectByValue(objectId: Protocol.Runtime.RemoteObjectId) {
const serializeFunction = function() {
const serializeFunction = function(JSON: { stringify: (o: any) => string }) {
try {
return JSON.stringify(this);
} catch (e) {
@ -231,6 +232,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
// Serialize object using standard JSON implementation to correctly pass 'undefined'.
functionDeclaration: serializeFunction + '\n' + suffix + '\n',
objectId: objectId,
arguments: [ { objectId: this._jsonObjectId } ],
returnByValue: true
}).catch(e => {
if (isSwappedOutError(e))

View File

@ -36,6 +36,8 @@ import { PNG } from 'pngjs';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
const JSON_CALL_MESSAGE = '__playwright_json_call__';
const JSON_SAVE_SCRIPT = `console.debug('${JSON_CALL_MESSAGE}', JSON)`;
export class WKPage implements PageDelegate {
readonly rawMouse: RawMouseImpl;
@ -47,7 +49,7 @@ export class WKPage implements PageDelegate {
private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private _isolatedWorlds: Set<string>;
private _sessionListeners: RegisteredListener[] = [];
private readonly _bootstrapScripts: string[] = [];
private readonly _bootstrapScripts: string[] = [ JSON_SAVE_SCRIPT ];
constructor(browser: WKBrowser, browserContext: BrowserContext) {
this._browser = browser;
@ -70,7 +72,7 @@ export class WKPage implements PageDelegate {
this._isolatedWorlds = new Set();
// New bootstrap scripts may have been added during provisional load, push them
// again to be on the safe side.
if (this._setBootstrapScripts.length)
if (this._bootstrapScripts.length)
this._setBootstrapScripts(session).catch(e => debugError(e));
}
@ -107,6 +109,8 @@ export class WKPage implements PageDelegate {
promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders));
if (this._page._state.viewport)
promises.push(WKPage._setViewport(session, this._page._state.viewport));
if (contextOptions.javaScriptEnabled !== false)
promises.push(session.send('Page.setBootstrapScript', { source: JSON_SAVE_SCRIPT }));
await Promise.all(promises);
}
@ -217,6 +221,12 @@ export class WKPage implements PageDelegate {
this._page._onBindingCalled(parameters[2].value, context);
return;
}
if (level === 'debug' && parameters && parameters[0].value === JSON_CALL_MESSAGE) {
const parsedObjectId = JSON.parse(parameters[1].objectId);
const context = this._contextIdToContext.get(parsedObjectId.injectedScriptId);
(context._delegate as WKExecutionContext)._jsonObjectId = parameters[1].objectId;
return;
}
let derivedType: string = type;
if (type === 'log')
derivedType = level;

View File

@ -259,10 +259,10 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROME, WEBKIT}) {
})).catch(e => error = e);
expect(error.message).toContain('Error in promise');
});
it.skip(FFOX || WEBKIT)('should work even when JSON is set to null', async({page, server}) => {
it('should work even when JSON is set to null', async ({ page }) => {
await page.evaluate(() => { window.JSON.stringify = null; window.JSON = null; });
const result = await page.evaluate(() => ({abc: 123}));
expect(result).toEqual({abc: 123});
expect(result).toEqual({abc: 123});
})
});