diff --git a/docs/api.md b/docs/api.md
index b950218f5f..d04d5c5e88 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -210,6 +210,9 @@ Indicates that the browser is connected.
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
- `offline` <[boolean]> Whether to emulate network being offline. Defaults to `false`.
+ - `httpCredentials` <[Object]> Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
+ - `username` <[string]>
+ - `password` <[string]>
- returns: <[Promise]<[BrowserContext]>>
Creates a new browser context. It won't share cookies/cache with other browser contexts.
@@ -245,6 +248,9 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
- `offline` <[boolean]> Whether to emulate network being offline. Defaults to `false`.
+ - `httpCredentials` <[Object]> Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
+ - `username` <[string]>
+ - `password` <[string]>
- returns: <[Promise]<[Page]>>
Creates a new page in a new browser context. Closing this page will close the context as well.
@@ -289,6 +295,7 @@ await context.close();
- [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout)
- [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders)
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
+- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
@@ -521,6 +528,16 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});
> **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation.
+#### browserContext.setHTTPCredentials(httpCredentials)
+- `httpCredentials` [Object]>
+ - `username` <[string]>
+ - `password` <[string]>
+- returns: <[Promise]>
+
+Provide credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
+
+To disable authentication, pass `null`.
+
#### browserContext.setOffline(offline)
- `offline` <[boolean]> Whether to emulate network being offline for the browser context.
- returns: <[Promise]>
@@ -624,7 +641,6 @@ page.removeListener('request', logRequest);
- [page.addInitScript(script[, ...args])](#pageaddinitscriptscript-args)
- [page.addScriptTag(options)](#pageaddscripttagoptions)
- [page.addStyleTag(options)](#pageaddstyletagoptions)
-- [page.authenticate(credentials)](#pageauthenticatecredentials)
- [page.check(selector, [options])](#pagecheckselector-options)
- [page.click(selector[, options])](#pageclickselector-options)
- [page.close([options])](#pagecloseoptions)
@@ -894,16 +910,6 @@ Adds a `` tag into the page with the desired url or a `
- - `username` <[string]>
- - `password` <[string]>
-- returns: <[Promise]>
-
-Provide credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
-
-To disable authentication, pass `null`.
-
#### page.check(selector, [options])
- `selector` <[string]> A selector to search for checkbox or radio button to check. If there are multiple elements satisfying the selector, the first will be checked.
- `options` <[Object]>
@@ -3895,6 +3901,7 @@ const backgroundPage = await backroundPageTarget.page();
- [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout)
- [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders)
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
+- [browserContext.setHTTPCredentials(httpCredentials)](#browsercontextsethttpcredentialshttpcredentials)
- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline)
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [browserContext.waitForEvent(event[, optionsOrPredicate])](#browsercontextwaitforeventevent-optionsorpredicate)
diff --git a/package.json b/package.json
index 3c140737d9..59a06503e5 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"main": "index.js",
"playwright": {
"chromium_revision": "747023",
- "firefox_revision": "1035",
+ "firefox_revision": "1039",
"webkit_revision": "1168"
},
"scripts": {
diff --git a/src/browserContext.ts b/src/browserContext.ts
index 7bca57a2eb..8a30fe3e3d 100644
--- a/src/browserContext.ts
+++ b/src/browserContext.ts
@@ -35,6 +35,7 @@ export type BrowserContextOptions = {
permissions?: { [key: string]: string[] },
extraHTTPHeaders?: network.Headers,
offline?: boolean,
+ httpCredentials?: types.Credentials,
};
export interface BrowserContext {
@@ -50,6 +51,7 @@ export interface BrowserContext {
setGeolocation(geolocation: types.Geolocation | null): Promise;
setExtraHTTPHeaders(headers: network.Headers): Promise;
setOffline(offline: boolean): Promise;
+ setHTTPCredentials(httpCredentials: types.Credentials | null): Promise;
addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]): Promise;
exposeFunction(name: string, playwrightFunction: Function): Promise;
waitForEvent(event: string, optionsOrPredicate?: Function | (types.TimeoutOptions & { predicate?: Function })): Promise;
@@ -93,6 +95,7 @@ export abstract class BrowserContextBase extends platform.EventEmitter implement
abstract setPermissions(origin: string, permissions: string[]): Promise;
abstract clearPermissions(): Promise;
abstract setGeolocation(geolocation: types.Geolocation | null): Promise;
+ abstract setHTTPCredentials(httpCredentials: types.Credentials | null): Promise;
abstract setExtraHTTPHeaders(headers: network.Headers): Promise;
abstract setOffline(offline: boolean): Promise;
abstract addInitScript(script: string | Function | { path?: string | undefined; content?: string | undefined; }, ...args: any[]): Promise;
diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts
index 3b2102298a..2e55d21b0f 100644
--- a/src/chromium/crBrowser.ts
+++ b/src/chromium/crBrowser.ts
@@ -244,6 +244,8 @@ export class CRBrowserContext extends BrowserContextBase {
await this.setGeolocation(this._options.geolocation);
if (this._options.offline)
await this.setOffline(this._options.offline);
+ if (this._options.httpCredentials)
+ await this.setHTTPCredentials(this._options.httpCredentials);
}
_existingPages(): Page[] {
@@ -344,6 +346,12 @@ export class CRBrowserContext extends BrowserContextBase {
await (page._delegate as CRPage)._networkManager.setOffline(offline);
}
+ async setHTTPCredentials(httpCredentials: types.Credentials | null): Promise {
+ this._options.httpCredentials = httpCredentials || undefined;
+ for (const page of this._existingPages())
+ await (page._delegate as CRPage)._networkManager.authenticate(httpCredentials);
+ }
+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
const source = await helper.evaluationScript(script, args);
this._evaluateOnNewDocumentSources.push(source);
diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts
index f7e7b5d70c..a6c03aa996 100644
--- a/src/chromium/crPage.ts
+++ b/src/chromium/crPage.ts
@@ -121,6 +121,8 @@ export class CRPage implements PageDelegate {
promises.push(this.updateExtraHTTPHeaders());
if (options.offline)
promises.push(this._networkManager.setOffline(options.offline));
+ if (options.httpCredentials)
+ promises.push(this._networkManager.authenticate(options.httpCredentials));
for (const binding of this._browserContext._pageBindings.values())
promises.push(this._initBinding(binding));
for (const source of this._browserContext._evaluateOnNewDocumentSources)
@@ -376,10 +378,6 @@ export class CRPage implements PageDelegate {
await this._networkManager.setRequestInterception(enabled);
}
- async authenticate(credentials: types.Credentials | null) {
- await this._networkManager.authenticate(credentials);
- }
-
async setFileChooserIntercepted(enabled: boolean) {
await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed.
}
diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts
index 0bca65ffe7..6d34d5a7f7 100644
--- a/src/firefox/ffBrowser.ts
+++ b/src/firefox/ffBrowser.ts
@@ -289,6 +289,8 @@ export class FFBrowserContext extends BrowserContextBase {
await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders);
if (this._options.offline)
await this.setOffline(this._options.offline);
+ if (this._options.httpCredentials)
+ await this.setHTTPCredentials(this._options.httpCredentials);
}
_existingPages(): Page[] {
@@ -381,6 +383,11 @@ export class FFBrowserContext extends BrowserContextBase {
this._options.offline = offline;
}
+ async setHTTPCredentials(httpCredentials: types.Credentials | null): Promise {
+ this._options.httpCredentials = httpCredentials || undefined;
+ await this._browser._connection.send('Browser.setHTTPCredentials', { browserContextId: this._browserContextId || undefined, credentials: httpCredentials });
+ }
+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
const source = await helper.evaluationScript(script, args);
this._evaluateOnNewDocumentSources.push(source);
diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts
index 9da098d886..f00616b6d2 100644
--- a/src/firefox/ffPage.ts
+++ b/src/firefox/ffPage.ts
@@ -284,10 +284,6 @@ export class FFPage implements PageDelegate {
await this._networkManager.setRequestInterception(enabled);
}
- async authenticate(credentials: types.Credentials | null): Promise {
- await this._session.send('Network.setAuthCredentials', credentials || { username: null, password: null });
- }
-
async setFileChooserIntercepted(enabled: boolean) {
await this._session.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => {}); // target can be closed.
}
diff --git a/src/page.ts b/src/page.ts
index f2e12274ab..24b4677c20 100644
--- a/src/page.ts
+++ b/src/page.ts
@@ -49,7 +49,6 @@ export interface PageDelegate {
setViewportSize(viewportSize: types.Size): Promise;
setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise;
setRequestInterception(enabled: boolean): Promise;
- authenticate(credentials: types.Credentials | null): Promise;
setFileChooserIntercepted(enabled: boolean): Promise;
canScreenshotOutsideViewport(): boolean;
@@ -79,7 +78,6 @@ type PageState = {
colorScheme: types.ColorScheme | null;
extraHTTPHeaders: network.Headers | null;
interceptNetwork: boolean | null;
- credentials: types.Credentials | null;
hasTouch: boolean | null;
};
@@ -150,7 +148,6 @@ export class Page extends platform.EventEmitter {
colorScheme: null,
extraHTTPHeaders: null,
interceptNetwork: null,
- credentials: null,
hasTouch: null,
};
this.accessibility = new accessibility.Accessibility(delegate.getAccessibilityTree.bind(delegate));
@@ -412,11 +409,6 @@ export class Page extends platform.EventEmitter {
request.continue();
}
- async authenticate(credentials: types.Credentials | null) {
- this._state.credentials = credentials;
- await this._delegate.authenticate(credentials);
- }
-
async screenshot(options?: types.ScreenshotOptions): Promise {
return this._screenshotter.screenshotPage(options);
}
diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts
index 517836fe54..4dc83599b6 100644
--- a/src/webkit/wkBrowser.ts
+++ b/src/webkit/wkBrowser.ts
@@ -198,6 +198,8 @@ export class WKBrowserContext extends BrowserContextBase {
await this.setGeolocation(this._options.geolocation);
if (this._options.offline)
await this.setOffline(this._options.offline);
+ if (this._options.httpCredentials)
+ await this.setHTTPCredentials(this._options.httpCredentials);
}
_existingPages(): Page[] {
@@ -285,6 +287,12 @@ export class WKBrowserContext extends BrowserContextBase {
await (page._delegate as WKPage).updateOffline();
}
+ async setHTTPCredentials(httpCredentials: types.Credentials | null): Promise {
+ this._options.httpCredentials = httpCredentials || undefined;
+ for (const page of this._existingPages())
+ await (page._delegate as WKPage).updateHttpCredentials();
+ }
+
async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) {
const source = await helper.evaluationScript(script, args);
this._evaluateOnNewDocumentSources.push(source);
diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts
index dabde85b05..5b0fa33e4d 100644
--- a/src/webkit/wkPage.ts
+++ b/src/webkit/wkPage.ts
@@ -90,13 +90,13 @@ export class WKPage implements PageDelegate {
const promises: Promise[] = [
this._pageProxySession.send('Dialog.enable'),
this._pageProxySession.send('Emulation.setActiveAndFocused', { active: true }),
- this.authenticate(this._page._state.credentials)
];
const contextOptions = this._browserContext._options;
if (contextOptions.javaScriptEnabled === false)
promises.push(this._pageProxySession.send('Emulation.setJavaScriptEnabled', { enabled: false }));
if (this._page._state.viewportSize || contextOptions.viewport)
promises.push(this._updateViewport(true /* updateTouch */));
+ promises.push(this.updateHttpCredentials());
await Promise.all(promises);
}
@@ -515,8 +515,9 @@ export class WKPage implements PageDelegate {
await this._updateState('Network.setEmulateOfflineState', { offline: !!this._browserContext._options.offline });
}
- async authenticate(credentials: types.Credentials | null) {
- await this._pageProxySession.send('Emulation.setAuthCredentials', { ...(credentials || { username: '', password: '' }) });
+ async updateHttpCredentials() {
+ const credentials = this._browserContext._options.httpCredentials || { username: '', password: '' };
+ await this._pageProxySession.send('Emulation.setAuthCredentials', { username: credentials.username, password: credentials.password });
}
async setFileChooserIntercepted(enabled: boolean) {
diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js
index 8e5f47bb8a..92487d5bf7 100644
--- a/test/browsercontext.spec.js
+++ b/test/browsercontext.spec.js
@@ -367,6 +367,53 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF
});
});
+ describe('BrowserContext.setHTTPCredentials', function() {
+ it('should work', async({browser, server}) => {
+ server.setAuth('/empty.html', 'user', 'pass');
+ const context = await browser.newContext();
+ const page = await context.newPage();
+ let response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(401);
+ await context.setHTTPCredentials({
+ username: 'user',
+ password: 'pass'
+ });
+ response = await page.reload();
+ expect(response.status()).toBe(200);
+ await context.close();
+ });
+ it('should fail if wrong credentials', async({browser, server}) => {
+ server.setAuth('/empty.html', 'user', 'pass');
+ const context = await browser.newContext({
+ httpCredentials: { username: 'foo', password: 'bar' }
+ });
+ const page = await context.newPage();
+ let response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(401);
+ await context.setHTTPCredentials({
+ username: 'user',
+ password: 'pass'
+ });
+ response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(200);
+ await context.close();
+ });
+ it('should allow disable authentication', async({browser, server}) => {
+ server.setAuth('/empty.html', 'user', 'pass');
+ const context = await browser.newContext({
+ httpCredentials: { username: 'user', password: 'pass' }
+ });
+ const page = await context.newPage();
+ let response = await page.goto(server.EMPTY_PAGE);
+ expect(response.status()).toBe(200);
+ await context.setHTTPCredentials(null);
+ // Navigate to a different origin to bust Chromium's credential caching.
+ response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(response.status()).toBe(401);
+ await context.close();
+ });
+ });
+
describe.fail(FFOX)('BrowserContext.setOffline', function() {
it('should work with initial option', async({browser, server}) => {
const context = await browser.newContext({offline: true});
diff --git a/test/interception.spec.js b/test/interception.spec.js
index 6f5f349b54..4f434540b7 100644
--- a/test/interception.spec.js
+++ b/test/interception.spec.js
@@ -486,44 +486,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
});
});
- describe('Page.authenticate', function() {
- it('should work', async({page, server}) => {
- server.setAuth('/empty.html', 'user', 'pass');
- let response = await page.goto(server.EMPTY_PAGE);
- expect(response.status()).toBe(401);
- await page.authenticate({
- username: 'user',
- password: 'pass'
- });
- response = await page.reload();
- expect(response.status()).toBe(200);
- });
- it('should fail if wrong credentials', async({page, server}) => {
- // Use unique user/password since Chromium caches credentials per origin.
- server.setAuth('/empty.html', 'user2', 'pass2');
- await page.authenticate({
- username: 'foo',
- password: 'bar'
- });
- const response = await page.goto(server.EMPTY_PAGE);
- expect(response.status()).toBe(401);
- });
- it('should allow disable authentication', async({page, server}) => {
- // Use unique user/password since Chromium caches credentials per origin.
- server.setAuth('/empty.html', 'user3', 'pass3');
- await page.authenticate({
- username: 'user3',
- password: 'pass3'
- });
- let response = await page.goto(server.EMPTY_PAGE);
- expect(response.status()).toBe(200);
- await page.authenticate(null);
- // Navigate to a different origin to bust Chromium's credential caching.
- response = await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
- expect(response.status()).toBe(401);
- });
- });
-
describe('Interception vs isNavigationRequest', () => {
it('should work with request interception', async({page, server}) => {
const requests = new Map();
diff --git a/test/popup.spec.js b/test/popup.spec.js
index bf049abb9a..bdb51e97ad 100644
--- a/test/popup.spec.js
+++ b/test/popup.spec.js
@@ -82,6 +82,21 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
await context.close();
expect(online).toBe(false);
});
+ it('should inherit http credentials from browser context', async function({browser, server}) {
+ // Use unique user/password since Chromium caches credentials per origin.
+ server.setAuth('/title.html', 'user', 'pass');
+ const context = await browser.newContext({
+ httpCredentials: { username: 'user', password: 'pass' }
+ });
+ const page = await context.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ const [popup] = await Promise.all([
+ page.waitForEvent('popup').then(async e => { const popup = await e.page(); await popup.waitForLoadState(); return popup; }),
+ page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/title.html'),
+ ]);
+ expect(await popup.title()).toBe('Woof-Woof');
+ await context.close();
+ });
it.skip(FFOX)('should inherit touch support from browser context', async function({browser, server}) {
const context = await browser.newContext({
viewport: { width: 400, height: 500, isMobile: true }