diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 2aa7f27fee..6c14b625fe 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 { getMatchingTLSOptionsForOrigin, rewriteOpenSSLErrorIfNeeded } from './socksClientCertificatesInterceptor'; import type * as har from '@trace/har'; +import { TLSSocket } from 'tls'; type FetchRequestOptions = { userAgent: string; @@ -73,6 +74,7 @@ export type APIRequestFinishedEvent = { statusMessage: string; body?: Buffer; timings: har.Timings; + securityDetails?: har.SecurityDetails; }; type SendRequestOptions = https.RequestOptions & { @@ -303,6 +305,8 @@ export abstract class APIRequestContext extends SdkObject { let tlsHandshakeAt: number | undefined; let requestFinishAt: number | undefined; + let securityDetails: har.SecurityDetails | undefined; + const request = requestConstructor(url, requestOptions as any, async response => { const responseAt = monotonicTime(); const notifyRequestFinished = (body?: Buffer) => { @@ -328,6 +332,7 @@ export abstract class APIRequestContext extends SdkObject { cookies, body, timings, + securityDetails, }; this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent); }; @@ -482,7 +487,20 @@ export abstract class APIRequestContext extends SdkObject { // non-happy-eyeballs sockets socket.on('lookup', () => { dnsLookupAt = monotonicTime(); }); socket.on('connect', () => { tcpConnectionAt = monotonicTime(); }); - socket.on('secureConnect', () => { tlsHandshakeAt = monotonicTime(); }); + socket.on('secureConnect', () => { + tlsHandshakeAt = monotonicTime(); + + if (socket instanceof TLSSocket) { + const peerCertificate = socket.getPeerCertificate(); + securityDetails = { + protocol: socket.getProtocol() ?? undefined, + subjectName: peerCertificate.subject.CN, + validFrom: new Date(peerCertificate.valid_from).getTime() / 1000, + validTo: new Date(peerCertificate.valid_to).getTime() / 1000, + issuer: peerCertificate.issuer.CN + }; + } + }); }); request.on('finish', () => { requestFinishAt = monotonicTime(); }); diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 76da6682d4..197b640086 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -218,6 +218,9 @@ export class HarTracer { this._computeHarEntryTotalTime(harEntry); } + if (!this._options.omitSecurityDetails) + harEntry._securityDetails = event.securityDetails; + for (let i = 0; i < event.rawHeaders.length; i += 2) { harEntry.response.headers.push({ name: event.rawHeaders[i], diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index 2b7dab4262..f46854d049 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -610,6 +610,7 @@ it('should have security details', async ({ contextFactory, httpsServer, browser const { page, getLog } = await pageWithHar(contextFactory, testInfo); await page.goto(httpsServer.EMPTY_PAGE); + await page.request.get(httpsServer.EMPTY_PAGE); const log = await getLog(); const { serverIPAddress, _serverPort: port, _securityDetails: securityDetails } = log.entries[0]; if (!mode.startsWith('service')) { @@ -620,6 +621,8 @@ it('should have security details', async ({ contextFactory, httpsServer, browser expect(securityDetails).toEqual({ protocol: 'TLS 1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 }); else expect(securityDetails).toEqual({ issuer: 'playwright-test', protocol: 'TLS 1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 }); + + expect(log.entries[1]._securityDetails).toEqual({ issuer: 'playwright-test', protocol: 'TLSv1.3', subjectName: 'playwright-test', validFrom: 1691708270, validTo: 2007068270 }); }); it('should have connection details for redirects', async ({ contextFactory, server, browserName, mode }, testInfo) => {