Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(popups): add BrowserContext.evaluateOnNewDocument #1136

Merged
merged 1 commit into from
Feb 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ await context.close();
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
- [browserContext.close()](#browsercontextclose)
- [browserContext.cookies([...urls])](#browsercontextcookiesurls)
- [browserContext.evaluateOnNewDocument(pageFunction[, ...args])](#browsercontextevaluateonnewdocumentpagefunction-args)
- [browserContext.newPage()](#browsercontextnewpage)
- [browserContext.pages()](#browsercontextpages)
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
Expand Down Expand Up @@ -308,7 +309,7 @@ context.clearPermissions();
Closes the browser context. All the targets that belong to the browser context
will be closed.

> **NOTE** only incognito browser contexts can be closed.
> **NOTE** the default browser context cannot be closed.

#### browserContext.cookies([...urls])
- `...urls` <...[string]>
Expand All @@ -327,7 +328,29 @@ will be closed.
If no URLs are specified, this method returns all cookies.
If URLs are specified, only cookies that affect those URLs are returned.

> **NOTE** the default browser context cannot be closed.
#### browserContext.evaluateOnNewDocument(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in all pages in the browser context
- `...args` <...[Serializable]> Arguments to pass to `pageFunction`
- returns: <[Promise]>

Adds a function which would be invoked in one of the following scenarios:
- Whenever a page is created in the browser context or is navigated.
- Whenever a child frame is attached or navigated in any page in the browser context. In this case, the function is invoked in the context of the newly attached frame.

The function is invoked after the document was created but before any of its scripts were run. This is useful to amend the JavaScript environment, e.g. to seed `Math.random`.

An example of overriding `Math.random` before the page loads:

```js
// preload.js
Math.random = () => 42;

// In your playwright script, assuming the preload.js file is in same folder
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
await browserContext.evaluateOnNewDocument(preloadFile);
```

> **NOTE** The order of evaluation of multiple scripts installed via [browserContext.evaluateOnNewDocument(pageFunction[, ...args])](#browsercontextevaluateonnewdocumentpagefunction-args) and [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args) is not defined.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make it deterministic, is it chromium backend limitation?


#### browserContext.newPage()
- returns: <[Promise]<[Page]>>
Expand Down Expand Up @@ -950,7 +973,7 @@ await resultHandle.dispose();
```

#### page.evaluateOnNewDocument(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
- `pageFunction` <[function]|[string]> Function to be evaluated in the page
- `...args` <...[Serializable]> Arguments to pass to `pageFunction`
- returns: <[Promise]>

Expand All @@ -960,23 +983,19 @@ Adds a function which would be invoked in one of the following scenarios:

The function is invoked after the document was created but before any of its scripts were run. This is useful to amend the JavaScript environment, e.g. to seed `Math.random`.

An example of overriding the navigator.languages property before the page loads:
An example of overriding `Math.random` before the page loads:

```js
// preload.js
Math.random = () => 42;

// overwrite the `languages` property to use a custom getter
Object.defineProperty(navigator, "languages", {
get: function() {
return ["en-US", "en", "bn"];
}
});

// In your playwright script, assuming the preload.js file is in same folder of our script
// In your playwright script, assuming the preload.js file is in same folder
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
await page.evaluateOnNewDocument(preloadFile);
```

> **NOTE** The order of evaluation of multiple scripts installed via [browserContext.evaluateOnNewDocument(pageFunction[, ...args])](#browsercontextevaluateonnewdocumentpagefunction-args) and [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args) is not defined.

#### page.exposeFunction(name, playwrightFunction)
- `name` <[string]> Name of the function on the window object
- `playwrightFunction` <[function]> Callback function which will be called in Playwright's context.
Expand Down Expand Up @@ -3588,6 +3607,7 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
- [browserContext.close()](#browsercontextclose)
- [browserContext.cookies([...urls])](#browsercontextcookiesurls)
- [browserContext.evaluateOnNewDocument(pageFunction[, ...args])](#browsercontextevaluateonnewdocumentpagefunction-args)
- [browserContext.newPage()](#browsercontextnewpage)
- [browserContext.pages()](#browsercontextpages)
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"main": "index.js",
"playwright": {
"chromium_revision": "744254",
"firefox_revision": "1031",
"firefox_revision": "1032",
"webkit_revision": "1162"
},
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions src/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface BrowserContext {
clearPermissions(): Promise<void>;
setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]): Promise<void>;
close(): Promise<void>;

_existingPages(): Page[];
Expand Down
9 changes: 9 additions & 0 deletions src/chromium/crBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
readonly _browserContextId: string | null;
readonly _options: BrowserContextOptions;
readonly _timeoutSettings: TimeoutSettings;
readonly _evaluateOnNewDocumentSources: string[];
private _closed = false;

constructor(browser: CRBrowser, browserContextId: string | null, options: BrowserContextOptions) {
Expand All @@ -195,6 +196,7 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
this._browserContextId = browserContextId;
this._timeoutSettings = new TimeoutSettings();
this._options = options;
this._evaluateOnNewDocumentSources = [];
}

async _initialize() {
Expand Down Expand Up @@ -300,6 +302,13 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
await (page._delegate as CRPage).updateExtraHTTPHeaders();
}

async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
const source = helper.evaluationString(pageFunction, ...args);
this._evaluateOnNewDocumentSources.push(source);
for (const page of this._existingPages())
await (page._delegate as CRPage).evaluateOnNewDocument(source);
}

async close() {
if (this._closed)
return;
Expand Down
9 changes: 6 additions & 3 deletions src/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ import { RawMouseImpl, RawKeyboardImpl } from './crInput';
import { getAccessibilityTree } from './crAccessibility';
import { CRCoverage } from './crCoverage';
import { CRPDF } from './crPdf';
import { CRBrowser } from './crBrowser';
import { BrowserContext } from '../browserContext';
import { CRBrowser, CRBrowserContext } from './crBrowser';
import * as types from '../types';
import { ConsoleMessage } from '../console';
import * as platform from '../platform';
Expand All @@ -54,14 +53,16 @@ export class CRPage implements PageDelegate {
private _browser: CRBrowser;
private _pdf: CRPDF;
private _coverage: CRCoverage;
private readonly _browserContext: CRBrowserContext;

constructor(client: CRSession, browser: CRBrowser, browserContext: BrowserContext) {
constructor(client: CRSession, browser: CRBrowser, browserContext: CRBrowserContext) {
this._client = client;
this._browser = browser;
this.rawKeyboard = new RawKeyboardImpl(client);
this.rawMouse = new RawMouseImpl(client);
this._pdf = new CRPDF(client);
this._coverage = new CRCoverage(client);
this._browserContext = browserContext;
this._page = new Page(this, browserContext);
this._networkManager = new CRNetworkManager(client, this._page);

Expand Down Expand Up @@ -119,6 +120,8 @@ export class CRPage implements PageDelegate {
if (options.geolocation)
promises.push(this._client.send('Emulation.setGeolocationOverride', options.geolocation));
promises.push(this.updateExtraHTTPHeaders());
for (const source of this._browserContext._evaluateOnNewDocumentSources)
promises.push(this.evaluateOnNewDocument(source));
await Promise.all(promises);
}

Expand Down
10 changes: 9 additions & 1 deletion src/firefox/ffBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { headersArray } from './ffNetworkManager';
export class FFBrowser extends platform.EventEmitter implements Browser {
_connection: FFConnection;
_targets: Map<string, Target>;
readonly _defaultContext: BrowserContext;
readonly _defaultContext: FFBrowserContext;
readonly _contexts: Map<string, FFBrowserContext>;
private _eventListeners: RegisteredListener[];

Expand Down Expand Up @@ -261,13 +261,15 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
readonly _options: BrowserContextOptions;
readonly _timeoutSettings: TimeoutSettings;
private _closed = false;
private readonly _evaluateOnNewDocumentSources: string[];

constructor(browser: FFBrowser, browserContextId: string | null, options: BrowserContextOptions) {
super();
this._browser = browser;
this._browserContextId = browserContextId;
this._timeoutSettings = new TimeoutSettings();
this._options = options;
this._evaluateOnNewDocumentSources = [];
}

async _initialize() {
Expand Down Expand Up @@ -357,6 +359,12 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(this._options.extraHTTPHeaders) });
}

async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
const source = helper.evaluationString(pageFunction, ...args);
this._evaluateOnNewDocumentSources.push(source);
await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source });
}

async close() {
if (this._closed)
return;
Expand Down
11 changes: 10 additions & 1 deletion src/webkit/wkBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
private readonly _connection: WKConnection;
private readonly _attachToDefaultContext: boolean;
readonly _browserSession: WKSession;
readonly _defaultContext: BrowserContext;
readonly _defaultContext: WKBrowserContext;
readonly _contexts = new Map<string, WKBrowserContext>();
readonly _pageProxies = new Map<string, WKPageProxy>();
private readonly _eventListeners: RegisteredListener[];
Expand Down Expand Up @@ -174,13 +174,15 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
readonly _options: BrowserContextOptions;
readonly _timeoutSettings: TimeoutSettings;
private _closed = false;
readonly _evaluateOnNewDocumentSources: string[];

constructor(browser: WKBrowser, browserContextId: string | undefined, options: BrowserContextOptions) {
super();
this._browser = browser;
this._browserContextId = browserContextId;
this._timeoutSettings = new TimeoutSettings();
this._options = options;
this._evaluateOnNewDocumentSources = [];
}

async _initialize() {
Expand Down Expand Up @@ -276,6 +278,13 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
await (page._delegate as WKPage).updateExtraHTTPHeaders();
}

async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
const source = helper.evaluationString(pageFunction, ...args);
this._evaluateOnNewDocumentSources.push(source);
for (const page of this._existingPages())
await (page._delegate as WKPage)._updateBootstrapScript();
}

async close() {
if (this._closed)
return;
Expand Down
24 changes: 13 additions & 11 deletions src/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ import { WKWorkers } from './wkWorkers';
import { Page, PageDelegate } from '../page';
import { Protocol } from './protocol';
import * as dialog from '../dialog';
import { BrowserContext } from '../browserContext';
import { RawMouseImpl, RawKeyboardImpl } from './wkInput';
import * as types from '../types';
import * as accessibility from '../accessibility';
import * as platform from '../platform';
import { getAccessibilityTree } from './wkAccessibility';
import { WKProvisionalPage } from './wkProvisionalPage';
import { WKPageProxy } from './wkPageProxy';
import { WKBrowserContext } from './wkBrowser';

const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
Expand All @@ -53,8 +53,9 @@ export class WKPage implements PageDelegate {
private _mainFrameContextId?: number;
private _sessionListeners: RegisteredListener[] = [];
private readonly _bootstrapScripts: string[] = [];
private readonly _browserContext: WKBrowserContext;

constructor(browserContext: BrowserContext, pageProxySession: WKSession, opener: WKPageProxy | null) {
constructor(browserContext: WKBrowserContext, pageProxySession: WKSession, opener: WKPageProxy | null) {
this._pageProxySession = pageProxySession;
this._opener = opener;
this.rawKeyboard = new RawKeyboardImpl(pageProxySession);
Expand All @@ -63,6 +64,7 @@ export class WKPage implements PageDelegate {
this._page = new Page(this, browserContext);
this._workers = new WKWorkers(this._page);
this._session = undefined as any as WKSession;
this._browserContext = browserContext;
this._page.on(Events.Page.FrameDetached, frame => this._removeContextsForFrame(frame, false));
}

Expand Down Expand Up @@ -137,10 +139,7 @@ export class WKPage implements PageDelegate {
promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
if (this._page._state.mediaType || this._page._state.colorScheme)
promises.push(WKPage._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme));
if (this._bootstrapScripts.length) {
const source = this._bootstrapScripts.join(';');
promises.push(session.send('Page.setBootstrapScript', { source }));
}
promises.push(session.send('Page.setBootstrapScript', { source: this._calculateBootstrapScript() }));
if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }));
Expand Down Expand Up @@ -465,18 +464,21 @@ export class WKPage implements PageDelegate {
async exposeBinding(name: string, bindingFunction: string): Promise<void> {
const script = `self.${name} = (param) => console.debug('${BINDING_CALL_MESSAGE}', {}, param); ${bindingFunction}`;
this._bootstrapScripts.unshift(script);
await this._setBootstrapScripts();
await this._updateBootstrapScript();
await Promise.all(this._page.frames().map(frame => frame.evaluate(script).catch(debugError)));
}

async evaluateOnNewDocument(script: string): Promise<void> {
this._bootstrapScripts.push(script);
await this._setBootstrapScripts();
await this._updateBootstrapScript();
}

private _calculateBootstrapScript(): string {
return [...this._browserContext._evaluateOnNewDocumentSources, ...this._bootstrapScripts].join(';');
}

private async _setBootstrapScripts() {
const source = this._bootstrapScripts.join(';');
await this._updateState('Page.setBootstrapScript', { source });
async _updateBootstrapScript(): Promise<void> {
await this._updateState('Page.setBootstrapScript', { source: this._calculateBootstrapScript() });
}

async closePage(runBeforeUnload: boolean): Promise<void> {
Expand Down
6 changes: 3 additions & 3 deletions src/webkit/wkPageProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
* limitations under the License.
*/

import { BrowserContext } from '../browserContext';
import { Page } from '../page';
import { Protocol } from './protocol';
import { WKSession } from './wkConnection';
import { WKPage } from './wkPage';
import { RegisteredListener, helper, assert, debugError } from '../helper';
import { Events } from '../events';
import { WKBrowserContext } from './wkBrowser';

const isPovisionalSymbol = Symbol('isPovisional');

export class WKPageProxy {
private readonly _pageProxySession: WKSession;
readonly _browserContext: BrowserContext;
readonly _browserContext: WKBrowserContext;
private readonly _opener: WKPageProxy | null;
private readonly _pagePromise: Promise<Page | null>;
private _pagePromiseFulfill: (page: Page | null) => void = () => {};
Expand All @@ -36,7 +36,7 @@ export class WKPageProxy {
private readonly _sessions = new Map<string, WKSession>();
private readonly _eventListeners: RegisteredListener[];

constructor(pageProxySession: WKSession, browserContext: BrowserContext, opener: WKPageProxy | null) {
constructor(pageProxySession: WKSession, browserContext: WKBrowserContext, opener: WKPageProxy | null) {
this._pageProxySession = pageProxySession;
this._browserContext = browserContext;
this._opener = opener;
Expand Down
18 changes: 18 additions & 0 deletions test/evaluation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,24 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with browser context scripts', async({browser, server}) => {
const context = await browser.newContext();
await context.evaluateOnNewDocument(() => window.temp = 123);
const page = await context.newPage();
await page.evaluateOnNewDocument(() => window.injected = window.temp);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should work with browser context scripts for already created pages', async({browser, server}) => {
const context = await browser.newContext();
const page = await context.newPage();
await context.evaluateOnNewDocument(() => window.temp = 123);
await page.evaluateOnNewDocument(() => window.injected = window.temp);
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
await context.close();
});
it('should support multiple scripts', async({page, server}) => {
await page.evaluateOnNewDocument(function(){
window.script1 = 1;
Expand Down
Loading