From a7ea94634144b070d21ca06853a54eed250d6d74 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 17 Sep 2024 10:08:21 +0200 Subject: [PATCH] feat(fetch): expose .timing() --- packages/playwright-core/src/client/fetch.ts | 4 ++++ packages/playwright-core/src/client/network.ts | 16 ++-------------- .../playwright-core/src/protocol/validator.ts | 2 ++ .../server/dispatchers/networkDispatchers.ts | 3 ++- packages/playwright-core/src/server/fetch.ts | 17 ++++++++++++++++- packages/protocol/src/channels.ts | 2 ++ packages/protocol/src/protocol.yml | 2 ++ tests/library/browsercontext-fetch.spec.ts | 11 +++++++++++ 8 files changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index 58928532ac9e4..b87f5f07c5f7c 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -332,6 +332,10 @@ export class APIResponse implements api.APIResponse { return this._headers.headersArray(); } + timing() { + return this._initializer.timing; + } + async body(): Promise { try { const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() }); diff --git a/packages/playwright-core/src/client/network.ts b/packages/playwright-core/src/client/network.ts index 164af0f7e2e2f..bc96209287b30 100644 --- a/packages/playwright-core/src/client/network.ts +++ b/packages/playwright-core/src/client/network.ts @@ -84,7 +84,7 @@ export class Request extends ChannelOwner implements ap _failureText: string | null = null; private _provisionalHeaders: RawHeaders; private _actualHeadersPromise: Promise | undefined; - _timing: ResourceTiming; + _timing: channels.ResourceTiming; private _fallbackOverrides: SerializedFallbackOverrides = {}; static from(request: channels.RequestChannel): Request { @@ -242,7 +242,7 @@ export class Request extends ChannelOwner implements ap }; } - timing(): ResourceTiming { + timing(): channels.ResourceTiming { return this._timing; } @@ -453,18 +453,6 @@ export class Route extends ChannelOwner implements api.Ro export type RouteHandlerCallback = (route: Route, request: Request) => Promise | void; -export type ResourceTiming = { - startTime: number; - domainLookupStart: number; - domainLookupEnd: number; - connectStart: number; - secureConnectionStart: number; - connectEnd: number; - requestStart: number; - responseStart: number; - responseEnd: number; -}; - export type RequestSizes = { requestBodySize: number; requestHeadersSize: number; diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index b67edcbca89db..2795e03043f08 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -224,6 +224,7 @@ scheme.APIResponse = tObject({ status: tNumber, statusText: tString, headers: tArray(tType('NameValue')), + timing: tType('ResourceTiming'), }); scheme.LifecycleEvent = tEnum(['load', 'domcontentloaded', 'networkidle', 'commit']); scheme.LocalUtilsInitializer = tObject({ @@ -2121,6 +2122,7 @@ scheme.ResourceTiming = tObject({ connectEnd: tNumber, requestStart: tNumber, responseStart: tNumber, + responseEnd: tNumber, }); scheme.ResponseInitializer = tObject({ request: tChannel(['Request']), diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index ba600f697e7d7..ef280e61c66dd 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -212,7 +212,8 @@ 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, + responseEnd: endAt - startAt, + }; + const body = Buffer.concat(chunks); notifyRequestFinished(body); fulfill({ @@ -442,7 +456,8 @@ export abstract class APIRequestContext extends SdkObject { status: response.statusCode || 0, statusText: response.statusMessage || '', headers: toHeadersArray(response.rawHeaders), - body + body, + timing, }); }; diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index 143f1ad0e023c..e9a350c1bea72 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -398,6 +398,7 @@ export type APIResponse = { status: number, statusText: string, headers: NameValue[], + timing: ResourceTiming, }; export type LifecycleEvent = 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; @@ -3778,6 +3779,7 @@ export type ResourceTiming = { connectEnd: number, requestStart: number, responseStart: number, + responseEnd: number, }; // ----------- Response ----------- diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 4f064ffa08a37..9d2633ad0b91a 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -353,6 +353,7 @@ APIResponse: headers: type: array items: NameValue + timing: ResourceTiming LifecycleEvent: @@ -2945,6 +2946,7 @@ ResourceTiming: connectEnd: number requestStart: number responseStart: number + responseEnd: number Response: type: interface diff --git a/tests/library/browsercontext-fetch.spec.ts b/tests/library/browsercontext-fetch.spec.ts index d10b182cf036b..eb24bd939ff31 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'); });