chore: use HeadersArray instead of Headers object on the server side (#3512)
This simplifies implementation and avoids multiple conversions. Also adding some tests around lowercase and wrong types.
This commit is contained in:
parent
77cab8bed3
commit
aeadf50165
|
|
@ -39,7 +39,7 @@ export interface BrowserContext extends EventEmitter {
|
||||||
grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void>;
|
grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void>;
|
||||||
clearPermissions(): Promise<void>;
|
clearPermissions(): Promise<void>;
|
||||||
setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
||||||
setExtraHTTPHeaders(headers: types.Headers): Promise<void>;
|
setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
||||||
setOffline(offline: boolean): Promise<void>;
|
setOffline(offline: boolean): Promise<void>;
|
||||||
setHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
setHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
||||||
addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void>;
|
addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void>;
|
||||||
|
|
@ -103,7 +103,7 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser
|
||||||
abstract _doClearPermissions(): Promise<void>;
|
abstract _doClearPermissions(): Promise<void>;
|
||||||
abstract setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
abstract setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
||||||
abstract _doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
abstract _doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise<void>;
|
||||||
abstract setExtraHTTPHeaders(headers: types.Headers): Promise<void>;
|
abstract setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
||||||
abstract setOffline(offline: boolean): Promise<void>;
|
abstract setOffline(offline: boolean): Promise<void>;
|
||||||
abstract _doAddInitScript(expression: string): Promise<void>;
|
abstract _doAddInitScript(expression: string): Promise<void>;
|
||||||
abstract _doExposeBinding(binding: PageBinding): Promise<void>;
|
abstract _doExposeBinding(binding: PageBinding): Promise<void>;
|
||||||
|
|
@ -189,9 +189,11 @@ export abstract class BrowserContextBase extends EventEmitter implements Browser
|
||||||
const { username, password } = proxy;
|
const { username, password } = proxy;
|
||||||
if (username) {
|
if (username) {
|
||||||
this._options.httpCredentials = { username, password: password! };
|
this._options.httpCredentials = { username, password: password! };
|
||||||
this._options.extraHTTPHeaders = this._options.extraHTTPHeaders || {};
|
|
||||||
const token = Buffer.from(`${username}:${password}`).toString('base64');
|
const token = Buffer.from(`${username}:${password}`).toString('base64');
|
||||||
this._options.extraHTTPHeaders['Proxy-Authorization'] = `Basic ${token}`;
|
this._options.extraHTTPHeaders = network.mergeHeaders([
|
||||||
|
this._options.extraHTTPHeaders,
|
||||||
|
network.singleHeader('Proxy-Authorization', `Basic ${token}`),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,8 +238,6 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio
|
||||||
if (!options.viewport && !options.noDefaultViewport)
|
if (!options.viewport && !options.noDefaultViewport)
|
||||||
options.viewport = { width: 1280, height: 720 };
|
options.viewport = { width: 1280, height: 720 };
|
||||||
verifyGeolocation(options.geolocation);
|
verifyGeolocation(options.geolocation);
|
||||||
if (options.extraHTTPHeaders)
|
|
||||||
options.extraHTTPHeaders = network.verifyHeaders(options.extraHTTPHeaders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifyGeolocation(geolocation?: types.Geolocation) {
|
export function verifyGeolocation(geolocation?: types.Geolocation) {
|
||||||
|
|
|
||||||
|
|
@ -387,8 +387,8 @@ export class CRBrowserContext extends BrowserContextBase {
|
||||||
await (page._delegate as CRPage).updateGeolocation();
|
await (page._delegate as CRPage).updateGeolocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
|
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
this._options.extraHTTPHeaders = headers;
|
||||||
for (const page of this.pages())
|
for (const page of this.pages())
|
||||||
await (page._delegate as CRPage).updateExtraHTTPHeaders();
|
await (page._delegate as CRPage).updateExtraHTTPHeaders();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import * as network from '../network';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { CRPage } from './crPage';
|
import { CRPage } from './crPage';
|
||||||
|
import { headersObjectToArray } from '../converters';
|
||||||
|
|
||||||
export class CRNetworkManager {
|
export class CRNetworkManager {
|
||||||
private _client: CRSession;
|
private _client: CRSession;
|
||||||
|
|
@ -239,7 +240,7 @@ export class CRNetworkManager {
|
||||||
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
|
const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
|
||||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||||
};
|
};
|
||||||
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
|
return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), getResponseBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
|
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response) {
|
||||||
|
|
@ -350,7 +351,7 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||||
if (postDataEntries && postDataEntries.length && postDataEntries[0].bytes)
|
if (postDataEntries && postDataEntries.length && postDataEntries[0].bytes)
|
||||||
postDataBuffer = Buffer.from(postDataEntries[0].bytes, 'base64');
|
postDataBuffer = Buffer.from(postDataEntries[0].bytes, 'base64');
|
||||||
|
|
||||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postDataBuffer, headersObject(headers));
|
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postDataBuffer, headersObjectToArray(headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: types.NormalizedContinueOverrides) {
|
async continue(overrides: types.NormalizedContinueOverrides) {
|
||||||
|
|
@ -406,11 +407,3 @@ const errorReasons: { [reason: string]: Protocol.Network.ErrorReason } = {
|
||||||
'timedout': 'TimedOut',
|
'timedout': 'TimedOut',
|
||||||
'failed': 'Failed',
|
'failed': 'Failed',
|
||||||
};
|
};
|
||||||
|
|
||||||
function headersObject(headers: Protocol.Network.Headers): types.Headers {
|
|
||||||
const result: types.Headers = {};
|
|
||||||
for (const key of Object.keys(headers))
|
|
||||||
result[key.toLowerCase()] = headers[key];
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import * as types from '../types';
|
||||||
import { ConsoleMessage } from '../console';
|
import { ConsoleMessage } from '../console';
|
||||||
import * as sourceMap from '../utils/sourceMap';
|
import * as sourceMap from '../utils/sourceMap';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
import { headersArrayToObject } from '../converters';
|
||||||
|
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
@ -729,7 +730,7 @@ class FrameSession {
|
||||||
this._crPage._browserContext._options.extraHTTPHeaders,
|
this._crPage._browserContext._options.extraHTTPHeaders,
|
||||||
this._page._state.extraHTTPHeaders
|
this._page._state.extraHTTPHeaders
|
||||||
]);
|
]);
|
||||||
await this._client.send('Network.setExtraHTTPHeaders', { headers });
|
await this._client.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(headers, false /* lowerCase */) });
|
||||||
}
|
}
|
||||||
|
|
||||||
async _updateGeolocation(): Promise<void> {
|
async _updateGeolocation(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import * as mime from 'mime';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { helper, assert } from './helper';
|
|
||||||
|
|
||||||
export async function normalizeFilePayloads(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<types.FilePayload[]> {
|
export async function normalizeFilePayloads(files: string | types.FilePayload | string[] | types.FilePayload[]): Promise<types.FilePayload[]> {
|
||||||
let ff: string[] | types.FilePayload[];
|
let ff: string[] | types.FilePayload[];
|
||||||
|
|
@ -43,65 +42,18 @@ export async function normalizeFilePayloads(files: string | types.FilePayload |
|
||||||
return filePayloads;
|
return filePayloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function normalizeFulfillParameters(params: types.FulfillResponse & { path?: string }): Promise<types.NormalizedFulfillResponse> {
|
export function headersObjectToArray(headers: { [key: string]: string }): types.HeadersArray {
|
||||||
let body = '';
|
|
||||||
let isBase64 = false;
|
|
||||||
let length = 0;
|
|
||||||
if (params.path) {
|
|
||||||
const buffer = await util.promisify(fs.readFile)(params.path);
|
|
||||||
body = buffer.toString('base64');
|
|
||||||
isBase64 = true;
|
|
||||||
length = buffer.length;
|
|
||||||
} else if (helper.isString(params.body)) {
|
|
||||||
body = params.body;
|
|
||||||
isBase64 = false;
|
|
||||||
length = Buffer.byteLength(body);
|
|
||||||
} else if (params.body) {
|
|
||||||
body = params.body.toString('base64');
|
|
||||||
isBase64 = true;
|
|
||||||
length = params.body.length;
|
|
||||||
}
|
|
||||||
const headers: types.Headers = {};
|
|
||||||
for (const header of Object.keys(params.headers || {}))
|
|
||||||
headers[header.toLowerCase()] = String(params.headers![header]);
|
|
||||||
if (params.contentType)
|
|
||||||
headers['content-type'] = String(params.contentType);
|
|
||||||
else if (params.path)
|
|
||||||
headers['content-type'] = mime.getType(params.path) || 'application/octet-stream';
|
|
||||||
if (length && !('content-length' in headers))
|
|
||||||
headers['content-length'] = String(length);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: params.status || 200,
|
|
||||||
headers: headersObjectToArray(headers),
|
|
||||||
body,
|
|
||||||
isBase64
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeContinueOverrides(overrides: types.ContinueOverrides): types.NormalizedContinueOverrides {
|
|
||||||
return {
|
|
||||||
method: overrides.method,
|
|
||||||
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
|
|
||||||
postData: helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function headersObjectToArray(headers: types.Headers): types.HeadersArray {
|
|
||||||
const result: types.HeadersArray = [];
|
const result: types.HeadersArray = [];
|
||||||
for (const name in headers) {
|
for (const name in headers) {
|
||||||
if (!Object.is(headers[name], undefined)) {
|
if (!Object.is(headers[name], undefined))
|
||||||
const value = headers[name];
|
result.push({ name, value: headers[name] });
|
||||||
assert(helper.isString(value), `Expected value of header "${name}" to be String, but "${typeof value}" is found.`);
|
|
||||||
result.push({ name, value });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function headersArrayToObject(headers: types.HeadersArray): types.Headers {
|
export function headersArrayToObject(headers: types.HeadersArray, lowerCase: boolean): { [key: string]: string } {
|
||||||
const result: types.Headers = {};
|
const result: { [key: string]: string } = {};
|
||||||
for (const { name, value } of headers)
|
for (const { name, value } of headers)
|
||||||
result[name] = value;
|
result[lowerCase ? name.toLowerCase() : name] = value;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { Page, PageBinding } from '../page';
|
||||||
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
import { ConnectionTransport, SlowMoTransport } from '../transport';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { ConnectionEvents, FFConnection } from './ffConnection';
|
import { ConnectionEvents, FFConnection } from './ffConnection';
|
||||||
import { headersArray } from './ffNetworkManager';
|
|
||||||
import { FFPage } from './ffPage';
|
import { FFPage } from './ffPage';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
|
|
@ -211,7 +210,7 @@ export class FFBrowserContext extends BrowserContextBase {
|
||||||
if (this._options.permissions)
|
if (this._options.permissions)
|
||||||
promises.push(this.grantPermissions(this._options.permissions));
|
promises.push(this.grantPermissions(this._options.permissions));
|
||||||
if (this._options.extraHTTPHeaders || this._options.locale)
|
if (this._options.extraHTTPHeaders || this._options.locale)
|
||||||
promises.push(this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || {}));
|
promises.push(this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []));
|
||||||
if (this._options.httpCredentials)
|
if (this._options.httpCredentials)
|
||||||
promises.push(this.setHTTPCredentials(this._options.httpCredentials));
|
promises.push(this.setHTTPCredentials(this._options.httpCredentials));
|
||||||
if (this._options.geolocation)
|
if (this._options.geolocation)
|
||||||
|
|
@ -294,12 +293,12 @@ export class FFBrowserContext extends BrowserContextBase {
|
||||||
await this._browser._connection.send('Browser.setGeolocationOverride', { browserContextId: this._browserContextId || undefined, geolocation: geolocation || null });
|
await this._browser._connection.send('Browser.setGeolocationOverride', { browserContextId: this._browserContextId || undefined, geolocation: geolocation || null });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
|
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
this._options.extraHTTPHeaders = headers;
|
||||||
const allHeaders = { ...this._options.extraHTTPHeaders };
|
let allHeaders = this._options.extraHTTPHeaders;
|
||||||
if (this._options.locale)
|
if (this._options.locale)
|
||||||
allHeaders['Accept-Language'] = this._options.locale;
|
allHeaders = network.mergeHeaders([allHeaders, network.singleHeader('Accept-Language', this._options.locale)]);
|
||||||
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(allHeaders) });
|
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: allHeaders });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setOffline(offline: boolean): Promise<void> {
|
async setOffline(offline: boolean): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,7 @@ export class FFNetworkManager {
|
||||||
throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
|
throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
|
||||||
return Buffer.from(response.base64body, 'base64');
|
return Buffer.from(response.base64body, 'base64');
|
||||||
};
|
};
|
||||||
const headers: types.Headers = {};
|
const response = new network.Response(request.request, event.status, event.statusText, event.headers, getResponseBody);
|
||||||
for (const {name, value} of event.headers)
|
|
||||||
headers[name.toLowerCase()] = value;
|
|
||||||
const response = new network.Response(request.request, event.status, event.statusText, headers, getResponseBody);
|
|
||||||
this._page._frameManager.requestReceivedResponse(response);
|
this._page._frameManager.requestReceivedResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,14 +147,11 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||||
this._id = payload.requestId;
|
this._id = payload.requestId;
|
||||||
this._session = session;
|
this._session = session;
|
||||||
|
|
||||||
const headers: types.Headers = {};
|
|
||||||
for (const {name, value} of payload.headers)
|
|
||||||
headers[name.toLowerCase()] = value;
|
|
||||||
let postDataBuffer = null;
|
let postDataBuffer = null;
|
||||||
if (payload.postData)
|
if (payload.postData)
|
||||||
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
||||||
this.request = new network.Request(payload.isIntercepted ? this : null, frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
this.request = new network.Request(payload.isIntercepted ? this : null, frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
||||||
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, headers);
|
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: types.NormalizedContinueOverrides) {
|
async continue(overrides: types.NormalizedContinueOverrides) {
|
||||||
|
|
@ -188,12 +182,3 @@ class InterceptableRequest implements network.RouteDelegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function headersArray(headers: types.Headers): Protocol.Network.HTTPHeader[] {
|
|
||||||
const result: Protocol.Network.HTTPHeader[] = [];
|
|
||||||
for (const name in headers) {
|
|
||||||
if (!Object.is(headers[name], undefined))
|
|
||||||
result.push({name, value: headers[name] + ''});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { FFBrowserContext } from './ffBrowser';
|
||||||
import { FFSession, FFSessionEvents } from './ffConnection';
|
import { FFSession, FFSessionEvents } from './ffConnection';
|
||||||
import { FFExecutionContext } from './ffExecutionContext';
|
import { FFExecutionContext } from './ffExecutionContext';
|
||||||
import { RawKeyboardImpl, RawMouseImpl } from './ffInput';
|
import { RawKeyboardImpl, RawMouseImpl } from './ffInput';
|
||||||
import { FFNetworkManager, headersArray } from './ffNetworkManager';
|
import { FFNetworkManager } from './ffNetworkManager';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { selectors } from '../selectors';
|
import { selectors } from '../selectors';
|
||||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||||
|
|
@ -272,7 +272,7 @@ export class FFPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateExtraHTTPHeaders(): Promise<void> {
|
async updateExtraHTTPHeaders(): Promise<void> {
|
||||||
await this._session.send('Network.setExtraHTTPHeaders', { headers: headersArray(this._page._state.extraHTTPHeaders || {}) });
|
await this._session.send('Network.setExtraHTTPHeaders', { headers: this._page._state.extraHTTPHeaders || [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setViewportSize(viewportSize: types.Size): Promise<void> {
|
async setViewportSize(viewportSize: types.Size): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -428,8 +428,9 @@ export class Frame {
|
||||||
return runNavigationTask(this, options, async progress => {
|
return runNavigationTask(this, options, async progress => {
|
||||||
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
const waitUntil = verifyLifecycle('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
|
||||||
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
|
progress.log(`navigating to "${url}", waiting until "${waitUntil}"`);
|
||||||
const headers = (this._page._state.extraHTTPHeaders || {});
|
const headers = this._page._state.extraHTTPHeaders || [];
|
||||||
let referer = headers['referer'] || headers['Referer'];
|
const refererHeader = headers.find(h => h.name === 'referer' || h.name === 'Referer');
|
||||||
|
let referer = refererHeader ? refererHeader.value : undefined;
|
||||||
if (options.referer !== undefined) {
|
if (options.referer !== undefined) {
|
||||||
if (referer !== undefined && referer !== options.referer)
|
if (referer !== undefined && referer !== options.referer)
|
||||||
throw new Error('"referer" is already specified as extra HTTP header');
|
throw new Error('"referer" is already specified as extra HTTP header');
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@
|
||||||
|
|
||||||
import * as frames from './frames';
|
import * as frames from './frames';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { assert, helper } from './helper';
|
import { assert } from './helper';
|
||||||
import { normalizeFulfillParameters, normalizeContinueOverrides } from './converters';
|
|
||||||
|
|
||||||
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
|
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
|
||||||
const parsedURLs = urls.map(s => new URL(s));
|
const parsedURLs = urls.map(s => new URL(s));
|
||||||
|
|
@ -78,13 +77,13 @@ export class Request {
|
||||||
private _resourceType: string;
|
private _resourceType: string;
|
||||||
private _method: string;
|
private _method: string;
|
||||||
private _postData: Buffer | null;
|
private _postData: Buffer | null;
|
||||||
private _headers: types.Headers;
|
private _headers: types.HeadersArray;
|
||||||
private _frame: frames.Frame;
|
private _frame: frames.Frame;
|
||||||
private _waitForResponsePromise: Promise<Response | null>;
|
private _waitForResponsePromise: Promise<Response | null>;
|
||||||
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
|
private _waitForResponsePromiseCallback: (value: Response | null) => void = () => {};
|
||||||
|
|
||||||
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
|
constructor(routeDelegate: RouteDelegate | null, frame: frames.Frame, redirectedFrom: Request | null, documentId: string | undefined,
|
||||||
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.Headers) {
|
url: string, resourceType: string, method: string, postData: Buffer | null, headers: types.HeadersArray) {
|
||||||
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
|
assert(!url.startsWith('data:'), 'Data urls should not fire requests');
|
||||||
assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
|
assert(!(routeDelegate && redirectedFrom), 'Should not be able to intercept redirects');
|
||||||
this._routeDelegate = routeDelegate;
|
this._routeDelegate = routeDelegate;
|
||||||
|
|
@ -123,8 +122,8 @@ export class Request {
|
||||||
return this._postData;
|
return this._postData;
|
||||||
}
|
}
|
||||||
|
|
||||||
headers(): {[key: string]: string} {
|
headers(): types.HeadersArray {
|
||||||
return { ...this._headers };
|
return this._headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
response(): Promise<Response | null> {
|
response(): Promise<Response | null> {
|
||||||
|
|
@ -191,15 +190,15 @@ export class Route {
|
||||||
await this._delegate.abort(errorCode);
|
await this._delegate.abort(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: types.FulfillResponse & { path?: string }) {
|
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||||
assert(!this._handled, 'Route is already handled!');
|
assert(!this._handled, 'Route is already handled!');
|
||||||
this._handled = true;
|
this._handled = true;
|
||||||
await this._delegate.fulfill(await normalizeFulfillParameters(response));
|
await this._delegate.fulfill(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: types.ContinueOverrides = {}) {
|
async continue(overrides: types.NormalizedContinueOverrides = {}) {
|
||||||
assert(!this._handled, 'Route is already handled!');
|
assert(!this._handled, 'Route is already handled!');
|
||||||
await this._delegate.continue(normalizeContinueOverrides(overrides));
|
await this._delegate.continue(overrides);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,10 +214,10 @@ export class Response {
|
||||||
private _status: number;
|
private _status: number;
|
||||||
private _statusText: string;
|
private _statusText: string;
|
||||||
private _url: string;
|
private _url: string;
|
||||||
private _headers: types.Headers;
|
private _headers: types.HeadersArray;
|
||||||
private _getResponseBodyCallback: GetResponseBodyCallback;
|
private _getResponseBodyCallback: GetResponseBodyCallback;
|
||||||
|
|
||||||
constructor(request: Request, status: number, statusText: string, headers: types.Headers, getResponseBodyCallback: GetResponseBodyCallback) {
|
constructor(request: Request, status: number, statusText: string, headers: types.HeadersArray, getResponseBodyCallback: GetResponseBodyCallback) {
|
||||||
this._request = request;
|
this._request = request;
|
||||||
this._status = status;
|
this._status = status;
|
||||||
this._statusText = statusText;
|
this._statusText = statusText;
|
||||||
|
|
@ -247,8 +246,8 @@ export class Response {
|
||||||
return this._statusText;
|
return this._statusText;
|
||||||
}
|
}
|
||||||
|
|
||||||
headers(): types.Headers {
|
headers(): types.HeadersArray {
|
||||||
return { ...this._headers };
|
return this._headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
finished(): Promise<Error | null> {
|
finished(): Promise<Error | null> {
|
||||||
|
|
@ -348,30 +347,24 @@ export const STATUS_TEXTS: { [status: string]: string } = {
|
||||||
'511': 'Network Authentication Required',
|
'511': 'Network Authentication Required',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function verifyHeaders(headers: types.Headers): types.Headers {
|
export function singleHeader(name: string, value: string): types.HeadersArray {
|
||||||
const result: types.Headers = {};
|
return [{ name, value }];
|
||||||
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: (types.Headers | undefined | null)[]): types.Headers {
|
export function mergeHeaders(headers: (types.HeadersArray | undefined | null)[]): types.HeadersArray {
|
||||||
const lowerCaseToValue = new Map<string, string>();
|
const lowerCaseToValue = new Map<string, string>();
|
||||||
const lowerCaseToOriginalCase = new Map<string, string>();
|
const lowerCaseToOriginalCase = new Map<string, string>();
|
||||||
for (const h of headers) {
|
for (const h of headers) {
|
||||||
if (!h)
|
if (!h)
|
||||||
continue;
|
continue;
|
||||||
for (const key of Object.keys(h)) {
|
for (const { name, value } of h) {
|
||||||
const lower = key.toLowerCase();
|
const lower = name.toLowerCase();
|
||||||
lowerCaseToOriginalCase.set(lower, key);
|
lowerCaseToOriginalCase.set(lower, name);
|
||||||
lowerCaseToValue.set(lower, h[key]);
|
lowerCaseToValue.set(lower, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result: types.Headers = {};
|
const result: types.HeadersArray = [];
|
||||||
for (const [lower, value] of lowerCaseToValue)
|
for (const [lower, value] of lowerCaseToValue)
|
||||||
result[lowerCaseToOriginalCase.get(lower)!] = value;
|
result.push({ name: lowerCaseToOriginalCase.get(lower)!, value });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ type PageState = {
|
||||||
viewportSize: types.Size | null;
|
viewportSize: types.Size | null;
|
||||||
mediaType: types.MediaType | null;
|
mediaType: types.MediaType | null;
|
||||||
colorScheme: types.ColorScheme | null;
|
colorScheme: types.ColorScheme | null;
|
||||||
extraHTTPHeaders: types.Headers | null;
|
extraHTTPHeaders: types.HeadersArray | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Page extends EventEmitter {
|
export class Page extends EventEmitter {
|
||||||
|
|
@ -214,8 +214,8 @@ export class Page extends EventEmitter {
|
||||||
await this._delegate.exposeBinding(binding);
|
await this._delegate.exposeBinding(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
setExtraHTTPHeaders(headers: types.Headers) {
|
setExtraHTTPHeaders(headers: types.HeadersArray) {
|
||||||
this._state.extraHTTPHeaders = network.verifyHeaders(headers);
|
this._state.extraHTTPHeaders = headers;
|
||||||
return this._delegate.updateExtraHTTPHeaders();
|
return this._delegate.updateExtraHTTPHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { Events } from './events';
|
||||||
import { BrowserType } from './browserType';
|
import { BrowserType } from './browserType';
|
||||||
import { headersObjectToArray } from '../../converters';
|
import { headersObjectToArray } from '../../converters';
|
||||||
import { BrowserContextOptions } from './types';
|
import { BrowserContextOptions } from './types';
|
||||||
|
import { validateHeaders } from './network';
|
||||||
|
|
||||||
export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
|
export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
|
||||||
readonly _contexts = new Set<BrowserContext>();
|
readonly _contexts = new Set<BrowserContext>();
|
||||||
|
|
@ -47,8 +48,9 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
|
||||||
|
|
||||||
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
|
||||||
const logger = options.logger;
|
const logger = options.logger;
|
||||||
options = { ...options, logger: undefined };
|
|
||||||
return this._wrapApiCall('browser.newContext', async () => {
|
return this._wrapApiCall('browser.newContext', async () => {
|
||||||
|
if (options.extraHTTPHeaders)
|
||||||
|
validateHeaders(options.extraHTTPHeaders);
|
||||||
const contextOptions: BrowserNewContextParams = {
|
const contextOptions: BrowserNewContextParams = {
|
||||||
...options,
|
...options,
|
||||||
viewport: options.viewport === null ? undefined : options.viewport,
|
viewport: options.viewport === null ? undefined : options.viewport,
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
|
||||||
|
|
||||||
async setExtraHTTPHeaders(headers: Headers): Promise<void> {
|
async setExtraHTTPHeaders(headers: Headers): Promise<void> {
|
||||||
return this._wrapApiCall('browserContext.setExtraHTTPHeaders', async () => {
|
return this._wrapApiCall('browserContext.setExtraHTTPHeaders', async () => {
|
||||||
|
network.validateHeaders(headers);
|
||||||
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import { Events } from './events';
|
||||||
import { TimeoutSettings } from '../../timeoutSettings';
|
import { TimeoutSettings } from '../../timeoutSettings';
|
||||||
import { ChildProcess } from 'child_process';
|
import { ChildProcess } from 'child_process';
|
||||||
import { envObjectToArray } from './clientHelper';
|
import { envObjectToArray } from './clientHelper';
|
||||||
|
import { validateHeaders } from './network';
|
||||||
|
|
||||||
export interface BrowserServerLauncher {
|
export interface BrowserServerLauncher {
|
||||||
launchServer(options?: LaunchServerOptions): Promise<BrowserServer>;
|
launchServer(options?: LaunchServerOptions): Promise<BrowserServer>;
|
||||||
|
|
@ -88,6 +89,8 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
|
||||||
const logger = options.logger;
|
const logger = options.logger;
|
||||||
options = { ...options, logger: undefined };
|
options = { ...options, logger: undefined };
|
||||||
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
|
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
|
||||||
|
if (options.extraHTTPHeaders)
|
||||||
|
validateHeaders(options.extraHTTPHeaders);
|
||||||
const persistentOptions: BrowserTypeLaunchPersistentContextParams = {
|
const persistentOptions: BrowserTypeLaunchPersistentContextParams = {
|
||||||
...options,
|
...options,
|
||||||
viewport: options.viewport === null ? undefined : options.viewport,
|
viewport: options.viewport === null ? undefined : options.viewport,
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,12 @@ import { URLSearchParams } from 'url';
|
||||||
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
|
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
|
||||||
import { ChannelOwner } from './channelOwner';
|
import { ChannelOwner } from './channelOwner';
|
||||||
import { Frame } from './frame';
|
import { Frame } from './frame';
|
||||||
import { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides } from '../../converters';
|
import { headersArrayToObject, headersObjectToArray } from '../../converters';
|
||||||
import { Headers } from './types';
|
import { Headers } from './types';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as mime from 'mime';
|
||||||
|
import * as util from 'util';
|
||||||
|
import { helper } from '../../helper';
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -44,19 +48,6 @@ export type SetNetworkCookieParam = {
|
||||||
sameSite?: 'Strict' | 'Lax' | 'None'
|
sameSite?: 'Strict' | 'Lax' | 'None'
|
||||||
};
|
};
|
||||||
|
|
||||||
type FulfillResponse = {
|
|
||||||
status?: number,
|
|
||||||
headers?: Headers,
|
|
||||||
contentType?: string,
|
|
||||||
body?: string | Buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
type ContinueOverrides = {
|
|
||||||
method?: string,
|
|
||||||
headers?: Headers,
|
|
||||||
postData?: string | Buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
|
export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
|
||||||
private _redirectedFrom: Request | null = null;
|
private _redirectedFrom: Request | null = null;
|
||||||
private _redirectedTo: Request | null = null;
|
private _redirectedTo: Request | null = null;
|
||||||
|
|
@ -77,7 +68,7 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
|
||||||
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
|
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
|
||||||
if (this._redirectedFrom)
|
if (this._redirectedFrom)
|
||||||
this._redirectedFrom._redirectedTo = this;
|
this._redirectedFrom._redirectedTo = this;
|
||||||
this._headers = headersArrayToObject(initializer.headers);
|
this._headers = headersArrayToObject(initializer.headers, true /* lowerCase */);
|
||||||
this._postData = initializer.postData ? Buffer.from(initializer.postData, 'base64') : null;
|
this._postData = initializer.postData ? Buffer.from(initializer.postData, 'base64') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,17 +166,49 @@ export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
|
||||||
await this._channel.abort({ errorCode });
|
await this._channel.abort({ errorCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: FulfillResponse & { path?: string }) {
|
async fulfill(response: { status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string }) {
|
||||||
const normalized = await normalizeFulfillParameters(response);
|
let body = '';
|
||||||
await this._channel.fulfill(normalized);
|
let isBase64 = false;
|
||||||
|
let length = 0;
|
||||||
|
if (response.path) {
|
||||||
|
const buffer = await util.promisify(fs.readFile)(response.path);
|
||||||
|
body = buffer.toString('base64');
|
||||||
|
isBase64 = true;
|
||||||
|
length = buffer.length;
|
||||||
|
} else if (helper.isString(response.body)) {
|
||||||
|
body = response.body;
|
||||||
|
isBase64 = false;
|
||||||
|
length = Buffer.byteLength(body);
|
||||||
|
} else if (response.body) {
|
||||||
|
body = response.body.toString('base64');
|
||||||
|
isBase64 = true;
|
||||||
|
length = response.body.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
async continue(overrides: ContinueOverrides = {}) {
|
const headers: Headers = {};
|
||||||
const normalized = normalizeContinueOverrides(overrides);
|
for (const header of Object.keys(response.headers || {}))
|
||||||
|
headers[header.toLowerCase()] = String(response.headers![header]);
|
||||||
|
if (response.contentType)
|
||||||
|
headers['content-type'] = String(response.contentType);
|
||||||
|
else if (response.path)
|
||||||
|
headers['content-type'] = mime.getType(response.path) || 'application/octet-stream';
|
||||||
|
if (length && !('content-length' in headers))
|
||||||
|
headers['content-length'] = String(length);
|
||||||
|
|
||||||
|
await this._channel.fulfill({
|
||||||
|
status: response.status || 200,
|
||||||
|
headers: headersObjectToArray(headers),
|
||||||
|
body,
|
||||||
|
isBase64
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async continue(overrides: { method?: string, headers?: Headers, postData?: string | Buffer } = {}) {
|
||||||
|
const postDataBuffer = helper.isString(overrides.postData) ? Buffer.from(overrides.postData, 'utf8') : overrides.postData;
|
||||||
await this._channel.continue({
|
await this._channel.continue({
|
||||||
method: normalized.method,
|
method: overrides.method,
|
||||||
headers: normalized.headers,
|
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
|
||||||
postData: normalized.postData ? normalized.postData.toString('base64') : undefined
|
postData: postDataBuffer ? postDataBuffer.toString('base64') : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +228,7 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: ResponseInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: ResponseInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
this._headers = headersArrayToObject(initializer.headers);
|
this._headers = headersArrayToObject(initializer.headers, true /* lowerCase */);
|
||||||
}
|
}
|
||||||
|
|
||||||
url(): string {
|
url(): string {
|
||||||
|
|
@ -257,3 +280,11 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
|
||||||
return Request.from(this._initializer.request).frame();
|
return Request.from(this._initializer.request).frame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateHeaders(headers: Headers) {
|
||||||
|
for (const key of Object.keys(headers)) {
|
||||||
|
const value = headers[key];
|
||||||
|
if (!Object.is(value, undefined) && !helper.isString(value))
|
||||||
|
throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import { Worker } from './worker';
|
||||||
import { Frame, FunctionWithSource, verifyLoadState, WaitForNavigationOptions } from './frame';
|
import { Frame, FunctionWithSource, verifyLoadState, WaitForNavigationOptions } from './frame';
|
||||||
import { Keyboard, Mouse } from './input';
|
import { Keyboard, Mouse } from './input';
|
||||||
import { assertMaxArguments, Func1, FuncOn, SmartHandle, serializeArgument, parseResult } from './jsHandle';
|
import { assertMaxArguments, Func1, FuncOn, SmartHandle, serializeArgument, parseResult } from './jsHandle';
|
||||||
import { Request, Response, Route, RouteHandler } from './network';
|
import { Request, Response, Route, RouteHandler, validateHeaders } from './network';
|
||||||
import { FileChooser } from './fileChooser';
|
import { FileChooser } from './fileChooser';
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
import { ChromiumCoverage } from './chromiumCoverage';
|
import { ChromiumCoverage } from './chromiumCoverage';
|
||||||
|
|
@ -291,6 +291,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
|
||||||
|
|
||||||
async setExtraHTTPHeaders(headers: Headers) {
|
async setExtraHTTPHeaders(headers: Headers) {
|
||||||
return this._wrapApiCall('page.setExtraHTTPHeaders', async () => {
|
return this._wrapApiCall('page.setExtraHTTPHeaders', async () => {
|
||||||
|
validateHeaders(headers);
|
||||||
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||||
import { CRBrowserContext } from '../../chromium/crBrowser';
|
import { CRBrowserContext } from '../../chromium/crBrowser';
|
||||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||||
import { Events as ChromiumEvents } from '../../chromium/events';
|
import { Events as ChromiumEvents } from '../../chromium/events';
|
||||||
import { headersArrayToObject } from '../../converters';
|
|
||||||
|
|
||||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, BrowserContextInitializer> implements BrowserContextChannel {
|
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, BrowserContextInitializer> implements BrowserContextChannel {
|
||||||
private _context: BrowserContextBase;
|
private _context: BrowserContextBase;
|
||||||
|
|
@ -96,7 +95,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, Browser
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
|
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
|
||||||
await this._context.setExtraHTTPHeaders(headersArrayToObject(params.headers));
|
await this._context.setExtraHTTPHeaders(params.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setOffline(params: { offline: boolean }): Promise<void> {
|
async setOffline(params: { offline: boolean }): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||||
import { CRBrowser } from '../../chromium/crBrowser';
|
import { CRBrowser } from '../../chromium/crBrowser';
|
||||||
import { PageDispatcher } from './pageDispatcher';
|
import { PageDispatcher } from './pageDispatcher';
|
||||||
import { headersArrayToObject } from '../../converters';
|
|
||||||
|
|
||||||
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
|
export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> implements BrowserChannel {
|
||||||
constructor(scope: DispatcherScope, browser: BrowserBase, guid?: string) {
|
constructor(scope: DispatcherScope, browser: BrowserBase, guid?: string) {
|
||||||
|
|
@ -37,11 +36,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, BrowserInitializer> i
|
||||||
}
|
}
|
||||||
|
|
||||||
async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> {
|
async newContext(params: BrowserNewContextParams): Promise<{ context: BrowserContextChannel }> {
|
||||||
const options = {
|
return { context: new BrowserContextDispatcher(this._scope, await this._object.newContext(params) as BrowserContextBase) };
|
||||||
...params,
|
|
||||||
extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined,
|
|
||||||
};
|
|
||||||
return { context: new BrowserContextDispatcher(this._scope, await this._object.newContext(options) as BrowserContextBase) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import { BrowserChannel, BrowserTypeChannel, BrowserContextChannel, BrowserTypeI
|
||||||
import { Dispatcher, DispatcherScope } from './dispatcher';
|
import { Dispatcher, DispatcherScope } from './dispatcher';
|
||||||
import { BrowserContextBase } from '../../browserContext';
|
import { BrowserContextBase } from '../../browserContext';
|
||||||
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||||
import { headersArrayToObject } from '../../converters';
|
|
||||||
|
|
||||||
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel {
|
export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeInitializer> implements BrowserTypeChannel {
|
||||||
constructor(scope: DispatcherScope, browserType: BrowserTypeBase) {
|
constructor(scope: DispatcherScope, browserType: BrowserTypeBase) {
|
||||||
|
|
@ -37,11 +36,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, BrowserTypeIn
|
||||||
}
|
}
|
||||||
|
|
||||||
async launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams): Promise<{ context: BrowserContextChannel }> {
|
async launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams): Promise<{ context: BrowserContextChannel }> {
|
||||||
const options = {
|
const browserContext = await this._object.launchPersistentContext(params.userDataDir, params);
|
||||||
...params,
|
|
||||||
extraHTTPHeaders: params.extraHTTPHeaders ? headersArrayToObject(params.extraHTTPHeaders) : undefined,
|
|
||||||
};
|
|
||||||
const browserContext = await this._object.launchPersistentContext(params.userDataDir, options);
|
|
||||||
return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) };
|
return { context: new BrowserContextDispatcher(this._scope, browserContext as BrowserContextBase) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import { Request, Response, Route } from '../../network';
|
||||||
import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels';
|
import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels';
|
||||||
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
|
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
|
||||||
import { FrameDispatcher } from './frameDispatcher';
|
import { FrameDispatcher } from './frameDispatcher';
|
||||||
import { headersObjectToArray, headersArrayToObject } from '../../converters';
|
|
||||||
import * as types from '../../types';
|
import * as types from '../../types';
|
||||||
|
|
||||||
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {
|
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {
|
||||||
|
|
@ -40,7 +39,7 @@ export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> i
|
||||||
resourceType: request.resourceType(),
|
resourceType: request.resourceType(),
|
||||||
method: request.method(),
|
method: request.method(),
|
||||||
postData: postData === null ? undefined : postData.toString('base64'),
|
postData: postData === null ? undefined : postData.toString('base64'),
|
||||||
headers: headersObjectToArray(request.headers()),
|
headers: request.headers(),
|
||||||
isNavigationRequest: request.isNavigationRequest(),
|
isNavigationRequest: request.isNavigationRequest(),
|
||||||
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
|
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
|
||||||
});
|
});
|
||||||
|
|
@ -60,7 +59,7 @@ export class ResponseDispatcher extends Dispatcher<Response, ResponseInitializer
|
||||||
url: response.url(),
|
url: response.url(),
|
||||||
status: response.status(),
|
status: response.status(),
|
||||||
statusText: response.statusText(),
|
statusText: response.statusText(),
|
||||||
headers: headersObjectToArray(response.headers()),
|
headers: response.headers(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,17 +84,13 @@ export class RouteDispatcher extends Dispatcher<Route, RouteInitializer> impleme
|
||||||
async continue(params: { method?: string, headers?: types.HeadersArray, postData?: string }): Promise<void> {
|
async continue(params: { method?: string, headers?: types.HeadersArray, postData?: string }): Promise<void> {
|
||||||
await this._object.continue({
|
await this._object.continue({
|
||||||
method: params.method,
|
method: params.method,
|
||||||
headers: params.headers ? headersArrayToObject(params.headers) : undefined,
|
headers: params.headers,
|
||||||
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
|
postData: params.postData ? Buffer.from(params.postData, 'base64') : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(params: types.NormalizedFulfillResponse): Promise<void> {
|
async fulfill(params: types.NormalizedFulfillResponse): Promise<void> {
|
||||||
await this._object.fulfill({
|
await this._object.fulfill(params);
|
||||||
status: params.status,
|
|
||||||
headers: params.headers ? headersArrayToObject(params.headers) : undefined,
|
|
||||||
body: params.isBase64 ? Buffer.from(params.body, 'base64') : params.body,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async abort(params: { errorCode?: string }): Promise<void> {
|
async abort(params: { errorCode?: string }): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import * as types from '../../types';
|
||||||
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError, PageAccessibilitySnapshotResult, SerializedValue, PageEmulateMediaParams, AXNode } from '../channels';
|
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, SerializedArgument, PagePdfParams, SerializedError, PageAccessibilitySnapshotResult, SerializedValue, PageEmulateMediaParams, AXNode } from '../channels';
|
||||||
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
|
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
|
||||||
import { parseError, serializeError } from '../serializers';
|
import { parseError, serializeError } from '../serializers';
|
||||||
import { headersArrayToObject } from '../../converters';
|
|
||||||
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
|
||||||
import { DialogDispatcher } from './dialogDispatcher';
|
import { DialogDispatcher } from './dialogDispatcher';
|
||||||
import { DownloadDispatcher } from './downloadDispatcher';
|
import { DownloadDispatcher } from './downloadDispatcher';
|
||||||
|
|
@ -92,7 +91,7 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
|
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
|
||||||
await this._page.setExtraHTTPHeaders(headersArrayToObject(params.headers));
|
await this._page.setExtraHTTPHeaders(params.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
async reload(params: types.NavigateOptions): Promise<{ response?: ResponseChannel }> {
|
async reload(params: types.NavigateOptions): Promise<{ response?: ResponseChannel }> {
|
||||||
|
|
|
||||||
16
src/types.ts
16
src/types.ts
|
|
@ -208,20 +208,12 @@ export type MouseMultiClickOptions = PointerActionOptions & {
|
||||||
|
|
||||||
export type World = 'main' | 'utility';
|
export type World = 'main' | 'utility';
|
||||||
|
|
||||||
export type Headers = { [key: string]: string };
|
|
||||||
export type HeadersArray = { name: string, value: string }[];
|
export type HeadersArray = { name: string, value: string }[];
|
||||||
|
|
||||||
export type GotoOptions = NavigateOptions & {
|
export type GotoOptions = NavigateOptions & {
|
||||||
referer?: string,
|
referer?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FulfillResponse = {
|
|
||||||
status?: number,
|
|
||||||
headers?: Headers,
|
|
||||||
contentType?: string,
|
|
||||||
body?: string | Buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NormalizedFulfillResponse = {
|
export type NormalizedFulfillResponse = {
|
||||||
status: number,
|
status: number,
|
||||||
headers: HeadersArray,
|
headers: HeadersArray,
|
||||||
|
|
@ -229,12 +221,6 @@ export type NormalizedFulfillResponse = {
|
||||||
isBase64: boolean,
|
isBase64: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContinueOverrides = {
|
|
||||||
method?: string,
|
|
||||||
headers?: Headers,
|
|
||||||
postData?: string | Buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NormalizedContinueOverrides = {
|
export type NormalizedContinueOverrides = {
|
||||||
method?: string,
|
method?: string,
|
||||||
headers?: HeadersArray,
|
headers?: HeadersArray,
|
||||||
|
|
@ -275,7 +261,7 @@ export type BrowserContextOptions = {
|
||||||
timezoneId?: string,
|
timezoneId?: string,
|
||||||
geolocation?: Geolocation,
|
geolocation?: Geolocation,
|
||||||
permissions?: string[],
|
permissions?: string[],
|
||||||
extraHTTPHeaders?: Headers,
|
extraHTTPHeaders?: HeadersArray,
|
||||||
offline?: boolean,
|
offline?: boolean,
|
||||||
httpCredentials?: Credentials,
|
httpCredentials?: Credentials,
|
||||||
deviceScaleFactor?: number,
|
deviceScaleFactor?: number,
|
||||||
|
|
|
||||||
|
|
@ -293,8 +293,8 @@ export class WKBrowserContext extends BrowserContextBase {
|
||||||
await this._browser._browserSession.send('Playwright.setGeolocationOverride', { browserContextId: this._browserContextId, geolocation: payload });
|
await this._browser._browserSession.send('Playwright.setGeolocationOverride', { browserContextId: this._browserContextId, geolocation: payload });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
|
async setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void> {
|
||||||
this._options.extraHTTPHeaders = network.verifyHeaders(headers);
|
this._options.extraHTTPHeaders = headers;
|
||||||
for (const page of this.pages())
|
for (const page of this.pages())
|
||||||
await (page._delegate as WKPage).updateExtraHTTPHeaders();
|
await (page._delegate as WKPage).updateExtraHTTPHeaders();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import * as network from '../network';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { WKSession } from './wkConnection';
|
import { WKSession } from './wkConnection';
|
||||||
import { headersArrayToObject } from '../converters';
|
import { headersArrayToObject, headersObjectToArray } from '../converters';
|
||||||
|
|
||||||
const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
|
const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
|
||||||
'aborted': 'Cancellation',
|
'aborted': 'Cancellation',
|
||||||
|
|
@ -57,7 +57,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||||
if (event.request.postData)
|
if (event.request.postData)
|
||||||
postDataBuffer = Buffer.from(event.request.postData, 'binary');
|
postDataBuffer = Buffer.from(event.request.postData, 'binary');
|
||||||
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url,
|
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url,
|
||||||
resourceType, event.request.method, postDataBuffer, headersObject(event.request.headers));
|
resourceType, event.request.method, postDataBuffer, headersObjectToArray(event.request.headers));
|
||||||
this._interceptedPromise = new Promise(f => this._interceptedCallback = f);
|
this._interceptedPromise = new Promise(f => this._interceptedCallback = f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||||
// In certain cases, protocol will return error if the request was already canceled
|
// In certain cases, protocol will return error if the request was already canceled
|
||||||
// or the page was closed. We should tolerate these errors.
|
// or the page was closed. We should tolerate these errors.
|
||||||
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
|
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
|
||||||
const headers = headersArrayToObject(response.headers);
|
const headers = headersArrayToObject(response.headers, false /* lowerCase */);
|
||||||
const contentType = headers['content-type'];
|
const contentType = headers['content-type'];
|
||||||
if (contentType)
|
if (contentType)
|
||||||
mimeType = contentType.split(';')[0].trim();
|
mimeType = contentType.split(';')[0].trim();
|
||||||
|
|
@ -98,7 +98,7 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||||
await this._session.sendMayFail('Network.interceptWithRequest', {
|
await this._session.sendMayFail('Network.interceptWithRequest', {
|
||||||
requestId: this._requestId,
|
requestId: this._requestId,
|
||||||
method: overrides.method,
|
method: overrides.method,
|
||||||
headers: overrides.headers ? headersArrayToObject(overrides.headers) : undefined,
|
headers: overrides.headers ? headersArrayToObject(overrides.headers, false /* lowerCase */) : undefined,
|
||||||
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
|
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -108,13 +108,6 @@ export class WKInterceptableRequest implements network.RouteDelegate {
|
||||||
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
|
||||||
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
|
||||||
};
|
};
|
||||||
return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);
|
return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), getResponseBody);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function headersObject(headers: Protocol.Network.Headers): types.Headers {
|
|
||||||
const result: types.Headers = {};
|
|
||||||
for (const key of Object.keys(headers))
|
|
||||||
result[key.toLowerCase()] = headers[key];
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import { selectors } from '../selectors';
|
||||||
import * as jpeg from 'jpeg-js';
|
import * as jpeg from 'jpeg-js';
|
||||||
import * as png from 'pngjs';
|
import * as png from 'pngjs';
|
||||||
import { JSHandle } from '../javascript';
|
import { JSHandle } from '../javascript';
|
||||||
|
import { headersArrayToObject } from '../converters';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||||
|
|
@ -175,7 +176,7 @@ export class WKPage implements PageDelegate {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
promises.push(this.updateEmulateMedia());
|
promises.push(this.updateEmulateMedia());
|
||||||
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }));
|
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(this._calculateExtraHTTPHeaders(), false /* lowerCase */) }));
|
||||||
if (contextOptions.offline)
|
if (contextOptions.offline)
|
||||||
promises.push(session.send('Network.setEmulateOfflineState', { offline: true }));
|
promises.push(session.send('Network.setEmulateOfflineState', { offline: true }));
|
||||||
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: !!contextOptions.hasTouch }));
|
promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: !!contextOptions.hasTouch }));
|
||||||
|
|
@ -551,17 +552,16 @@ export class WKPage implements PageDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateExtraHTTPHeaders(): Promise<void> {
|
async updateExtraHTTPHeaders(): Promise<void> {
|
||||||
await this._updateState('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() });
|
await this._updateState('Network.setExtraHTTPHeaders', { headers: headersArrayToObject(this._calculateExtraHTTPHeaders(), false /* lowerCase */) });
|
||||||
}
|
}
|
||||||
|
|
||||||
_calculateExtraHTTPHeaders(): types.Headers {
|
_calculateExtraHTTPHeaders(): types.HeadersArray {
|
||||||
|
const locale = this._browserContext._options.locale;
|
||||||
const headers = network.mergeHeaders([
|
const headers = network.mergeHeaders([
|
||||||
this._browserContext._options.extraHTTPHeaders,
|
this._browserContext._options.extraHTTPHeaders,
|
||||||
this._page._state.extraHTTPHeaders
|
this._page._state.extraHTTPHeaders,
|
||||||
|
locale ? network.singleHeader('Accept-Language', locale) : undefined,
|
||||||
]);
|
]);
|
||||||
const locale = this._browserContext._options.locale;
|
|
||||||
if (locale)
|
|
||||||
headers['Accept-Language'] = locale;
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,13 @@ import path from 'path';
|
||||||
it('should work', async({page, server}) => {
|
it('should work', async({page, server}) => {
|
||||||
server.setRoute('/empty.html', (req, res) => {
|
server.setRoute('/empty.html', (req, res) => {
|
||||||
res.setHeader('foo', 'bar');
|
res.setHeader('foo', 'bar');
|
||||||
|
res.setHeader('BaZ', 'bAz');
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
const response = await page.goto(server.EMPTY_PAGE);
|
const response = await page.goto(server.EMPTY_PAGE);
|
||||||
expect(response.headers()['foo']).toBe('bar');
|
expect(response.headers()['foo']).toBe('bar');
|
||||||
|
expect(response.headers()['baz']).toBe('bAz');
|
||||||
|
expect(response.headers()['BaZ']).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,15 @@ import './base.fixture';
|
||||||
|
|
||||||
it('should work', async({page, server}) => {
|
it('should work', async({page, server}) => {
|
||||||
await page.setExtraHTTPHeaders({
|
await page.setExtraHTTPHeaders({
|
||||||
foo: 'bar'
|
foo: 'bar',
|
||||||
|
baz: undefined,
|
||||||
});
|
});
|
||||||
const [request] = await Promise.all([
|
const [request] = await Promise.all([
|
||||||
server.waitForRequest('/empty.html'),
|
server.waitForRequest('/empty.html'),
|
||||||
page.goto(server.EMPTY_PAGE),
|
page.goto(server.EMPTY_PAGE),
|
||||||
]);
|
]);
|
||||||
expect(request.headers['foo']).toBe('bar');
|
expect(request.headers['foo']).toBe('bar');
|
||||||
|
expect(request.headers['baz']).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with redirects', async({page, server}) => {
|
it('should work with redirects', async({page, server}) => {
|
||||||
|
|
@ -70,12 +72,11 @@ it('should override extra headers from browser context', async({browser, server}
|
||||||
expect(request.headers['bar']).toBe('foO');
|
expect(request.headers['bar']).toBe('foO');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw for non-string header values', async({page, server}) => {
|
it('should throw for non-string header values', async({browser, page}) => {
|
||||||
let error = null;
|
const error1 = await page.setExtraHTTPHeaders({ 'foo': 1 as any }).catch(e => e);
|
||||||
try {
|
expect(error1.message).toContain('Expected value of header "foo" to be String, but "number" is found.');
|
||||||
await page.setExtraHTTPHeaders({ 'foo': 1 as any });
|
const error2 = await page.context().setExtraHTTPHeaders({ 'foo': true as any }).catch(e => e);
|
||||||
} catch (e) {
|
expect(error2.message).toContain('Expected value of header "foo" to be String, but "boolean" is found.');
|
||||||
error = e;
|
const error3 = await browser.newContext({ extraHTTPHeaders: { 'foo': null as any } }).catch(e => e);
|
||||||
}
|
expect(error3.message).toContain('Expected value of header "foo" to be String, but "object" is found.');
|
||||||
expect(error.message).toContain('Expected value of header "foo" to be String, but "number" is found.');
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue