diff --git a/examples/github-api/tests/demo.spec.ts b/examples/github-api/tests/demo.spec.ts new file mode 100644 index 0000000000..3449819fab --- /dev/null +++ b/examples/github-api/tests/demo.spec.ts @@ -0,0 +1,7 @@ +import { test, expect } from "@playwright/test"; + +test('should get posts', async ({ request }) => { + const posts = await request.get(`https://jsonplaceholder.typicode.com/posts/1`); + expect(posts.ok()).toBeTruthy(); + expect(await posts.text()).toHaveLength(10) +}); \ No newline at end of file diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 7a6102a50d..cfec0b8b09 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -41,6 +41,7 @@ import type * as types from './types'; import type { HeadersArray, ProxySettings } from './types'; import { kMaxCookieExpiresDateInSeconds } from './network'; import { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor'; +import type * as har from '@trace/har'; type FetchRequestOptions = { userAgent: string; @@ -72,6 +73,7 @@ export type APIRequestFinishedEvent = { statusCode: number; statusMessage: string; body?: Buffer; + timings: har.Timings; }; type SendRequestOptions = https.RequestOptions & { @@ -307,8 +309,30 @@ export abstract class APIRequestContext extends SdkObject { // If we have a proxy agent already, do not override it. const agent = options.agent || (url.protocol === 'https:' ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent); const requestOptions = { ...options, agent }; + + const startAt = monotonicTime(); + const timings: Record<'startAt' | 'requestFinishAt' | 'dnsLookupAt' | 'tcpConnectionAt' | 'tlsHandshakeAt' | 'firstByteAt' | 'endAt', number | undefined> = { + startAt, + requestFinishAt: undefined, + dnsLookupAt: undefined, + tcpConnectionAt: undefined, + tlsHandshakeAt: undefined, + firstByteAt: undefined, + endAt: undefined + }; + const request = requestConstructor(url, requestOptions as any, async response => { + response.once('readable', () => { timings.firstByteAt = monotonicTime(); }); + response.once('end', () => { timings.endAt = monotonicTime(); }); + const notifyRequestFinished = (body?: Buffer) => { + const send = timings.requestFinishAt! - startAt; + const dnsLookup = timings.dnsLookupAt ? startAt - timings.dnsLookupAt : undefined; + const tcpConnection = timings.tcpConnectionAt! - (timings.dnsLookupAt ?? startAt); + const tlsHandshake = timings.tlsHandshakeAt ? (timings.tlsHandshakeAt - timings.tcpConnectionAt!) : undefined; + const firstByte = timings.firstByteAt! - (timings.tlsHandshakeAt ?? timings.tcpConnectionAt!); + const contentTransfer = timings.endAt! - timings.firstByteAt!; + const requestFinishedEvent: APIRequestFinishedEvent = { requestEvent, httpVersion: response.httpVersion, @@ -317,7 +341,16 @@ export abstract class APIRequestContext extends SdkObject { headers: response.headers, rawHeaders: response.rawHeaders, cookies, - body + body, + timings: { + send, + wait: firstByte, + receive: contentTransfer, + dns: dnsLookup, + connect: tcpConnection, + ssl: tlsHandshake, + blocked: firstByte, + }, }; this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent); }; @@ -463,6 +496,13 @@ export abstract class APIRequestContext extends SdkObject { this.on(APIRequestContext.Events.Dispose, disposeListener); request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener)); + request.on('socket', socket => { + socket.on('lookup', () => { timings.dnsLookupAt = monotonicTime(); }); + socket.on('connect', () => { timings.tcpConnectionAt = monotonicTime(); }); + socket.on('secureConnect', () => { timings.tlsHandshakeAt = monotonicTime(); }); + }); + request.on('finish', () => { timings.requestFinishAt = monotonicTime(); }); + progress.log(`→ ${options.method} ${url.toString()}`); if (options.headers) { for (const [name, value] of Object.entries(options.headers)) diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index e6330f3889..077777ff34 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -212,6 +212,10 @@ export class HarTracer { harEntry.response.statusText = event.statusMessage; harEntry.response.httpVersion = event.httpVersion; harEntry.response.redirectURL = event.headers.location || ''; + + harEntry.timings = event.timings; + this._computeHarEntryTotalTime(harEntry); + for (let i = 0; i < event.rawHeaders.length; i += 2) { harEntry.response.headers.push({ name: event.rawHeaders[i],