diff --git a/docs/src/api/class-apiresponse.md b/docs/src/api/class-apiresponse.md index 5a901b76ba..5f57a3b391 100644 --- a/docs/src/api/class-apiresponse.md +++ b/docs/src/api/class-apiresponse.md @@ -110,3 +110,26 @@ Returns the text representation of response body. - returns: <[string]> Contains the URL of the response. + +## method: APIResponse.timing +* since: v1.48 +- returns: <[Object]> + - `startTime` <[float]> Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC + - `domainLookupStart` <[float]> Time immediately before the client starts the domain name lookup for the + resource. The value is given in milliseconds relative to `startTime`, -1 if not available. + - `domainLookupEnd` <[float]> Time immediately after the client ends the domain name lookup for the resource. + The value is given in milliseconds relative to `startTime`, -1 if not available. + - `connectStart` <[float]> Time immediately before the client starts establishing the connection to the server + to retrieve the resource. The value is given in milliseconds relative to `startTime`, -1 if not available. + - `secureConnectionStart` <[float]> Time immediately before the client starts the handshake process to secure the + current connection. The value is given in milliseconds relative to `startTime`, -1 if not available. + - `connectEnd` <[float]> Time immediately before the client starts establishing the connection to the server + to retrieve the resource. The value is given in milliseconds relative to `startTime`, -1 if not available. + - `requestStart` <[float]> Time immediately before the client starts requesting the resource from the server. The value is given in milliseconds relative to `startTime`, -1 if not available. + - `responseStart` <[float]> Time immediately after the client receives the first byte of the response from the server. The value is given in milliseconds relative to `startTime`, -1 if not available. + - `responseEnd` <[float]> Time immediately after the client receives the last byte of the resource or immediately + before the transport connection is closed, whichever comes first. The value is given in milliseconds relative to + `startTime`, -1 if not available. + +Returns resource timing information for given response. Find more information at +[Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming). diff --git a/docs/src/api/class-request.md b/docs/src/api/class-request.md index e13d9de69f..a9f2d307e5 100644 --- a/docs/src/api/class-request.md +++ b/docs/src/api/class-request.md @@ -314,7 +314,7 @@ Returns resource size information for given request. - `startTime` <[float]> Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC - `domainLookupStart` <[float]> Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds relative to `startTime`, -1 if not available. - - `domainLookupEnd` <[float]> Time immediately after the browser starts the domain name lookup for the resource. + - `domainLookupEnd` <[float]> Time immediately after the browser ends the domain name lookup for the resource. The value is given in milliseconds relative to `startTime`, -1 if not available. - `connectStart` <[float]> Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The value is given in milliseconds relative to `startTime`, -1 if not available. diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 58928532ac..a86a87f362 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -332,6 +332,13 @@ export class APIResponse implements api.APIResponse { return this._headers.headersArray(); } + timing() { + return { + ...this._initializer.timing, + responseEnd: this._initializer.responseEndTiming, + }; + } + async body(): Promise { try { const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() }); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index abea7f8fce..369568a808 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -224,6 +224,8 @@ scheme.APIResponse = tObject({ status: tNumber, statusText: tString, headers: tArray(tType('NameValue')), + timing: tType('ResourceTiming'), + responseEndTiming: tNumber, }); scheme.LifecycleEvent = tEnum(['load', 'domcontentloaded', 'networkidle', 'commit']); scheme.LocalUtilsInitializer = tObject({ diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index ba600f697e..e387ff1cbd 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -212,7 +212,9 @@ export class APIRequestContextDispatcher extends Dispatcher { + const endAt = monotonicTime(); + // spec: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming + const timing: channels.ResourceTiming = { + startTime: startAt, + domainLookupStart: dnsLookupAt ? 0 : -1, + domainLookupEnd: dnsLookupAt ? dnsLookupAt! - startAt : -1, + connectStart: dnsLookupAt ? dnsLookupAt! - startAt : 0, + secureConnectionStart: dnsLookupAt ? dnsLookupAt! - startAt : 0, + connectEnd: (tlsHandshakeAt ?? tcpConnectionAt!) - startAt, + requestStart: (tlsHandshakeAt ?? tcpConnectionAt!) - startAt, + responseStart: responseAt - startAt, + }; + const responseEndTiming = endAt - startAt; + const body = Buffer.concat(chunks); notifyRequestFinished(body); fulfill({ @@ -429,7 +443,9 @@ export abstract class APIRequestContext extends SdkObject { status: response.statusCode || 0, statusText: response.statusMessage || '', headers: toHeadersArray(response.rawHeaders), - body + body, + timing, + responseEndTiming, }); }; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index ad102f9271..008b0d426d 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -17366,6 +17366,66 @@ export interface APIResponse { */ text(): Promise; + /** + * Returns resource timing information for given response. Find more information at + * [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming). + */ + timing(): { + /** + * Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC + */ + startTime: number; + + /** + * Time immediately before the client starts the domain name lookup for the resource. The value is given in + * milliseconds relative to `startTime`, -1 if not available. + */ + domainLookupStart: number; + + /** + * Time immediately after the client ends the domain name lookup for the resource. The value is given in milliseconds + * relative to `startTime`, -1 if not available. + */ + domainLookupEnd: number; + + /** + * Time immediately before the client starts establishing the connection to the server to retrieve the resource. The + * value is given in milliseconds relative to `startTime`, -1 if not available. + */ + connectStart: number; + + /** + * Time immediately before the client starts the handshake process to secure the current connection. The value is + * given in milliseconds relative to `startTime`, -1 if not available. + */ + secureConnectionStart: number; + + /** + * Time immediately before the client starts establishing the connection to the server to retrieve the resource. The + * value is given in milliseconds relative to `startTime`, -1 if not available. + */ + connectEnd: number; + + /** + * Time immediately before the client starts requesting the resource from the server. The value is given in + * milliseconds relative to `startTime`, -1 if not available. + */ + requestStart: number; + + /** + * Time immediately after the client receives the first byte of the response from the server. The value is given in + * milliseconds relative to `startTime`, -1 if not available. + */ + responseStart: number; + + /** + * Time immediately after the client receives the last byte of the resource or immediately before the transport + * connection is closed, whichever comes first. The value is given in milliseconds relative to `startTime`, -1 if not + * available. + */ + responseEnd: number; + }; + /** * Contains the URL of the response. */ @@ -19307,8 +19367,8 @@ export interface Request { domainLookupStart: number; /** - * Time immediately after the browser starts the domain name lookup for the resource. The value is given in - * milliseconds relative to `startTime`, -1 if not available. + * Time immediately after the browser ends the domain name lookup for the resource. The value is given in milliseconds + * relative to `startTime`, -1 if not available. */ domainLookupEnd: number; diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 689f0275b1..6a7ec6e0cf 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -398,6 +398,8 @@ export type APIResponse = { status: number, statusText: string, headers: NameValue[], + timing: ResourceTiming, + responseEndTiming: number, }; export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index ce206ab569..1a6d4801ca 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -353,6 +353,8 @@ APIResponse: headers: type: array items: NameValue + timing: ResourceTiming + responseEndTiming: number LifecycleEvent: diff --git a/tests/library/browsercontext-fetch.spec.ts b/tests/library/browsercontext-fetch.spec.ts index d10b182cf0..eb24bd939f 100644 --- a/tests/library/browsercontext-fetch.spec.ts +++ b/tests/library/browsercontext-fetch.spec.ts @@ -52,6 +52,17 @@ it('fetch should work', async ({ context, server }) => { expect(response.ok()).toBeTruthy(); expect(response.headers()['content-type']).toBe('application/json; charset=utf-8'); expect(response.headersArray()).toContainEqual({ name: 'Content-Type', value: 'application/json; charset=utf-8' }); + expect(response.timing()).toEqual({ + connectEnd: expect.any(Number), + connectStart: expect.any(Number), + domainLookupEnd: expect.any(Number), + domainLookupStart: expect.any(Number), + requestStart: expect.any(Number), + responseStart: expect.any(Number), + responseEnd: expect.any(Number), + secureConnectionStart: expect.any(Number), + startTime: expect.any(Number), + }); expect(await response.text()).toBe('{"foo": "bar"}\n'); });