From c1dcd857e3bc17769f02474a41bbedee01f471dc Mon Sep 17 00:00:00 2001 From: Junyan Date: Tue, 5 Apr 2022 17:06:35 +0800 Subject: [PATCH] feat: respond multiple headers with same key (#8183) --- docs/api.md | 2 +- src/common/HTTPRequest.ts | 24 ++++++++++++++++-------- test/requestinterception.spec.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/docs/api.md b/docs/api.md index cf5a028ecdc70..727a26f3d8244 100644 --- a/docs/api.md +++ b/docs/api.md @@ -5122,7 +5122,7 @@ ResourceType will be one of the following: `document`, `stylesheet`, `image`, `m - `response` <[Object]> Response that will fulfill this request - `status` <[number]> Response status code, defaults to `200`. - - `headers` <[Object]> Optional response headers. Header values will be converted to a string. + - `headers` <[Object]> Optional response headers. Header values should be a string or a string array. - `contentType` <[string]> If set, equals to setting `Content-Type` response header - `body` <[string]|[Buffer]> Optional response body - `priority` <[number]> - Optional intercept abort priority. If provided, intercept will be resolved using coopeative handling rules. Otherwise, intercept will be resolved immediately. diff --git a/src/common/HTTPRequest.ts b/src/common/HTTPRequest.ts index 69ee61b0ce528..496230f000130 100644 --- a/src/common/HTTPRequest.ts +++ b/src/common/HTTPRequest.ts @@ -555,12 +555,15 @@ export class HTTPRequest { ? Buffer.from(response.body) : (response.body as Buffer) || null; - const responseHeaders: Record = {}; + const responseHeaders: Record = {}; if (response.headers) { - for (const header of Object.keys(response.headers)) - responseHeaders[header.toLowerCase()] = String( - response.headers[header] - ); + for (const header of Object.keys(response.headers)) { + const value = response.headers[header]; + + responseHeaders[header.toLowerCase()] = Array.isArray(value) + ? value.map((item) => String(item)) + : String(value); + } } if (response.contentType) responseHeaders['content-type'] = response.contentType; @@ -696,12 +699,17 @@ const errorReasons: Record = { export type ActionResult = 'continue' | 'abort' | 'respond'; function headersArray( - headers: Record + headers: Record ): Array<{ name: string; value: string }> { const result = []; for (const name in headers) { - if (!Object.is(headers[name], undefined)) - result.push({ name, value: headers[name] + '' }); + const value = headers[name]; + + if (!Object.is(value, undefined)) { + const values = Array.isArray(value) ? value : [value]; + + result.push(...values.map((value) => ({ name, value: value + '' }))); + } } return result; } diff --git a/test/requestinterception.spec.ts b/test/requestinterception.spec.ts index 8cf9c7b00f275..c0bd5d2010ab2 100644 --- a/test/requestinterception.spec.ts +++ b/test/requestinterception.spec.ts @@ -710,6 +710,33 @@ describe('request interception', function () { ); expect(response.url()).toBe(server.EMPTY_PAGE); }); + it('should allow mocking multiple headers with same key', async () => { + const { page, server } = getTestState(); + + await page.setRequestInterception(true); + page.on('request', (request) => { + request.respond({ + status: 200, + headers: { + foo: 'bar', + arr: ['1', '2'], + 'set-cookie': ['first=1', 'second=2'], + }, + body: 'Hello world', + }); + }); + const response = await page.goto(server.EMPTY_PAGE); + const cookies = await page.cookies(); + const firstCookie = cookies.find((cookie) => cookie.name === 'first'); + const secondCookie = cookies.find((cookie) => cookie.name === 'second'); + expect(response.status()).toBe(200); + expect(response.headers().foo).toBe('bar'); + expect(response.headers().arr).toBe('1\n2'); + // request.respond() will not trigger Network.responseReceivedExtraInfo + // fail to get 'set-cookie' header from response + expect(firstCookie?.value).toBe('1'); + expect(secondCookie?.value).toBe('2'); + }); it('should allow mocking binary responses', async () => { const { page, server } = getTestState();