chore: fix headers api again (#8854)

This commit is contained in:
Pavel Feldman 2021-09-11 13:27:00 -07:00 committed by GitHub
parent 737b155869
commit 798d0bfa9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 232 additions and 81 deletions

View file

@ -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.

View file

@ -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]>

View file

@ -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]>

View file

@ -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;
}
}

View file

@ -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');
}); });

View file

@ -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 }) => {

View file

@ -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');
});

View file

@ -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
View file

@ -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.