diff --git a/docs/src/api/class-response.md b/docs/src/api/class-response.md index cddaf696592ce..8fcef0883d764 100644 --- a/docs/src/api/class-response.md +++ b/docs/src/api/class-response.md @@ -80,6 +80,12 @@ Returns all values of the headers matching the name, for example `set-cookie`. T Name of the header. +## async method: Response.httpVersion +* since: v1.59 +- returns: <[string]> + +Returns the http version used by the response. + ## async method: Response.json * since: v1.8 * langs: js, python diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 604208ad14b25..fe0d2ff377920 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -21155,6 +21155,11 @@ export interface Response { */ headerValues(name: string): Promise>; + /** + * Returns the http version used by the response. + */ + httpVersion(): Promise; + /** * Returns the JSON representation of response body. * diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index e43e3d4ed75be..843c1fa6a7055 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -741,6 +741,10 @@ export class Response extends ChannelOwner implements async securityDetails(): Promise { return (await this._channel.securityDetails()).value || null; } + + async httpVersion(): Promise { + return (await this._channel.httpVersion()).value; + } } export class WebSocket extends ChannelOwner implements api.WebSocket { diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 06622ef91f8a3..8a59f8ab5932c 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -2416,6 +2416,10 @@ scheme.ResponseRawResponseHeadersParams = tOptional(tObject({})); scheme.ResponseRawResponseHeadersResult = tObject({ headers: tArray(tType('NameValue')), }); +scheme.ResponseHttpVersionParams = tOptional(tObject({})); +scheme.ResponseHttpVersionResult = tObject({ + value: tString, +}); scheme.ResponseSizesParams = tOptional(tObject({})); scheme.ResponseSizesResult = tObject({ sizes: tType('RequestSizes'), diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index f6d33847091c9..f64571fac1105 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -426,7 +426,8 @@ export class CRNetworkManager { responseStart: -1, }; } - const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), timing, getResponseBody, !!responsePayload.fromServiceWorker, responsePayload.protocol); + const response = new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers), timing, getResponseBody, !!responsePayload.fromServiceWorker); + response._setHttpVersion(responsePayload?.protocol ?? null); if (responsePayload?.remoteIPAddress && typeof responsePayload?.remotePort === 'number') { response._serverAddrFinished({ ipAddress: responsePayload.remoteIPAddress, diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index 41b8618109695..df73f81f78af2 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -117,6 +117,10 @@ export class ResponseDispatcher extends Dispatcher { + return { value: await progress.race(this._object.httpVersion()) }; + } + async sizes(params: channels.ResponseSizesParams, progress: Progress): Promise { return { sizes: await progress.race(this._object.sizes()) }; } diff --git a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts index bebe6670dd223..de33acb6a2a80 100644 --- a/packages/playwright-core/src/server/firefox/ffNetworkManager.ts +++ b/packages/playwright-core/src/server/firefox/ffNetworkManager.ts @@ -144,8 +144,7 @@ export class FFNetworkManager { this._requests.delete(request._id); response._requestFinished(responseEndTime); } - if (event.protocolVersion) - response._setHttpVersion(event.protocolVersion); + response._setHttpVersion(event.protocolVersion ?? null); this._page.frameManager.reportRequestFinished(request.request, response); } @@ -159,6 +158,7 @@ export class FFNetworkManager { response.setTransferSize(null); response.setEncodedBodySize(null); response._requestFinished(-1); + response._setHttpVersion(null); } request.request._setFailureText(event.errorCode); this._page.frameManager.requestFailed(request.request, event.errorCode === 'NS_BINDING_ABORTED'); diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 601f1821e554d..dc6399abf7fe3 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -323,10 +323,6 @@ export class HarTracer { })); } - const httpVersion = response.httpVersion(); - harEntry.request.httpVersion = httpVersion; - harEntry.response.httpVersion = httpVersion; - const compressionCalculationBarrier = this._options.omitSizes ? undefined : { _encodedBodySize: -1, _decodedBodySize: -1, @@ -452,7 +448,7 @@ export class HarTracer { harEntry.response = { status: response.status(), statusText: response.statusText(), - httpVersion: response.httpVersion(), + httpVersion: FALLBACK_HTTP_VERSION, // These are bad values that will be overwritten below. cookies: [], headers: [], @@ -466,6 +462,11 @@ export class HarTracer { _transferSize: this._options.omitSizes ? undefined : -1 }; + this._addBarrier(page || request.serviceWorker(), response.httpVersion().then(httpVersion => { + harEntry.request.httpVersion = httpVersion; + harEntry.response.httpVersion = httpVersion; + })); + if (!this._options.omitTiming) { const startDateTime = pageEntry ? ((pageEntry as any)[startedDateSymbol] as Date).valueOf() : 0; const timing = response.timing(); diff --git a/packages/playwright-core/src/server/network.ts b/packages/playwright-core/src/server/network.ts index 5a45644d3f3cd..b39d668d14a15 100644 --- a/packages/playwright-core/src/server/network.ts +++ b/packages/playwright-core/src/server/network.ts @@ -508,13 +508,13 @@ export class Response extends SdkObject { private _serverAddrPromise = new ManualPromise(); private _securityDetailsPromise = new ManualPromise(); private _rawResponseHeadersPromise = new ManualPromise(); - private _httpVersion: string | undefined; + private _httpVersionPromise = new ManualPromise(); private _fromServiceWorker: boolean; private _encodedBodySizePromise = new ManualPromise(); private _transferSizePromise = new ManualPromise(); private _responseHeadersSizePromise = new ManualPromise(); - constructor(request: Request, status: number, statusText: string, headers: HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback, fromServiceWorker: boolean, httpVersion?: string) { + constructor(request: Request, status: number, statusText: string, headers: HeadersArray, timing: ResourceTiming, getResponseBodyCallback: GetResponseBodyCallback, fromServiceWorker: boolean) { super(request.frame() || request._context, 'response'); this._request = request; this._timing = timing; @@ -526,7 +526,6 @@ export class Response extends SdkObject { this._headersMap.set(name.toLowerCase(), value); this._getResponseBodyCallback = getResponseBodyCallback; this._request._setResponse(this); - this._httpVersion = httpVersion; this._fromServiceWorker = fromServiceWorker; } @@ -546,8 +545,8 @@ export class Response extends SdkObject { this._finishedPromise.resolve(); } - _setHttpVersion(httpVersion: string) { - this._httpVersion = httpVersion; + _setHttpVersion(httpVersion: string | null) { + this._httpVersionPromise.resolve(httpVersion); } url(): string { @@ -631,14 +630,15 @@ export class Response extends SdkObject { return this._request.frame(); } - httpVersion(): string { - if (!this._httpVersion) + async httpVersion(): Promise { + const httpVersion = await this._httpVersionPromise || null; + if (!httpVersion) return 'HTTP/1.1'; - if (this._httpVersion === 'http/1.1') + if (httpVersion === 'http/1.1') return 'HTTP/1.1'; - if (this._httpVersion === 'h2') + if (httpVersion === 'h2') return 'HTTP/2.0'; - return this._httpVersion; + return httpVersion; } fromServiceWorker(): boolean { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 2e67685dc5b2d..4751ae522babe 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -1161,8 +1161,7 @@ export class WKPage implements PageDelegate { validFrom: responseReceivedPayload?.response.security?.certificate?.validFrom, validTo: responseReceivedPayload?.response.security?.certificate?.validUntil, }); - if (event.metrics?.protocol) - response._setHttpVersion(event.metrics.protocol); + response._setHttpVersion(event.metrics?.protocol ?? null); response.setEncodedBodySize(event.metrics?.responseBodyBytesReceived ?? null); response.setResponseHeadersSize(event.metrics?.responseHeaderBytesReceived ?? null); @@ -1196,6 +1195,7 @@ export class WKPage implements PageDelegate { if (response) { response._serverAddrFinished(); response._securityDetailsFinished(); + response._setHttpVersion(null); response.setResponseHeadersSize(null); response.setEncodedBodySize(null); response._requestFinished(helper.secondsToRoundishMillis(event.timestamp - request._timestamp)); diff --git a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts index f21f37956abc6..63a7027df02f9 100644 --- a/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts +++ b/packages/playwright-core/src/utils/isomorphic/protocolMetainfo.ts @@ -263,6 +263,7 @@ export const methodMetainfo = new Map>; + /** + * Returns the http version used by the response. + */ + httpVersion(): Promise; + /** * Returns the JSON representation of response body. * diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index a7c8251861ada..77f9ca5648551 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -4136,6 +4136,7 @@ export interface ResponseChannel extends ResponseEventTarget, Channel { securityDetails(params?: ResponseSecurityDetailsParams, progress?: Progress): Promise; serverAddr(params?: ResponseServerAddrParams, progress?: Progress): Promise; rawResponseHeaders(params?: ResponseRawResponseHeadersParams, progress?: Progress): Promise; + httpVersion(params?: ResponseHttpVersionParams, progress?: Progress): Promise; sizes(params?: ResponseSizesParams, progress?: Progress): Promise; } export type ResponseBodyParams = {}; @@ -4158,6 +4159,11 @@ export type ResponseRawResponseHeadersOptions = {}; export type ResponseRawResponseHeadersResult = { headers: NameValue[], }; +export type ResponseHttpVersionParams = {}; +export type ResponseHttpVersionOptions = {}; +export type ResponseHttpVersionResult = { + value: string, +}; export type ResponseSizesParams = {}; export type ResponseSizesOptions = {}; export type ResponseSizesResult = { diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index defad2e499f64..347a5a4d0291a 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -3656,6 +3656,11 @@ Response: type: array items: NameValue + httpVersion: + internal: true + returns: + value: string + sizes: internal: true returns: diff --git a/tests/page/page-network-response.spec.ts b/tests/page/page-network-response.spec.ts index daeeed6281945..849d7c22cbe2f 100644 --- a/tests/page/page-network-response.spec.ts +++ b/tests/page/page-network-response.spec.ts @@ -421,3 +421,8 @@ it('request.existingResponse should return the response after it is received', a const request = response.request(); expect(request.existingResponse()).toBe(response); }); + +it('should return http version', async ({ page, server }) => { + const response = await page.goto(server.EMPTY_PAGE); + expect(await response.httpVersion()).toBe('HTTP/1.1'); +});