mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-03 17:09:01 +03:00
feat(popups): add BrowserContext.evaluateOnNewDocument (#1136)
This commit is contained in:
parent
dc161df063
commit
7682865d73
44
docs/api.md
44
docs/api.md
@ -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)
|
||||
@ -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]>
|
||||
@ -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.
|
||||
|
||||
#### browserContext.newPage()
|
||||
- returns: <[Promise]<[Page]>>
|
||||
@ -944,7 +967,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]>
|
||||
|
||||
@ -954,23 +977,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.
|
||||
@ -3591,6 +3610,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)
|
||||
|
@ -9,7 +9,7 @@
|
||||
"main": "index.js",
|
||||
"playwright": {
|
||||
"chromium_revision": "744254",
|
||||
"firefox_revision": "1031",
|
||||
"firefox_revision": "1032",
|
||||
"webkit_revision": "1162"
|
||||
},
|
||||
"scripts": {
|
||||
|
@ -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[];
|
||||
|
@ -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) {
|
||||
@ -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() {
|
||||
@ -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;
|
||||
|
@ -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';
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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[];
|
||||
|
||||
@ -261,6 +261,7 @@ 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();
|
||||
@ -268,6 +269,7 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
this._browserContextId = browserContextId;
|
||||
this._timeoutSettings = new TimeoutSettings();
|
||||
this._options = options;
|
||||
this._evaluateOnNewDocumentSources = [];
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
@ -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;
|
||||
|
@ -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[];
|
||||
@ -174,6 +174,7 @@ 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();
|
||||
@ -181,6 +182,7 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
this._browserContextId = browserContextId;
|
||||
this._timeoutSettings = new TimeoutSettings();
|
||||
this._options = options;
|
||||
this._evaluateOnNewDocumentSources = [];
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
@ -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;
|
||||
|
@ -27,7 +27,6 @@ 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';
|
||||
@ -35,6 +34,7 @@ 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__';
|
||||
@ -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);
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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() }));
|
||||
@ -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 async _setBootstrapScripts() {
|
||||
const source = this._bootstrapScripts.join(';');
|
||||
await this._updateState('Page.setBootstrapScript', { source });
|
||||
private _calculateBootstrapScript(): string {
|
||||
return [...this._browserContext._evaluateOnNewDocumentSources, ...this._bootstrapScripts].join(';');
|
||||
}
|
||||
|
||||
async _updateBootstrapScript(): Promise<void> {
|
||||
await this._updateState('Page.setBootstrapScript', { source: this._calculateBootstrapScript() });
|
||||
}
|
||||
|
||||
async closePage(runBeforeUnload: boolean): Promise<void> {
|
||||
|
@ -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 = () => {};
|
||||
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -74,6 +74,18 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
|
||||
await context.close();
|
||||
expect(size).toEqual({width: 400, height: 500});
|
||||
});
|
||||
it.skip(CHROMIUM || WEBKIT)('should apply evaluateOnNewDocument from browser context', async function({browser, server}) {
|
||||
const context = await browser.newContext();
|
||||
await context.evaluateOnNewDocument(() => window.injected = 123);
|
||||
const page = await context.newPage();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const injected = await page.evaluate(() => {
|
||||
const win = window.open('about:blank');
|
||||
return win.injected;
|
||||
});
|
||||
await context.close();
|
||||
expect(injected).toBe(123);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Page.Events.Popup', function() {
|
||||
|
@ -85,7 +85,6 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
|
||||
await watchdog;
|
||||
});
|
||||
it('should work when resolved right before execution context disposal', async({page, server}) => {
|
||||
// FIXME: implement Page.addScriptToEvaluateOnNewDocument in WebKit.
|
||||
await page.evaluateOnNewDocument(() => window.__RELOADED = true);
|
||||
await page.waitForFunction(() => {
|
||||
if (!window.__RELOADED)
|
||||
|
Loading…
Reference in New Issue
Block a user