Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/src/api/class-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21155,6 +21155,11 @@ export interface Response {
*/
headerValues(name: string): Promise<Array<string>>;

/**
* Returns the http version used by the response.
*/
httpVersion(): Promise<string>;

/**
* Returns the JSON representation of response body.
*
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/client/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,10 @@ export class Response extends ChannelOwner<channels.ResponseChannel> implements
async securityDetails(): Promise<SecurityDetails|null> {
return (await this._channel.securityDetails()).value || null;
}

async httpVersion(): Promise<string> {
return (await this._channel.httpVersion()).value;
}
}

export class WebSocket extends ChannelOwner<channels.WebSocketChannel> implements api.WebSocket {
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export class ResponseDispatcher extends Dispatcher<Response, channels.ResponseCh
return { headers: await progress.race(this._object.rawResponseHeaders()) };
}

async httpVersion(params: channels.ResponseHttpVersionParams, progress: Progress): Promise<channels.ResponseHttpVersionResult> {
return { value: await progress.race(this._object.httpVersion()) };
}

async sizes(params: channels.ResponseSizesParams, progress: Progress): Promise<channels.ResponseSizesResult> {
return { sizes: await progress.race(this._object.sizes()) };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call this from _onRequestFailed as well.

this._page.frameManager.reportRequestFinished(request.request, response);
}

Expand All @@ -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');
Expand Down
11 changes: 6 additions & 5 deletions packages/playwright-core/src/server/har/harTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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: [],
Expand All @@ -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();
Expand Down
20 changes: 10 additions & 10 deletions packages/playwright-core/src/server/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,13 +508,13 @@ export class Response extends SdkObject {
private _serverAddrPromise = new ManualPromise<RemoteAddr | undefined>();
private _securityDetailsPromise = new ManualPromise<SecurityDetails | undefined>();
private _rawResponseHeadersPromise = new ManualPromise<HeadersArray>();
private _httpVersion: string | undefined;
private _httpVersionPromise = new ManualPromise<string | null>();
private _fromServiceWorker: boolean;
private _encodedBodySizePromise = new ManualPromise<number | null>();
private _transferSizePromise = new ManualPromise<number | null>();
private _responseHeadersSizePromise = new ManualPromise<number | null>();

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

Expand All @@ -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 {
Expand Down Expand Up @@ -631,14 +630,15 @@ export class Response extends SdkObject {
return this._request.frame();
}

httpVersion(): string {
if (!this._httpVersion)
async httpVersion(): Promise<string> {
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 {
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call this from _onLoadingFailed as well, similar to calling _securityDetailsFinished().

response.setEncodedBodySize(event.metrics?.responseBodyBytesReceived ?? null);
response.setResponseHeadersSize(event.metrics?.responseHeaderBytesReceived ?? null);

Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export const methodMetainfo = new Map<string, { internal?: boolean, title?: stri
['Response.securityDetails', { internal: true, }],
['Response.serverAddr', { internal: true, }],
['Response.rawResponseHeaders', { internal: true, }],
['Response.httpVersion', { internal: true, }],
['Response.sizes', { internal: true, }],
['BindingCall.reject', { internal: true, }],
['BindingCall.resolve', { internal: true, }],
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21155,6 +21155,11 @@ export interface Response {
*/
headerValues(name: string): Promise<Array<string>>;

/**
* Returns the http version used by the response.
*/
httpVersion(): Promise<string>;

/**
* Returns the JSON representation of response body.
*
Expand Down
6 changes: 6 additions & 0 deletions packages/protocol/src/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4136,6 +4136,7 @@ export interface ResponseChannel extends ResponseEventTarget, Channel {
securityDetails(params?: ResponseSecurityDetailsParams, progress?: Progress): Promise<ResponseSecurityDetailsResult>;
serverAddr(params?: ResponseServerAddrParams, progress?: Progress): Promise<ResponseServerAddrResult>;
rawResponseHeaders(params?: ResponseRawResponseHeadersParams, progress?: Progress): Promise<ResponseRawResponseHeadersResult>;
httpVersion(params?: ResponseHttpVersionParams, progress?: Progress): Promise<ResponseHttpVersionResult>;
sizes(params?: ResponseSizesParams, progress?: Progress): Promise<ResponseSizesResult>;
}
export type ResponseBodyParams = {};
Expand All @@ -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 = {
Expand Down
5 changes: 5 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3656,6 +3656,11 @@ Response:
type: array
items: NameValue

httpVersion:
internal: true
returns:
value: string

sizes:
internal: true
returns:
Expand Down
5 changes: 5 additions & 0 deletions tests/page/page-network-response.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test for a failing request - httpVersion() should not hang in this case. Consult Page.Events.RequestFailed from page-event-network.spec.ts to see how to trigger a failing request.

Copy link
Contributor Author

@smckee-r7 smckee-r7 Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on that test you mentioned, it shows that the Response object should be null if the request fails. This PR only exposes the httpVersion method on the response. Not sure how I'd test that exactly since the Response should be null.

I did briefly consider adding a httpVersion method to the Request but it didn't feel like it made sense. Since as far as I can tell, it's not available until a response payload is received anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, you are right!

const response = await page.goto(server.EMPTY_PAGE);
expect(await response.httpVersion()).toBe('HTTP/1.1');
});
Loading