api: evaluateOnNewDocument -> addInitScript (#1152)

Also adds more options to specify the script.
This commit is contained in:
Dmitry Gozman 2020-02-27 17:42:14 -08:00 committed by GitHub
parent 9478bf3ee5
commit 823bf389a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 118 additions and 77 deletions

View File

@ -264,11 +264,11 @@ await context.close();
<!-- GEN:toc -->
- [event: 'close'](#event-close)
- [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args)
- [browserContext.clearCookies()](#browsercontextclearcookies)
- [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)
@ -286,6 +286,32 @@ Emitted when Browser context gets closed. This might happen because of one of th
- Browser application is closed or crashed.
- The [`browser.close`](#browserclose) method was called.
#### browserContext.addInitScript(script[, ...args])
- `script` <[function]|[string]|[Object]> Script to be evaluated in all pages in the browser context.
- `path` <[string]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
- `content` <[string]> Raw script content.
- `...args` <...[Serializable]> Arguments to pass to `script` (only supported when passing a function).
- returns: <[Promise]>
Adds a script which would be evaluated 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 script is evaluated in the context of the newly attached frame.
The script is evaluated 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.addInitScript(preloadFile);
```
> **NOTE** The order of evaluation of multiple scripts installed via [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args) and [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args) is not defined.
#### browserContext.clearCookies()
- returns: <[Promise]>
@ -328,30 +354,6 @@ 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.
#### 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]>>
@ -511,6 +513,7 @@ page.removeListener('request', logRequest);
- [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1)
- [page.$wait(selector[, options])](#pagewaitselector-options)
- [page.accessibility](#pageaccessibility)
- [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args)
- [page.addScriptTag(options)](#pageaddscripttagoptions)
- [page.addStyleTag(options)](#pageaddstyletagoptions)
- [page.authenticate(credentials)](#pageauthenticatecredentials)
@ -524,7 +527,6 @@ page.removeListener('request', logRequest);
- [page.emulateMedia(options)](#pageemulatemediaoptions)
- [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args)
- [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args)
- [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args)
- [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction)
- [page.fill(selector, value, options)](#pagefillselector-value-options)
- [page.focus(selector, options)](#pagefocusselector-options)
@ -752,6 +754,32 @@ This is a shortcut to [page.waitForSelector(selector[, options])](#pagewaitforse
#### page.accessibility
- returns: <[Accessibility]>
#### page.addInitScript(script[, ...args])
- `script` <[function]|[string]|[Object]> Script to be evaluated in the page.
- `path` <[string]> Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd).
- `content` <[string]> Raw script content.
- `...args` <...[Serializable]> Arguments to pass to `script` (only supported when passing a function).
- returns: <[Promise]>
Adds a script which would be evaluated in one of the following scenarios:
- Whenever the page is navigated.
- Whenever the child frame is attached or navigated. In this case, the scritp is evaluated in the context of the newly attached frame.
The script is evaluated 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 page.addInitScript(preloadFile);
```
> **NOTE** The order of evaluation of multiple scripts installed via [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args) and [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args) is not defined.
#### page.addScriptTag(options)
- `options` <[Object]>
- `url` <[string]> URL of a script to be added.
@ -966,30 +994,6 @@ console.log(await resultHandle.jsonValue());
await resultHandle.dispose();
```
#### page.evaluateOnNewDocument(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in the page
- `...args` <...[Serializable]> Arguments to pass to `pageFunction`
- returns: <[Promise]>
Adds a function which would be invoked in one of the following scenarios:
- whenever the page is navigated
- whenever the child frame is attached or navigated. 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 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.
@ -3604,11 +3608,11 @@ const backgroundPage = await backroundPageTarget.page();
<!-- GEN:stop -->
<!-- GEN:toc-extends-BrowserContext -->
- [event: 'close'](#event-close)
- [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args)
- [browserContext.clearCookies()](#browsercontextclearcookies)
- [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)

View File

@ -46,7 +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>;
addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]): Promise<void>;
close(): Promise<void>;
_existingPages(): Page[];

View File

@ -302,8 +302,8 @@ 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);
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
const source = await helper.evaluationScript(script, ...args);
this._evaluateOnNewDocumentSources.push(source);
for (const page of this._existingPages())
await (page._delegate as CRPage).evaluateOnNewDocument(source);

View File

@ -359,8 +359,8 @@ 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);
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
const source = await helper.evaluationScript(script, ...args);
this._evaluateOnNewDocumentSources.push(source);
await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source });
}

View File

@ -41,6 +41,21 @@ class Helper {
}
}
static async evaluationScript(fun: Function | string | { path?: string, content?: string }, ...args: any[]): Promise<string> {
if (!helper.isString(fun) && typeof fun !== 'function') {
if (fun.content !== undefined) {
fun = fun.content;
} else if (fun.path !== undefined) {
let contents = await platform.readFileAsync(fun.path, 'utf8');
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
fun = contents;
} else {
throw new Error('Either path or content property must be present');
}
}
return helper.evaluationString(fun, ...args);
}
static installApiHooks(className: string, classType: any) {
const log = platform.debug('pw:api');
for (const methodName of Reflect.ownKeys(classType.prototype)) {

View File

@ -411,9 +411,8 @@ export class Page extends platform.EventEmitter {
return this.mainFrame().evaluate(pageFunction, ...args as any);
}
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
const source = helper.evaluationString(pageFunction, ...args);
await this._delegate.evaluateOnNewDocument(source);
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
await this._delegate.evaluateOnNewDocument(await helper.evaluationScript(script, ...args));
}
async setCacheEnabled(enabled: boolean = true) {

View File

@ -278,8 +278,8 @@ 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);
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
const source = await helper.evaluationScript(script, ...args);
this._evaluateOnNewDocumentSources.push(source);
for (const page of this._existingPages())
await (page._delegate as WKPage)._updateBootstrapScript();

View File

@ -1,2 +1,3 @@
window.__injected = 42;
window.injected = 123;
window.__injectedError = new Error('hi');

View File

@ -16,7 +16,7 @@
*/
const utils = require('./utils');
const path = require('path');
const bigint = typeof BigInt !== 'undefined';
/**
@ -277,19 +277,41 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
})
});
describe('Page.evaluateOnNewDocument', function() {
describe('Page.addInitScript', function() {
it('should evaluate before anything else on the page', async({page, server}) => {
await page.evaluateOnNewDocument(function(){
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with a path', async({page, server}) => {
await page.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should work with content', async({page, server}) => {
await page.addInitScript({ content: 'window.injected = 123' });
await page.goto(server.PREFIX + '/tamperable.html');
expect(await page.evaluate(() => window.result)).toBe(123);
});
it('should throw without path and content', async({page, server}) => {
const error = await page.addInitScript({ foo: 'bar' }).catch(e => e);
expect(error.message).toBe('Either path or content property must be present');
});
it('should work with browser context scripts', async({browser, server}) => {
const context = await browser.newContext();
await context.evaluateOnNewDocument(() => window.temp = 123);
await context.addInitScript(() => window.temp = 123);
const page = await context.newPage();
await page.addInitScript(() => 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 with a path', async({browser, server}) => {
const context = await browser.newContext();
await context.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
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();
@ -297,17 +319,17 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
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 context.addInitScript(() => window.temp = 123);
await page.addInitScript(() => 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(){
await page.addInitScript(function(){
window.script1 = 1;
});
await page.evaluateOnNewDocument(function(){
await page.addInitScript(function(){
window.script2 = 2;
});
await page.goto(server.PREFIX + '/tamperable.html');
@ -316,7 +338,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
});
it('should work with CSP', async({page, server}) => {
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
await page.evaluateOnNewDocument(function(){
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/empty.html');
@ -328,7 +350,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
});
it('should work after a cross origin navigation', async({page, server}) => {
await page.goto(server.CROSS_PROCESS_PREFIX);
await page.evaluateOnNewDocument(function(){
await page.addInitScript(function(){
window.injected = 123;
});
await page.goto(server.PREFIX + '/tamperable.html');

View File

@ -412,12 +412,12 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
});
expect(thrown).toBe(null);
});
it('should be callable from-inside evaluateOnNewDocument', async({page, server}) => {
it('should be callable from-inside addInitScript', async({page, server}) => {
let called = false;
await page.exposeFunction('woof', function() {
called = true;
});
await page.evaluateOnNewDocument(() => woof());
await page.addInitScript(() => woof());
await page.reload();
expect(called).toBe(true);
});

View File

@ -74,9 +74,9 @@ 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}) {
it.skip(CHROMIUM)('should apply addInitScript from browser context', async function({browser, server}) {
const context = await browser.newContext();
await context.evaluateOnNewDocument(() => window.injected = 123);
await context.addInitScript(() => window.injected = 123);
const page = await context.newPage();
await page.goto(server.EMPTY_PAGE);
const injected = await page.evaluate(() => {

View File

@ -85,7 +85,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
await watchdog;
});
it('should work when resolved right before execution context disposal', async({page, server}) => {
await page.evaluateOnNewDocument(() => window.__RELOADED = true);
await page.addInitScript(() => window.__RELOADED = true);
await page.waitForFunction(() => {
if (!window.__RELOADED)
window.location.reload();