Skip to content

Commit 823bf38

Browse files
authored
api: evaluateOnNewDocument -> addInitScript (#1152)
Also adds more options to specify the script.
1 parent 9478bf3 commit 823bf38

12 files changed

+118
-77
lines changed

docs/api.md

+55-51
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,11 @@ await context.close();
264264

265265
<!-- GEN:toc -->
266266
- [event: 'close'](#event-close)
267+
- [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args)
267268
- [browserContext.clearCookies()](#browsercontextclearcookies)
268269
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
269270
- [browserContext.close()](#browsercontextclose)
270271
- [browserContext.cookies([...urls])](#browsercontextcookiesurls)
271-
- [browserContext.evaluateOnNewDocument(pageFunction[, ...args])](#browsercontextevaluateonnewdocumentpagefunction-args)
272272
- [browserContext.newPage()](#browsercontextnewpage)
273273
- [browserContext.pages()](#browsercontextpages)
274274
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
@@ -286,6 +286,32 @@ Emitted when Browser context gets closed. This might happen because of one of th
286286
- Browser application is closed or crashed.
287287
- The [`browser.close`](#browserclose) method was called.
288288

289+
#### browserContext.addInitScript(script[, ...args])
290+
- `script` <[function]|[string]|[Object]> Script to be evaluated in all pages in the browser context.
291+
- `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).
292+
- `content` <[string]> Raw script content.
293+
- `...args` <...[Serializable]> Arguments to pass to `script` (only supported when passing a function).
294+
- returns: <[Promise]>
295+
296+
Adds a script which would be evaluated in one of the following scenarios:
297+
- Whenever a page is created in the browser context or is navigated.
298+
- 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.
299+
300+
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`.
301+
302+
An example of overriding `Math.random` before the page loads:
303+
304+
```js
305+
// preload.js
306+
Math.random = () => 42;
307+
308+
// In your playwright script, assuming the preload.js file is in same folder
309+
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
310+
await browserContext.addInitScript(preloadFile);
311+
```
312+
313+
> **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.
314+
289315
#### browserContext.clearCookies()
290316
- returns: <[Promise]>
291317

@@ -328,30 +354,6 @@ will be closed.
328354
If no URLs are specified, this method returns all cookies.
329355
If URLs are specified, only cookies that affect those URLs are returned.
330356

331-
#### browserContext.evaluateOnNewDocument(pageFunction[, ...args])
332-
- `pageFunction` <[function]|[string]> Function to be evaluated in all pages in the browser context
333-
- `...args` <...[Serializable]> Arguments to pass to `pageFunction`
334-
- returns: <[Promise]>
335-
336-
Adds a function which would be invoked in one of the following scenarios:
337-
- Whenever a page is created in the browser context or is navigated.
338-
- 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.
339-
340-
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`.
341-
342-
An example of overriding `Math.random` before the page loads:
343-
344-
```js
345-
// preload.js
346-
Math.random = () => 42;
347-
348-
// In your playwright script, assuming the preload.js file is in same folder
349-
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
350-
await browserContext.evaluateOnNewDocument(preloadFile);
351-
```
352-
353-
> **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.
354-
355357
#### browserContext.newPage()
356358
- returns: <[Promise]<[Page]>>
357359

@@ -511,6 +513,7 @@ page.removeListener('request', logRequest);
511513
- [page.$eval(selector, pageFunction[, ...args])](#pageevalselector-pagefunction-args-1)
512514
- [page.$wait(selector[, options])](#pagewaitselector-options)
513515
- [page.accessibility](#pageaccessibility)
516+
- [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args)
514517
- [page.addScriptTag(options)](#pageaddscripttagoptions)
515518
- [page.addStyleTag(options)](#pageaddstyletagoptions)
516519
- [page.authenticate(credentials)](#pageauthenticatecredentials)
@@ -524,7 +527,6 @@ page.removeListener('request', logRequest);
524527
- [page.emulateMedia(options)](#pageemulatemediaoptions)
525528
- [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args)
526529
- [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args)
527-
- [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args)
528530
- [page.exposeFunction(name, playwrightFunction)](#pageexposefunctionname-playwrightfunction)
529531
- [page.fill(selector, value, options)](#pagefillselector-value-options)
530532
- [page.focus(selector, options)](#pagefocusselector-options)
@@ -752,6 +754,32 @@ This is a shortcut to [page.waitForSelector(selector[, options])](#pagewaitforse
752754
#### page.accessibility
753755
- returns: <[Accessibility]>
754756

757+
#### page.addInitScript(script[, ...args])
758+
- `script` <[function]|[string]|[Object]> Script to be evaluated in the page.
759+
- `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).
760+
- `content` <[string]> Raw script content.
761+
- `...args` <...[Serializable]> Arguments to pass to `script` (only supported when passing a function).
762+
- returns: <[Promise]>
763+
764+
Adds a script which would be evaluated in one of the following scenarios:
765+
- Whenever the page is navigated.
766+
- Whenever the child frame is attached or navigated. In this case, the scritp is evaluated in the context of the newly attached frame.
767+
768+
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`.
769+
770+
An example of overriding `Math.random` before the page loads:
771+
772+
```js
773+
// preload.js
774+
Math.random = () => 42;
775+
776+
// In your playwright script, assuming the preload.js file is in same folder
777+
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
778+
await page.addInitScript(preloadFile);
779+
```
780+
781+
> **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.
782+
755783
#### page.addScriptTag(options)
756784
- `options` <[Object]>
757785
- `url` <[string]> URL of a script to be added.
@@ -966,30 +994,6 @@ console.log(await resultHandle.jsonValue());
966994
await resultHandle.dispose();
967995
```
968996

969-
#### page.evaluateOnNewDocument(pageFunction[, ...args])
970-
- `pageFunction` <[function]|[string]> Function to be evaluated in the page
971-
- `...args` <...[Serializable]> Arguments to pass to `pageFunction`
972-
- returns: <[Promise]>
973-
974-
Adds a function which would be invoked in one of the following scenarios:
975-
- whenever the page is navigated
976-
- whenever the child frame is attached or navigated. In this case, the function is invoked in the context of the newly attached frame
977-
978-
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`.
979-
980-
An example of overriding `Math.random` before the page loads:
981-
982-
```js
983-
// preload.js
984-
Math.random = () => 42;
985-
986-
// In your playwright script, assuming the preload.js file is in same folder
987-
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
988-
await page.evaluateOnNewDocument(preloadFile);
989-
```
990-
991-
> **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.
992-
993997
#### page.exposeFunction(name, playwrightFunction)
994998
- `name` <[string]> Name of the function on the window object
995999
- `playwrightFunction` <[function]> Callback function which will be called in Playwright's context.
@@ -3604,11 +3608,11 @@ const backgroundPage = await backroundPageTarget.page();
36043608
<!-- GEN:stop -->
36053609
<!-- GEN:toc-extends-BrowserContext -->
36063610
- [event: 'close'](#event-close)
3611+
- [browserContext.addInitScript(script[, ...args])](#browsercontextaddinitscriptscript-args)
36073612
- [browserContext.clearCookies()](#browsercontextclearcookies)
36083613
- [browserContext.clearPermissions()](#browsercontextclearpermissions)
36093614
- [browserContext.close()](#browsercontextclose)
36103615
- [browserContext.cookies([...urls])](#browsercontextcookiesurls)
3611-
- [browserContext.evaluateOnNewDocument(pageFunction[, ...args])](#browsercontextevaluateonnewdocumentpagefunction-args)
36123616
- [browserContext.newPage()](#browsercontextnewpage)
36133617
- [browserContext.pages()](#browsercontextpages)
36143618
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)

src/browserContext.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export interface BrowserContext {
4646
clearPermissions(): Promise<void>;
4747
setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
4848
setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
49-
evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]): Promise<void>;
49+
addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]): Promise<void>;
5050
close(): Promise<void>;
5151

5252
_existingPages(): Page[];

src/chromium/crBrowser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
302302
await (page._delegate as CRPage).updateExtraHTTPHeaders();
303303
}
304304

305-
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
306-
const source = helper.evaluationString(pageFunction, ...args);
305+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
306+
const source = await helper.evaluationScript(script, ...args);
307307
this._evaluateOnNewDocumentSources.push(source);
308308
for (const page of this._existingPages())
309309
await (page._delegate as CRPage).evaluateOnNewDocument(source);

src/firefox/ffBrowser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,8 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
359359
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(this._options.extraHTTPHeaders) });
360360
}
361361

362-
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
363-
const source = helper.evaluationString(pageFunction, ...args);
362+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
363+
const source = await helper.evaluationScript(script, ...args);
364364
this._evaluateOnNewDocumentSources.push(source);
365365
await this._browser._connection.send('Browser.addScriptToEvaluateOnNewDocument', { browserContextId: this._browserContextId || undefined, script: source });
366366
}

src/helper.ts

+15
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ class Helper {
4141
}
4242
}
4343

44+
static async evaluationScript(fun: Function | string | { path?: string, content?: string }, ...args: any[]): Promise<string> {
45+
if (!helper.isString(fun) && typeof fun !== 'function') {
46+
if (fun.content !== undefined) {
47+
fun = fun.content;
48+
} else if (fun.path !== undefined) {
49+
let contents = await platform.readFileAsync(fun.path, 'utf8');
50+
contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');
51+
fun = contents;
52+
} else {
53+
throw new Error('Either path or content property must be present');
54+
}
55+
}
56+
return helper.evaluationString(fun, ...args);
57+
}
58+
4459
static installApiHooks(className: string, classType: any) {
4560
const log = platform.debug('pw:api');
4661
for (const methodName of Reflect.ownKeys(classType.prototype)) {

src/page.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,8 @@ export class Page extends platform.EventEmitter {
411411
return this.mainFrame().evaluate(pageFunction, ...args as any);
412412
}
413413

414-
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
415-
const source = helper.evaluationString(pageFunction, ...args);
416-
await this._delegate.evaluateOnNewDocument(source);
414+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
415+
await this._delegate.evaluateOnNewDocument(await helper.evaluationScript(script, ...args));
417416
}
418417

419418
async setCacheEnabled(enabled: boolean = true) {

src/webkit/wkBrowser.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
278278
await (page._delegate as WKPage).updateExtraHTTPHeaders();
279279
}
280280

281-
async evaluateOnNewDocument(pageFunction: Function | string, ...args: any[]) {
282-
const source = helper.evaluationString(pageFunction, ...args);
281+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
282+
const source = await helper.evaluationScript(script, ...args);
283283
this._evaluateOnNewDocumentSources.push(source);
284284
for (const page of this._existingPages())
285285
await (page._delegate as WKPage)._updateBootstrapScript();

test/assets/injectedfile.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
window.__injected = 42;
2+
window.injected = 123;
23
window.__injectedError = new Error('hi');

test/evaluation.spec.js

+33-11
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
const utils = require('./utils');
19-
19+
const path = require('path');
2020
const bigint = typeof BigInt !== 'undefined';
2121

2222
/**
@@ -277,37 +277,59 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
277277
})
278278
});
279279

280-
describe('Page.evaluateOnNewDocument', function() {
280+
describe('Page.addInitScript', function() {
281281
it('should evaluate before anything else on the page', async({page, server}) => {
282-
await page.evaluateOnNewDocument(function(){
282+
await page.addInitScript(function(){
283283
window.injected = 123;
284284
});
285285
await page.goto(server.PREFIX + '/tamperable.html');
286286
expect(await page.evaluate(() => window.result)).toBe(123);
287287
});
288+
it('should work with a path', async({page, server}) => {
289+
await page.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
290+
await page.goto(server.PREFIX + '/tamperable.html');
291+
expect(await page.evaluate(() => window.result)).toBe(123);
292+
});
293+
it('should work with content', async({page, server}) => {
294+
await page.addInitScript({ content: 'window.injected = 123' });
295+
await page.goto(server.PREFIX + '/tamperable.html');
296+
expect(await page.evaluate(() => window.result)).toBe(123);
297+
});
298+
it('should throw without path and content', async({page, server}) => {
299+
const error = await page.addInitScript({ foo: 'bar' }).catch(e => e);
300+
expect(error.message).toBe('Either path or content property must be present');
301+
});
288302
it('should work with browser context scripts', async({browser, server}) => {
289303
const context = await browser.newContext();
290-
await context.evaluateOnNewDocument(() => window.temp = 123);
304+
await context.addInitScript(() => window.temp = 123);
305+
const page = await context.newPage();
306+
await page.addInitScript(() => window.injected = window.temp);
307+
await page.goto(server.PREFIX + '/tamperable.html');
308+
expect(await page.evaluate(() => window.result)).toBe(123);
309+
await context.close();
310+
});
311+
it('should work with browser context scripts with a path', async({browser, server}) => {
312+
const context = await browser.newContext();
313+
await context.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
291314
const page = await context.newPage();
292-
await page.evaluateOnNewDocument(() => window.injected = window.temp);
293315
await page.goto(server.PREFIX + '/tamperable.html');
294316
expect(await page.evaluate(() => window.result)).toBe(123);
295317
await context.close();
296318
});
297319
it('should work with browser context scripts for already created pages', async({browser, server}) => {
298320
const context = await browser.newContext();
299321
const page = await context.newPage();
300-
await context.evaluateOnNewDocument(() => window.temp = 123);
301-
await page.evaluateOnNewDocument(() => window.injected = window.temp);
322+
await context.addInitScript(() => window.temp = 123);
323+
await page.addInitScript(() => window.injected = window.temp);
302324
await page.goto(server.PREFIX + '/tamperable.html');
303325
expect(await page.evaluate(() => window.result)).toBe(123);
304326
await context.close();
305327
});
306328
it('should support multiple scripts', async({page, server}) => {
307-
await page.evaluateOnNewDocument(function(){
329+
await page.addInitScript(function(){
308330
window.script1 = 1;
309331
});
310-
await page.evaluateOnNewDocument(function(){
332+
await page.addInitScript(function(){
311333
window.script2 = 2;
312334
});
313335
await page.goto(server.PREFIX + '/tamperable.html');
@@ -316,7 +338,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
316338
});
317339
it('should work with CSP', async({page, server}) => {
318340
server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
319-
await page.evaluateOnNewDocument(function(){
341+
await page.addInitScript(function(){
320342
window.injected = 123;
321343
});
322344
await page.goto(server.PREFIX + '/empty.html');
@@ -328,7 +350,7 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
328350
});
329351
it('should work after a cross origin navigation', async({page, server}) => {
330352
await page.goto(server.CROSS_PROCESS_PREFIX);
331-
await page.evaluateOnNewDocument(function(){
353+
await page.addInitScript(function(){
332354
window.injected = 123;
333355
});
334356
await page.goto(server.PREFIX + '/tamperable.html');

test/page.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -412,12 +412,12 @@ module.exports.describe = function({testRunner, expect, headless, playwright, FF
412412
});
413413
expect(thrown).toBe(null);
414414
});
415-
it('should be callable from-inside evaluateOnNewDocument', async({page, server}) => {
415+
it('should be callable from-inside addInitScript', async({page, server}) => {
416416
let called = false;
417417
await page.exposeFunction('woof', function() {
418418
called = true;
419419
});
420-
await page.evaluateOnNewDocument(() => woof());
420+
await page.addInitScript(() => woof());
421421
await page.reload();
422422
expect(called).toBe(true);
423423
});

test/popup.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
7474
await context.close();
7575
expect(size).toEqual({width: 400, height: 500});
7676
});
77-
it.skip(CHROMIUM || WEBKIT)('should apply evaluateOnNewDocument from browser context', async function({browser, server}) {
77+
it.skip(CHROMIUM)('should apply addInitScript from browser context', async function({browser, server}) {
7878
const context = await browser.newContext();
79-
await context.evaluateOnNewDocument(() => window.injected = 123);
79+
await context.addInitScript(() => window.injected = 123);
8080
const page = await context.newPage();
8181
await page.goto(server.EMPTY_PAGE);
8282
const injected = await page.evaluate(() => {

test/waittask.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ module.exports.describe = function({testRunner, expect, product, playwright, FFO
8585
await watchdog;
8686
});
8787
it('should work when resolved right before execution context disposal', async({page, server}) => {
88-
await page.evaluateOnNewDocument(() => window.__RELOADED = true);
88+
await page.addInitScript(() => window.__RELOADED = true);
8989
await page.waitForFunction(() => {
9090
if (!window.__RELOADED)
9191
window.location.reload();

0 commit comments

Comments
 (0)