mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(popups): introduce BrowserContext.setDefaultHTTPHeaders (#1116)
This commit is contained in:
parent
4f69930fbe
commit
672f3f9960
12
docs/api.md
12
docs/api.md
@ -199,6 +199,7 @@ Indicates that the browser is connected.
|
||||
- `accuracy` <[number]> Optional non-negative accuracy value.
|
||||
- `locale` <?[string]> Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules.
|
||||
- `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.
|
||||
- returns: <[Promise]<[BrowserContext]>>
|
||||
|
||||
Creates a new browser context. It won't share cookies/cache with other browser contexts.
|
||||
@ -232,6 +233,7 @@ Creates a new browser context. It won't share cookies/cache with other browser c
|
||||
- `accuracy` <[number]> Optional non-negative accuracy value.
|
||||
- `locale` <?[string]> Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules.
|
||||
- `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.
|
||||
- returns: <[Promise]<[Page]>>
|
||||
|
||||
Creates a new page in a new browser context. Closing this page will close the context as well.
|
||||
@ -271,6 +273,7 @@ await context.close();
|
||||
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
|
||||
- [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout)
|
||||
- [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout)
|
||||
- [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders)
|
||||
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
|
||||
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
|
||||
<!-- GEN:stop -->
|
||||
@ -374,6 +377,14 @@ This setting will change the default maximum time for all the methods accepting
|
||||
|
||||
> **NOTE** [`page.setDefaultNavigationTimeout`](#pagesetdefaultnavigationtimeouttimeout), [`page.setDefaultTimeout`](#pagesetdefaulttimeouttimeout) and [`browserContext.setDefaultNavigationTimeout`](#browsercontextsetdefaultnavigationtimeouttimeout) take priority over [`browserContext.setDefaultTimeout`](#browserContextsetdefaulttimeouttimeout).
|
||||
|
||||
#### browserContext.setExtraHTTPHeaders(headers)
|
||||
- `headers` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
|
||||
- returns: <[Promise]>
|
||||
|
||||
The extra HTTP headers will be sent with every request initiated by any page in the context. These headers are merged with page-specific extra HTTP headers set with [page.setExtraHTTPHeaders()](#pagesetextrahttpheadersheaders). If page overrides a particular header, page-specific header value will be used instead of the browser context header value.
|
||||
|
||||
> **NOTE** `browserContext.setExtraHTTPHeaders` does not guarantee the order of headers in the outgoing requests.
|
||||
|
||||
#### browserContext.setGeolocation(geolocation)
|
||||
- `geolocation` <[Object]>
|
||||
- `latitude` <[number]> Latitude between -90 and 90.
|
||||
@ -3582,6 +3593,7 @@ const backgroundPage = await backroundPageTarget.page();
|
||||
- [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
|
||||
- [browserContext.setDefaultNavigationTimeout(timeout)](#browsercontextsetdefaultnavigationtimeouttimeout)
|
||||
- [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout)
|
||||
- [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders)
|
||||
- [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation)
|
||||
- [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
|
||||
<!-- GEN:stop -->
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
"main": "index.js",
|
||||
"playwright": {
|
||||
"chromium_revision": "744254",
|
||||
"firefox_revision": "1029",
|
||||
"firefox_revision": "1031",
|
||||
"webkit_revision": "1155"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@ -30,7 +30,8 @@ export type BrowserContextOptions = {
|
||||
locale?: string,
|
||||
timezoneId?: string,
|
||||
geolocation?: types.Geolocation,
|
||||
permissions?: { [key: string]: string[] };
|
||||
permissions?: { [key: string]: string[] },
|
||||
extraHTTPHeaders?: network.Headers,
|
||||
};
|
||||
|
||||
export interface BrowserContext {
|
||||
@ -44,6 +45,7 @@ export interface BrowserContext {
|
||||
setPermissions(origin: string, permissions: string[]): Promise<void>;
|
||||
clearPermissions(): Promise<void>;
|
||||
setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
|
||||
setExtraHTTPHeaders(headers: network.Headers): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
|
||||
_existingPages(): Page[];
|
||||
@ -67,6 +69,8 @@ export function validateBrowserContextOptions(options: BrowserContextOptions): B
|
||||
result.viewport = { ...result.viewport };
|
||||
if (result.geolocation)
|
||||
result.geolocation = verifyGeolocation(result.geolocation);
|
||||
if (result.extraHTTPHeaders)
|
||||
result.extraHTTPHeaders = network.verifyHeaders(result.extraHTTPHeaders);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -294,6 +294,12 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
await (page._delegate as CRPage)._client.send('Emulation.setGeolocationOverride', geolocation || {});
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
|
||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
for (const page of this._existingPages())
|
||||
await (page._delegate as CRPage).updateExtraHTTPHeaders();
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
@ -118,6 +118,7 @@ export class CRPage implements PageDelegate {
|
||||
promises.push(emulateTimezone(this._client, options.timezoneId));
|
||||
if (options.geolocation)
|
||||
promises.push(this._client.send('Emulation.setGeolocationOverride', options.geolocation));
|
||||
promises.push(this.updateExtraHTTPHeaders());
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
@ -316,7 +317,11 @@ export class CRPage implements PageDelegate {
|
||||
this._page._onFileChooserOpened(handle);
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
|
||||
async updateExtraHTTPHeaders(): Promise<void> {
|
||||
const headers = network.mergeHeaders([
|
||||
this._page.context()._options.extraHTTPHeaders,
|
||||
this._page._state.extraHTTPHeaders
|
||||
]);
|
||||
await this._client.send('Network.setExtraHTTPHeaders', { headers });
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ import * as platform from '../platform';
|
||||
import { Protocol } from './protocol';
|
||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||
import { TimeoutSettings } from '../timeoutSettings';
|
||||
import { headersArray } from './ffNetworkManager';
|
||||
|
||||
export class FFBrowser extends platform.EventEmitter implements Browser {
|
||||
_connection: FFConnection;
|
||||
@ -274,6 +275,8 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1])));
|
||||
if (this._options.geolocation)
|
||||
await this.setGeolocation(this._options.geolocation);
|
||||
if (this._options.extraHTTPHeaders)
|
||||
await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders);
|
||||
}
|
||||
|
||||
_existingPages(): Page[] {
|
||||
@ -349,6 +352,11 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
throw new Error('Geolocation emulation is not supported in Firefox');
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
|
||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(this._options.extraHTTPHeaders) });
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
@ -208,7 +208,7 @@ class InterceptableRequest implements network.RequestDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
function headersArray(headers: network.Headers): Protocol.Network.HTTPHeader[] {
|
||||
export function headersArray(headers: network.Headers): Protocol.Network.HTTPHeader[] {
|
||||
const result: Protocol.Network.HTTPHeader[] = [];
|
||||
for (const name in headers) {
|
||||
if (!Object.is(headers[name], undefined))
|
||||
|
||||
@ -21,14 +21,13 @@ import * as dom from '../dom';
|
||||
import { FFSession } from './ffConnection';
|
||||
import { FFExecutionContext } from './ffExecutionContext';
|
||||
import { Page, PageDelegate, Worker } from '../page';
|
||||
import { FFNetworkManager } from './ffNetworkManager';
|
||||
import { FFNetworkManager, headersArray } from './ffNetworkManager';
|
||||
import { Events } from '../events';
|
||||
import * as dialog from '../dialog';
|
||||
import { Protocol } from './protocol';
|
||||
import { RawMouseImpl, RawKeyboardImpl } from './ffInput';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { getAccessibilityTree } from './ffAccessibility';
|
||||
import * as network from '../network';
|
||||
import * as types from '../types';
|
||||
import * as platform from '../platform';
|
||||
import { kScreenshotDuringNavigationError } from '../screenshotter';
|
||||
@ -251,11 +250,8 @@ export class FFPage implements PageDelegate {
|
||||
return { newDocumentId: response.navigationId || undefined };
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
|
||||
const array = [];
|
||||
for (const [name, value] of Object.entries(headers))
|
||||
array.push({ name, value });
|
||||
await this._session.send('Network.setExtraHTTPHeaders', { headers: array });
|
||||
async updateExtraHTTPHeaders(): Promise<void> {
|
||||
await this._session.send('Network.setExtraHTTPHeaders', { headers: headersArray(this._page._state.extraHTTPHeaders || {}) });
|
||||
}
|
||||
|
||||
async setViewportSize(viewportSize: types.Size): Promise<void> {
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import * as frames from './frames';
|
||||
import { assert } from './helper';
|
||||
import { assert, helper } from './helper';
|
||||
import * as platform from './platform';
|
||||
|
||||
export type NetworkCookie = {
|
||||
@ -373,3 +373,31 @@ export const STATUS_TEXTS: { [status: string]: string } = {
|
||||
'510': 'Not Extended',
|
||||
'511': 'Network Authentication Required',
|
||||
};
|
||||
|
||||
export function verifyHeaders(headers: Headers): Headers {
|
||||
const result: Headers = {};
|
||||
for (const key of Object.keys(headers)) {
|
||||
const value = headers[key];
|
||||
assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
|
||||
result[key] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function mergeHeaders(headers: (Headers | undefined | null)[]): Headers {
|
||||
const lowerCaseToValue = new Map<string, string>();
|
||||
const lowerCaseToOriginalCase = new Map<string, string>();
|
||||
for (const h of headers) {
|
||||
if (!h)
|
||||
continue;
|
||||
for (const key of Object.keys(h)) {
|
||||
const lower = key.toLowerCase();
|
||||
lowerCaseToOriginalCase.set(lower, key);
|
||||
lowerCaseToValue.set(lower, h[key]);
|
||||
}
|
||||
}
|
||||
const result: Headers = {};
|
||||
for (const [lower, value] of lowerCaseToValue)
|
||||
result[lowerCaseToOriginalCase.get(lower)!] = value;
|
||||
return result;
|
||||
}
|
||||
|
||||
11
src/page.ts
11
src/page.ts
@ -45,7 +45,7 @@ export interface PageDelegate {
|
||||
|
||||
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
|
||||
|
||||
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
|
||||
updateExtraHTTPHeaders(): Promise<void>;
|
||||
setViewportSize(viewportSize: types.Size): Promise<void>;
|
||||
setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void>;
|
||||
setCacheEnabled(enabled: boolean): Promise<void>;
|
||||
@ -268,13 +268,8 @@ export class Page extends platform.EventEmitter {
|
||||
}
|
||||
|
||||
setExtraHTTPHeaders(headers: network.Headers) {
|
||||
this._state.extraHTTPHeaders = {};
|
||||
for (const key of Object.keys(headers)) {
|
||||
const value = headers[key];
|
||||
assert(helper.isString(value), `Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
|
||||
this._state.extraHTTPHeaders[key] = value;
|
||||
}
|
||||
return this._delegate.setExtraHTTPHeaders(headers);
|
||||
this._state.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
return this._delegate.updateExtraHTTPHeaders();
|
||||
}
|
||||
|
||||
async _onBindingCalled(payload: string, context: js.ExecutionContext) {
|
||||
|
||||
@ -28,6 +28,7 @@ import { WKConnection, WKSession, kPageProxyMessageReceived, PageProxyMessageRec
|
||||
import { WKPageProxy } from './wkPageProxy';
|
||||
import * as platform from '../platform';
|
||||
import { TimeoutSettings } from '../timeoutSettings';
|
||||
import { WKPage } from './wkPage';
|
||||
|
||||
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15';
|
||||
|
||||
@ -269,6 +270,12 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo
|
||||
await this._browser._browserSession.send('Browser.setGeolocationOverride', { browserContextId: this._browserContextId, geolocation: payload });
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
|
||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
||||
for (const page of this._existingPages())
|
||||
await (page._delegate as WKPage).updateExtraHTTPHeaders();
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._closed)
|
||||
return;
|
||||
|
||||
@ -142,12 +142,7 @@ export class WKPage implements PageDelegate {
|
||||
}
|
||||
if (contextOptions.bypassCSP)
|
||||
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
|
||||
if (this._page._state.extraHTTPHeaders || contextOptions.locale) {
|
||||
const headers = this._page._state.extraHTTPHeaders || {};
|
||||
if (contextOptions.locale)
|
||||
headers['Accept-Language'] = contextOptions.locale;
|
||||
promises.push(session.send('Network.setExtraHTTPHeaders', { headers }));
|
||||
}
|
||||
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }));
|
||||
if (this._page._state.hasTouch)
|
||||
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true }));
|
||||
if (contextOptions.timezoneId) {
|
||||
@ -378,12 +373,19 @@ export class WKPage implements PageDelegate {
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(headers: network.Headers): Promise<void> {
|
||||
const copy = { ...headers };
|
||||
async updateExtraHTTPHeaders(): Promise<void> {
|
||||
await this._updateState('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() });
|
||||
}
|
||||
|
||||
_calculateExtraHTTPHeaders(): network.Headers {
|
||||
const headers = network.mergeHeaders([
|
||||
this._page.context()._options.extraHTTPHeaders,
|
||||
this._page._state.extraHTTPHeaders
|
||||
]);
|
||||
const locale = this._page.context()._options.locale;
|
||||
if (locale)
|
||||
copy['Accept-Language'] = locale;
|
||||
await this._updateState('Network.setExtraHTTPHeaders', { headers: copy });
|
||||
headers['Accept-Language'] = locale;
|
||||
return headers;
|
||||
}
|
||||
|
||||
async setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise<void> {
|
||||
|
||||
@ -331,6 +331,35 @@ module.exports.describe = function({testRunner, expect, MAC, WIN, FFOX, CHROMIUM
|
||||
]);
|
||||
expect(request.headers['foo']).toBe('bar');
|
||||
});
|
||||
it('should work with extra headers from browser context', async({browser, server}) => {
|
||||
const context = await browser.newContext();
|
||||
await context.setExtraHTTPHeaders({
|
||||
'foo': 'bar',
|
||||
});
|
||||
const page = await context.newPage();
|
||||
const [request] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
page.goto(server.EMPTY_PAGE),
|
||||
]);
|
||||
await context.close();
|
||||
expect(request.headers['foo']).toBe('bar');
|
||||
});
|
||||
it('should override extra headers from browser context', async({browser, server}) => {
|
||||
const context = await browser.newContext({
|
||||
extraHTTPHeaders: { 'fOo': 'bAr', 'baR': 'foO' },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.setExtraHTTPHeaders({
|
||||
'Foo': 'Bar'
|
||||
});
|
||||
const [request] = await Promise.all([
|
||||
server.waitForRequest('/empty.html'),
|
||||
page.goto(server.EMPTY_PAGE),
|
||||
]);
|
||||
await context.close();
|
||||
expect(request.headers['foo']).toBe('Bar');
|
||||
expect(request.headers['bar']).toBe('foO');
|
||||
});
|
||||
it('should throw for non-string header values', async({page, server}) => {
|
||||
let error = null;
|
||||
try {
|
||||
|
||||
@ -36,6 +36,18 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
|
||||
expect(userAgent).toBe('hey');
|
||||
expect(request.headers['user-agent']).toBe('hey');
|
||||
});
|
||||
it.skip(CHROMIUM)('should inherit extra headers from browser context', async function({browser, server}) {
|
||||
const context = await browser.newContext({
|
||||
extraHTTPHeaders: { 'foo': 'bar' },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const requestPromise = server.waitForRequest('/dummy.html');
|
||||
await page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/dummy.html');
|
||||
const request = await requestPromise;
|
||||
await context.close();
|
||||
expect(request.headers['foo']).toBe('bar');
|
||||
});
|
||||
it.skip(CHROMIUM)('should inherit touch support from browser context', async function({browser, server}) {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 400, height: 500, isMobile: true }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user