chore: fix headers api again (#8854)
This commit is contained in:
parent
737b155869
commit
798d0bfa9b
|
|
@ -17,14 +17,6 @@ Disposes the body of this response. If not called then the body will stay in mem
|
||||||
An object with all the response HTTP headers associated with this response.
|
An object with all the response HTTP headers associated with this response.
|
||||||
|
|
||||||
## method: FetchResponse.headersArray
|
## method: FetchResponse.headersArray
|
||||||
* langs: js, csharp, python
|
|
||||||
- returns: <[Array]<[Array]<[string]>>>
|
|
||||||
|
|
||||||
An array with all the request HTTP headers associated with this response. Header names are not lower-cased.
|
|
||||||
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
|
||||||
|
|
||||||
## method: FetchResponse.headersArray
|
|
||||||
* langs: java
|
|
||||||
- returns: <[Array]<[Object]>>
|
- returns: <[Array]<[Object]>>
|
||||||
- `name` <[string]> Name of the header.
|
- `name` <[string]> Name of the header.
|
||||||
- `value` <[string]> Value of the header.
|
- `value` <[string]> Value of the header.
|
||||||
|
|
|
||||||
|
|
@ -62,21 +62,24 @@ Returns the [Frame] that initiated this request.
|
||||||
**DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use [`method: Request.allHeaders`] instead.
|
**DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use [`method: Request.allHeaders`] instead.
|
||||||
|
|
||||||
## async method: Request.headersArray
|
## async method: Request.headersArray
|
||||||
* langs: js, csharp, python
|
|
||||||
- returns: <[Array]<[Array]<[string]>>>
|
|
||||||
|
|
||||||
An array with all the request HTTP headers associated with this request. Unlike [`method: Request.allHeaders`], header names are not lower-cased.
|
|
||||||
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
|
||||||
|
|
||||||
## async method: Request.headersArray
|
|
||||||
* langs: java
|
|
||||||
- returns: <[Array]<[Object]>>
|
- returns: <[Array]<[Object]>>
|
||||||
- `name` <[string]> Name of the header.
|
- `name` <[string]> Name of the header.
|
||||||
- `value` <[string]> Value of the header.
|
- `value` <[string]> Value of the header.
|
||||||
|
|
||||||
An array with all the request HTTP headers associated with this request. Unlike [`method: Request.allHeaders`], header names are not lower-cased.
|
An array with all the request HTTP headers associated with this request. Unlike [`method: Request.allHeaders`], header names are NOT lower-cased.
|
||||||
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
||||||
|
|
||||||
|
## async method: Request.headerValue
|
||||||
|
- returns: <[null]|[string]>
|
||||||
|
|
||||||
|
Returns the value of the header matching the name. The name is case insensitive.
|
||||||
|
|
||||||
|
### param: Request.headerValue.name
|
||||||
|
- `name` <[string]>
|
||||||
|
|
||||||
|
Name of the header.
|
||||||
|
|
||||||
|
|
||||||
## method: Request.isNavigationRequest
|
## method: Request.isNavigationRequest
|
||||||
- returns: <[boolean]>
|
- returns: <[boolean]>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,34 @@ Returns the [Frame] that initiated this response.
|
||||||
**DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use [`method: Response.allHeaders`] instead.
|
**DEPRECATED** Incomplete list of headers as seen by the rendering engine. Use [`method: Response.allHeaders`] instead.
|
||||||
|
|
||||||
## async method: Response.headersArray
|
## async method: Response.headersArray
|
||||||
* langs: js, csharp, python
|
|
||||||
- returns: <[Array]<[Array]<[string]>>>
|
|
||||||
|
|
||||||
An array with all the request HTTP headers associated with this response. Unlike [`method: Response.allHeaders`], header names are not lower-cased.
|
|
||||||
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
|
||||||
|
|
||||||
## async method: Response.headersArray
|
|
||||||
* langs: java
|
|
||||||
- returns: <[Array]<[Object]>>
|
- returns: <[Array]<[Object]>>
|
||||||
- `name` <[string]> Name of the header.
|
- `name` <[string]> Name of the header.
|
||||||
- `value` <[string]> Value of the header.
|
- `value` <[string]> Value of the header.
|
||||||
|
|
||||||
An array with all the request HTTP headers associated with this response. Unlike [`method: Response.allHeaders`], header names are not lower-cased.
|
An array with all the request HTTP headers associated with this response. Unlike [`method: Response.allHeaders`], header names are NOT lower-cased.
|
||||||
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
||||||
|
|
||||||
|
## async method: Response.headerValue
|
||||||
|
- returns: <[null]|[string]>
|
||||||
|
|
||||||
|
Returns the value of the header matching the name. The name is case insensitive. If multiple headers have
|
||||||
|
the same name (except `set-cookie`), they are returned as a list separated by `, `. For `set-cookie`, the `\n` separator is used. If no headers are found, `null` is returned.
|
||||||
|
|
||||||
|
### param: Response.headerValue.name
|
||||||
|
- `name` <[string]>
|
||||||
|
|
||||||
|
Name of the header.
|
||||||
|
|
||||||
|
## async method: Response.headerValues
|
||||||
|
- returns: <[Array]<[string]>>
|
||||||
|
|
||||||
|
Returns all values of the headers matching the name, for example `set-cookie`. The name is case insensitive.
|
||||||
|
|
||||||
|
### param: Response.headerValues.name
|
||||||
|
- `name` <[string]>
|
||||||
|
|
||||||
|
Name of the header.
|
||||||
|
|
||||||
## async method: Response.json
|
## async method: Response.json
|
||||||
* langs: js, python
|
* langs: js, python
|
||||||
- returns: <[Serializable]>
|
- returns: <[Serializable]>
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,16 @@ import { Frame } from './frame';
|
||||||
import { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
|
import { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as mime from 'mime';
|
import * as mime from 'mime';
|
||||||
import { isString, headersObjectToArray, headersArrayToObject } from '../utils/utils';
|
import { isString, headersObjectToArray } from '../utils/utils';
|
||||||
import { ManualPromise } from '../utils/async';
|
import { ManualPromise } from '../utils/async';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { Page } from './page';
|
import { Page } from './page';
|
||||||
import { Waiter } from './waiter';
|
import { Waiter } from './waiter';
|
||||||
import * as api from '../../types/types';
|
import * as api from '../../types/types';
|
||||||
import { URLMatch } from '../common/types';
|
import { HeadersArray, URLMatch } from '../common/types';
|
||||||
import { urlMatches } from './clientHelper';
|
import { urlMatches } from './clientHelper';
|
||||||
import { BrowserContext } from './browserContext';
|
import { BrowserContext } from './browserContext';
|
||||||
|
import { MultiMap } from '../utils/multimap';
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
name: string,
|
name: string,
|
||||||
|
|
@ -58,8 +59,8 @@ export class Request extends ChannelOwner<channels.RequestChannel, channels.Requ
|
||||||
private _redirectedFrom: Request | null = null;
|
private _redirectedFrom: Request | null = null;
|
||||||
private _redirectedTo: Request | null = null;
|
private _redirectedTo: Request | null = null;
|
||||||
_failureText: string | null = null;
|
_failureText: string | null = null;
|
||||||
_headers: channels.NameValue[];
|
private _provisionalHeaders: RawHeaders;
|
||||||
private _allHeadersPromise: Promise<channels.NameValue[]> | undefined;
|
private _actualHeadersPromise: Promise<RawHeaders> | undefined;
|
||||||
private _postData: Buffer | null;
|
private _postData: Buffer | null;
|
||||||
_timing: ResourceTiming;
|
_timing: ResourceTiming;
|
||||||
|
|
||||||
|
|
@ -76,7 +77,7 @@ export class Request extends ChannelOwner<channels.RequestChannel, channels.Requ
|
||||||
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 = initializer.headers;
|
this._provisionalHeaders = new RawHeaders(initializer.headers);
|
||||||
this._postData = initializer.postData ? Buffer.from(initializer.postData, 'base64') : null;
|
this._postData = initializer.postData ? Buffer.from(initializer.postData, 'base64') : null;
|
||||||
this._timing = {
|
this._timing = {
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
|
@ -136,29 +137,33 @@ export class Request extends ChannelOwner<channels.RequestChannel, channels.Requ
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
headers(): Headers {
|
headers(): Headers {
|
||||||
return headersArrayToObject(this._headers, true /* lowerCase */);
|
return this._provisionalHeaders.headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getHeadersIfNeeded() {
|
_actualHeaders(): Promise<RawHeaders> {
|
||||||
if (!this._allHeadersPromise) {
|
if (!this._actualHeadersPromise) {
|
||||||
this._allHeadersPromise = this.response().then(response => {
|
this._actualHeadersPromise = this.response().then(response => {
|
||||||
// there is no response, so should we return the headers we have now?
|
// there is no response, so should we return the headers we have now?
|
||||||
if (!response)
|
if (!response)
|
||||||
return this._headers;
|
return this._provisionalHeaders;
|
||||||
return response._wrapApiCall(async (channel: channels.ResponseChannel) => {
|
return response._wrapApiCall(async (channel: channels.ResponseChannel) => {
|
||||||
return (await channel.rawRequestHeaders()).headers;
|
return new RawHeaders((await channel.rawRequestHeaders()).headers);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this._allHeadersPromise;
|
return this._actualHeadersPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async allHeaders(): Promise<Headers> {
|
async allHeaders(): Promise<Headers> {
|
||||||
return headersArrayToObject(await this._getHeadersIfNeeded(), true);
|
return (await this._actualHeaders()).headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
async headersArray(): Promise<string[][]> {
|
async headersArray(): Promise<HeadersArray> {
|
||||||
return (await this._getHeadersIfNeeded()).map(header => [header.name, header.value]);
|
return (await this._actualHeaders()).headersArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async headerValue(name: string): Promise<string | null> {
|
||||||
|
return (await this._actualHeaders()).get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async response(): Promise<Response | null> {
|
async response(): Promise<Response | null> {
|
||||||
|
|
@ -213,10 +218,12 @@ export class InterceptedResponse implements api.Response {
|
||||||
private readonly _route: Route;
|
private readonly _route: Route;
|
||||||
private readonly _initializer: channels.InterceptedResponse;
|
private readonly _initializer: channels.InterceptedResponse;
|
||||||
private readonly _request: Request;
|
private readonly _request: Request;
|
||||||
|
private readonly _headers: RawHeaders;
|
||||||
|
|
||||||
constructor(route: Route, initializer: channels.InterceptedResponse) {
|
constructor(route: Route, initializer: channels.InterceptedResponse) {
|
||||||
this._route = route;
|
this._route = route;
|
||||||
this._initializer = initializer;
|
this._initializer = initializer;
|
||||||
|
this._headers = new RawHeaders(initializer.headers);
|
||||||
this._request = Request.from(initializer.request);
|
this._request = Request.from(initializer.request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,15 +263,23 @@ export class InterceptedResponse implements api.Response {
|
||||||
}
|
}
|
||||||
|
|
||||||
headers(): Headers {
|
headers(): Headers {
|
||||||
return headersArrayToObject(this._initializer.headers, true /* lowerCase */);
|
return this._headers.headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
async allHeaders(): Promise<Headers> {
|
async allHeaders(): Promise<Headers> {
|
||||||
return headersArrayToObject(this._initializer.headers, true /* lowerCase */);
|
return this.headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
async headersArray(): Promise<string[][]> {
|
async headersArray(): Promise<HeadersArray> {
|
||||||
return this._initializer.headers.map(header => [header.name, header.value]);
|
return this._headers.headersArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
async headerValue(name: string): Promise<string | null> {
|
||||||
|
return this._headers.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async headerValues(name: string): Promise<string[]> {
|
||||||
|
return this._headers.getAll(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async body(): Promise<Buffer> {
|
async body(): Promise<Buffer> {
|
||||||
|
|
@ -423,10 +438,10 @@ export type RequestSizes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Response extends ChannelOwner<channels.ResponseChannel, channels.ResponseInitializer> implements api.Response {
|
export class Response extends ChannelOwner<channels.ResponseChannel, channels.ResponseInitializer> implements api.Response {
|
||||||
_headers: Headers;
|
private _provisionalHeaders: RawHeaders;
|
||||||
|
private _actualHeadersPromise: Promise<RawHeaders> | undefined;
|
||||||
private _request: Request;
|
private _request: Request;
|
||||||
readonly _finishedPromise = new ManualPromise<void>();
|
readonly _finishedPromise = new ManualPromise<void>();
|
||||||
private _rawHeadersPromise: Promise<channels.ResponseRawResponseHeadersResult> | undefined;
|
|
||||||
|
|
||||||
static from(response: channels.ResponseChannel): Response {
|
static from(response: channels.ResponseChannel): Response {
|
||||||
return (response as any)._object;
|
return (response as any)._object;
|
||||||
|
|
@ -438,7 +453,7 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
|
||||||
|
|
||||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.ResponseInitializer) {
|
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.ResponseInitializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
this._headers = headersArrayToObject(initializer.headers, true /* lowerCase */);
|
this._provisionalHeaders = new RawHeaders(initializer.headers);
|
||||||
this._request = Request.from(this._initializer.request);
|
this._request = Request.from(this._initializer.request);
|
||||||
Object.assign(this._request._timing, this._initializer.timing);
|
Object.assign(this._request._timing, this._initializer.timing);
|
||||||
}
|
}
|
||||||
|
|
@ -463,24 +478,32 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
headers(): Headers {
|
headers(): Headers {
|
||||||
return { ...this._headers };
|
return this._provisionalHeaders.headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getHeadersIfNeeded() {
|
async _actualHeaders(): Promise<RawHeaders> {
|
||||||
if (!this._rawHeadersPromise) {
|
if (!this._actualHeadersPromise) {
|
||||||
this._rawHeadersPromise = this._wrapApiCall(async (channel: channels.ResponseChannel) => {
|
this._actualHeadersPromise = this._wrapApiCall(async (channel: channels.ResponseChannel) => {
|
||||||
return await channel.rawResponseHeaders();
|
return new RawHeaders((await channel.rawResponseHeaders()).headers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this._rawHeadersPromise;
|
return this._actualHeadersPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async allHeaders(): Promise<Headers> {
|
async allHeaders(): Promise<Headers> {
|
||||||
return headersArrayToObject((await this._getHeadersIfNeeded()).headers, true /* lowerCase */);
|
return (await this._actualHeaders()).headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
async headersArray(): Promise<string[][]> {
|
async headersArray(): Promise<HeadersArray> {
|
||||||
return (await this._getHeadersIfNeeded()).headers.map(header => [header.name, header.value]);
|
return (await this._actualHeaders()).headersArray().slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
async headerValue(name: string): Promise<string | null> {
|
||||||
|
return (await this._actualHeaders()).get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
async headerValues(name: string): Promise<string[]> {
|
||||||
|
return (await this._actualHeaders()).getAll(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async finished(): Promise<null> {
|
async finished(): Promise<null> {
|
||||||
|
|
@ -526,13 +549,13 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
|
||||||
|
|
||||||
export class FetchResponse implements api.FetchResponse {
|
export class FetchResponse implements api.FetchResponse {
|
||||||
private readonly _initializer: channels.FetchResponse;
|
private readonly _initializer: channels.FetchResponse;
|
||||||
private readonly _headers: Headers;
|
private readonly _headers: RawHeaders;
|
||||||
private readonly _context: BrowserContext;
|
private readonly _context: BrowserContext;
|
||||||
|
|
||||||
constructor(context: BrowserContext, initializer: channels.FetchResponse) {
|
constructor(context: BrowserContext, initializer: channels.FetchResponse) {
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._initializer = initializer;
|
this._initializer = initializer;
|
||||||
this._headers = headersArrayToObject(this._initializer.headers, true /* lowerCase */);
|
this._headers = new RawHeaders(this._initializer.headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
ok(): boolean {
|
ok(): boolean {
|
||||||
|
|
@ -552,11 +575,11 @@ export class FetchResponse implements api.FetchResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
headers(): Headers {
|
headers(): Headers {
|
||||||
return { ...this._headers };
|
return this._headers.headers();
|
||||||
}
|
}
|
||||||
|
|
||||||
headersArray(): string[][] {
|
headersArray(): HeadersArray {
|
||||||
return this._initializer.headers.map(({name, value}) => [name, value]);
|
return this._headers.headersArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
async body(): Promise<Buffer> {
|
async body(): Promise<Buffer> {
|
||||||
|
|
@ -679,3 +702,36 @@ export class RouteHandler {
|
||||||
this.handledCount++;
|
this.handledCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RawHeaders {
|
||||||
|
private _headersArray: HeadersArray;
|
||||||
|
private _headersMap = new MultiMap<string, string>();
|
||||||
|
|
||||||
|
constructor(headers: HeadersArray) {
|
||||||
|
this._headersArray = headers;
|
||||||
|
for (const header of headers)
|
||||||
|
this._headersMap.set(header.name.toLowerCase(), header.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string): string | null {
|
||||||
|
const values = this.getAll(name);
|
||||||
|
if (!values || !values.length)
|
||||||
|
return null;
|
||||||
|
return values.join(name.toLowerCase() === 'set-cookie' ? '\n' : ', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(name: string): string[] {
|
||||||
|
return [...this._headersMap.get(name.toLowerCase())];
|
||||||
|
}
|
||||||
|
|
||||||
|
headers(): Headers {
|
||||||
|
const result: Headers = {};
|
||||||
|
for (const name of this._headersMap.keys())
|
||||||
|
result[name] = this.get(name)!;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
headersArray(): HeadersArray {
|
||||||
|
return this._headersArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ it('should work', async ({context, server}) => {
|
||||||
expect(response.ok()).toBeTruthy();
|
expect(response.ok()).toBeTruthy();
|
||||||
expect(response.url()).toBe(server.PREFIX + '/simple.json');
|
expect(response.url()).toBe(server.PREFIX + '/simple.json');
|
||||||
expect(response.headers()['content-type']).toBe('application/json; charset=utf-8');
|
expect(response.headers()['content-type']).toBe('application/json; charset=utf-8');
|
||||||
expect(response.headersArray()).toContainEqual(['Content-Type', 'application/json; charset=utf-8']);
|
expect(response.headersArray()).toContainEqual({ name: 'Content-Type', value: 'application/json; charset=utf-8' });
|
||||||
expect(await response.text()).toBe('{"foo": "bar"}\n');
|
expect(await response.text()).toBe('{"foo": "bar"}\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -268,10 +268,10 @@ it('should return raw headers', async ({context, page, server}) => {
|
||||||
});
|
});
|
||||||
const response = await context.fetch(`${server.PREFIX}/headers`);
|
const response = await context.fetch(`${server.PREFIX}/headers`);
|
||||||
expect(response.status()).toBe(200);
|
expect(response.status()).toBe(200);
|
||||||
const headers = response.headersArray().filter(([name, value]) => name.toLowerCase().includes('name-'));
|
const headers = response.headersArray().filter(({ name }) => name.toLowerCase().includes('name-'));
|
||||||
expect(headers).toEqual([['Name-A', 'v1'], ['name-b', 'v4'], ['Name-a', 'v2'], ['name-A', 'v3']]);
|
expect(headers).toEqual([{ name: 'Name-A', value: 'v1' }, { name: 'name-b', value: 'v4' }, { name: 'Name-a', value: 'v2' }, { name: 'name-A', value: 'v3' }]);
|
||||||
// Last value wins, this matches Response.headers()
|
// Comma separated values, this matches Response.headers()
|
||||||
expect(response.headers()['name-a']).toBe('v3');
|
expect(response.headers()['name-a']).toBe('v1, v2, v3');
|
||||||
expect(response.headers()['name-b']).toBe('v4');
|
expect(response.headers()['name-b']).toBe('v4');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -267,13 +267,13 @@ it('should return navigation bit when navigating to image', async ({page, server
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should report raw headers', async ({ page, server, browserName, platform }) => {
|
it('should report raw headers', async ({ page, server, browserName, platform }) => {
|
||||||
let expectedHeaders: string[][];
|
let expectedHeaders: { name: string, value: string }[];
|
||||||
server.setRoute('/headers', (req, res) => {
|
server.setRoute('/headers', (req, res) => {
|
||||||
expectedHeaders = [];
|
expectedHeaders = [];
|
||||||
for (let i = 0; i < req.rawHeaders.length; i += 2)
|
for (let i = 0; i < req.rawHeaders.length; i += 2)
|
||||||
expectedHeaders.push([req.rawHeaders[i], req.rawHeaders[i + 1]]);
|
expectedHeaders.push({ name: req.rawHeaders[i], value: req.rawHeaders[i + 1] });
|
||||||
if (browserName === 'webkit' && platform === 'win32')
|
if (browserName === 'webkit' && platform === 'win32')
|
||||||
expectedHeaders = expectedHeaders.filter(([name, value]) => name.toLowerCase() !== 'accept-encoding' && name.toLowerCase() !== 'accept-language');
|
expectedHeaders = expectedHeaders.filter(({ name }) => name.toLowerCase() !== 'accept-encoding' && name.toLowerCase() !== 'accept-language');
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
|
@ -289,7 +289,9 @@ it('should report raw headers', async ({ page, server, browserName, platform })
|
||||||
}))
|
}))
|
||||||
]);
|
]);
|
||||||
const headers = await request.headersArray();
|
const headers = await request.headersArray();
|
||||||
expect(headers.sort()).toEqual(expectedHeaders.sort());
|
expect(headers.sort((a, b) => a.name.localeCompare(b.name))).toEqual(expectedHeaders.sort((a, b) => a.name.localeCompare(b.name)));
|
||||||
|
expect(await request.headerValue('header-a')).toEqual('value-a, value-a-1, value-a-2');
|
||||||
|
expect(await request.headerValue('not-there')).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should report raw response headers in redirects', async ({ page, server, browserName }) => {
|
it('should report raw response headers in redirects', async ({ page, server, browserName }) => {
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,7 @@ it('should work', async ({page, server}) => {
|
||||||
expect((await response.allHeaders())['BaZ']).toBe(undefined);
|
expect((await response.allHeaders())['BaZ']).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return last header value for duplicates', async ({page, server}) => {
|
it('should return multiple header value', async ({page, server}) => {
|
||||||
it.fixme();
|
|
||||||
server.setRoute('/headers', (req, res) => {
|
server.setRoute('/headers', (req, res) => {
|
||||||
// Headers array is only supported since Node v14.14.0 so we write directly to the socket.
|
// Headers array is only supported since Node v14.14.0 so we write directly to the socket.
|
||||||
// res.writeHead(200, ['name-a', 'v1','name-b', 'v4','Name-a', 'v2', 'name-A', 'v3']);
|
// res.writeHead(200, ['name-a', 'v1','name-b', 'v4','Name-a', 'v2', 'name-A', 'v3']);
|
||||||
|
|
@ -196,7 +195,7 @@ it('should report all headers', async ({ page, server, browserName, platform })
|
||||||
]);
|
]);
|
||||||
const headers = await response.headersArray();
|
const headers = await response.headersArray();
|
||||||
const actualHeaders = {};
|
const actualHeaders = {};
|
||||||
for (const [name, value] of headers) {
|
for (const { name, value } of headers) {
|
||||||
if (!actualHeaders[name])
|
if (!actualHeaders[name])
|
||||||
actualHeaders[name] = [];
|
actualHeaders[name] = [];
|
||||||
actualHeaders[name].push(value);
|
actualHeaders[name].push(value);
|
||||||
|
|
@ -227,6 +226,42 @@ it('should report multiple set-cookie headers', async ({ page, server }) => {
|
||||||
page.evaluate(() => fetch('/headers'))
|
page.evaluate(() => fetch('/headers'))
|
||||||
]);
|
]);
|
||||||
const headers = await response.headersArray();
|
const headers = await response.headersArray();
|
||||||
const cookies = headers.filter(([name, value]) => name.toLowerCase() === 'set-cookie').map(([, value]) => value);
|
const cookies = headers.filter(({ name }) => name.toLowerCase() === 'set-cookie').map(({ value }) => value);
|
||||||
expect(cookies).toEqual(['a=b', 'c=d']);
|
expect(cookies).toEqual(['a=b', 'c=d']);
|
||||||
|
expect(await response.headerValue('not-there')).toEqual(null);
|
||||||
|
expect(await response.headerValue('set-cookie')).toEqual('a=b\nc=d');
|
||||||
|
expect(await response.headerValues('set-cookie')).toEqual(['a=b', 'c=d']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should behave the same way for headers and allHeaders', async ({ page, server, browserName, channel }) => {
|
||||||
|
it.skip(!!channel, 'Stable chrome uses \n as a header separator in non-raw headers');
|
||||||
|
server.setRoute('/headers', (req, res) => {
|
||||||
|
const headers = {
|
||||||
|
'Set-Cookie': ['a=b', 'c=d'],
|
||||||
|
'header-a': ['a=b', 'c=d'],
|
||||||
|
'Name-A': 'v1',
|
||||||
|
'name-b': 'v4',
|
||||||
|
'Name-a': 'v2',
|
||||||
|
'name-A': 'v3',
|
||||||
|
};
|
||||||
|
// Chromium does not report set-cookie headers immediately, so they are missing from .headers()
|
||||||
|
if (browserName === 'chromium')
|
||||||
|
delete headers['Set-Cookie'];
|
||||||
|
|
||||||
|
res.writeHead(200, headers);
|
||||||
|
res.write('\r\n');
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(server.EMPTY_PAGE);
|
||||||
|
const [response] = await Promise.all([
|
||||||
|
page.waitForResponse('**/*'),
|
||||||
|
page.evaluate(() => fetch('/headers'))
|
||||||
|
]);
|
||||||
|
const allHeaders = await response.allHeaders();
|
||||||
|
expect(response.headers()).toEqual(allHeaders);
|
||||||
|
expect(allHeaders['header-a']).toEqual('a=b, c=d');
|
||||||
|
expect(allHeaders['name-a']).toEqual('v1, v2, v3');
|
||||||
|
expect(allHeaders['name-b']).toEqual('v4');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ it('should give access to the intercepted response', async ({page, server}) => {
|
||||||
expect(response.url()).toBe(server.PREFIX + '/title.html');
|
expect(response.url()).toBe(server.PREFIX + '/title.html');
|
||||||
expect(response.headers()['content-type']).toBe('text/html; charset=utf-8');
|
expect(response.headers()['content-type']).toBe('text/html; charset=utf-8');
|
||||||
expect((await response.allHeaders())['content-type']).toBe('text/html; charset=utf-8');
|
expect((await response.allHeaders())['content-type']).toBe('text/html; charset=utf-8');
|
||||||
expect(await (await response.headersArray()).filter(([name, value]) => name.toLowerCase() === 'content-type')).toEqual([['Content-Type', 'text/html; charset=utf-8']]);
|
expect(await (await response.headersArray()).filter(({ name }) => name.toLowerCase() === 'content-type')).toEqual([{ name: 'Content-Type', value: 'text/html; charset=utf-8' }]);
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
await Promise.all([route.fulfill({ response }), evalPromise]);
|
await Promise.all([route.fulfill({ response }), evalPromise]);
|
||||||
|
|
|
||||||
60
types/types.d.ts
vendored
60
types/types.d.ts
vendored
|
|
@ -12688,7 +12688,17 @@ export interface FetchResponse {
|
||||||
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
|
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
|
||||||
* multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
* multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
||||||
*/
|
*/
|
||||||
headersArray(): Array<Array<string>>;
|
headersArray(): Array<{
|
||||||
|
/**
|
||||||
|
* Name of the header.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the header.
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the JSON representation of response body.
|
* Returns the JSON representation of response body.
|
||||||
|
|
@ -13174,10 +13184,26 @@ export interface Request {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array with all the request HTTP headers associated with this request. Unlike
|
* An array with all the request HTTP headers associated with this request. Unlike
|
||||||
* [request.allHeaders()](https://playwright.dev/docs/api/class-request#request-all-headers), header names are not
|
* [request.allHeaders()](https://playwright.dev/docs/api/class-request#request-all-headers), header names are NOT
|
||||||
* lower-cased. Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
* lower-cased. Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
||||||
*/
|
*/
|
||||||
headersArray(): Promise<Array<Array<string>>>;
|
headersArray(): Promise<Array<{
|
||||||
|
/**
|
||||||
|
* Name of the header.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the header.
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the header matching the name. The name is case insensitive.
|
||||||
|
* @param name Name of the header.
|
||||||
|
*/
|
||||||
|
headerValue(name: string): Promise<null|string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this request is driving frame's navigation.
|
* Whether this request is driving frame's navigation.
|
||||||
|
|
@ -13389,10 +13415,34 @@ export interface Response {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array with all the request HTTP headers associated with this response. Unlike
|
* An array with all the request HTTP headers associated with this response. Unlike
|
||||||
* [response.allHeaders()](https://playwright.dev/docs/api/class-response#response-all-headers), header names are not
|
* [response.allHeaders()](https://playwright.dev/docs/api/class-response#response-all-headers), header names are NOT
|
||||||
* lower-cased. Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
* lower-cased. Headers with multiple entries, such as `Set-Cookie`, appear in the array multiple times.
|
||||||
*/
|
*/
|
||||||
headersArray(): Promise<Array<Array<string>>>;
|
headersArray(): Promise<Array<{
|
||||||
|
/**
|
||||||
|
* Name of the header.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the header.
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
}>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the header matching the name. The name is case insensitive. If multiple headers have the same name
|
||||||
|
* (except `set-cookie`), they are returned as a list separated by `, `. For `set-cookie`, the `\n` separator is used. If
|
||||||
|
* no headers are found, `null` is returned.
|
||||||
|
* @param name Name of the header.
|
||||||
|
*/
|
||||||
|
headerValue(name: string): Promise<null|string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all values of the headers matching the name, for example `set-cookie`. The name is case insensitive.
|
||||||
|
* @param name Name of the header.
|
||||||
|
*/
|
||||||
|
headerValues(name: string): Promise<Array<string>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the JSON representation of response body.
|
* Returns the JSON representation of response body.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue