From 2fd9c9ec1f8c917a55f61568c9471cb655c94449 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 22 Sep 2026 16:28:59 -0700 Subject: [PATCH] fix(client-certificates): respect launch proxy and proxy.bypass Fixes: https://github.com/microsoft/playwright/issues/42806 --- .../playwright-core/src/server/browser.ts | 8 ++--- .../src/server/browserContext.ts | 4 +++ .../socksClientCertificatesInterceptor.ts | 8 ++--- tests/library/client-certificates.spec.ts | 30 +++++++++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index 53f6363221e5d..66546b7f4bf63 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -18,7 +18,7 @@ import fs from 'fs'; import { makeSocketPath } from '@utils/fileUtils'; import { createGuid } from '@utils/crypto'; -import { BrowserContext, validateBrowserContextOptions } from './browserContext'; +import { BrowserContext, effectiveProxy, validateBrowserContextOptions } from './browserContext'; import { Download } from './download'; import { SdkObject } from './instrumentation'; import { Page } from './page'; @@ -108,10 +108,8 @@ export abstract class Browser extends SdkObject { let context: BrowserContext | undefined; try { if (options.clientCertificates?.length) { - clientCertificatesProxy = await ClientCertificatesProxy.create(progress, options); - options = { ...options }; - options.proxyOverride = clientCertificatesProxy.proxySettings(); - options.internalIgnoreHTTPSErrors = true; + clientCertificatesProxy = await ClientCertificatesProxy.create(progress, { ...options, proxy: effectiveProxy(options.proxy, this.options.proxy) }); + options = { ...options, proxyOverride: clientCertificatesProxy.proxySettings(), internalIgnoreHTTPSErrors: true }; } context = await progress.race(this.doCreateNewContext(options)); context._clientCertificatesProxy = clientCertificatesProxy; diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index cd59af0ba7fe0..5cde633b4bd89 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -828,6 +828,10 @@ export function verifyClientCertificates(clientCertificates?: types.BrowserConte } } +export function effectiveProxy(contextProxy: types.ProxySettings | undefined, launchProxy: types.ProxySettings | undefined): types.ProxySettings | undefined { + return contextProxy || (launchProxy?.server === 'per-context' ? undefined : launchProxy); +} + export function normalizeProxySettings(proxy: types.ProxySettings): types.ProxySettings { let { server, bypass } = proxy; let url; diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 01aa7a3a14ba0..350272eb6c8d7 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -312,10 +312,10 @@ export class ClientCertificatesProxy { } _getProxyAgent(host: string, port: number) { - const proxyFromOptions = createProxyAgent(this._proxy); - if (proxyFromOptions) - return proxyFromOptions; - const proxyFromEnv = getProxyForUrl(`https://${host}:${port}`); + const url = new URL(`https://${net.isIPv6(host) ? `[${host}]` : host}:${port}`); + if (this._proxy) + return createProxyAgent(this._proxy, url); + const proxyFromEnv = getProxyForUrl(url.href); if (proxyFromEnv) return createProxyAgent({ server: proxyFromEnv }); } diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index 6340d5d54297c..2574c735bf5be 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -542,6 +542,36 @@ test.describe('browser', () => { delete process.env.HTTPS_PROXY; }); + test('should respect launch proxy and proxy bypass', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42806' } }, async ({ browserType, startCCServer, asset, browserName, proxyServer, isMac }) => { + const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac }); + const { origin, hostname, port } = new URL(serverURL); + proxyServer.forwardTo(parseInt(port, 10), { allowConnectRequests: true }); + const proxy = { server: `localhost:${proxyServer.PORT}` }; + const clientCertificates = [{ + origin, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), + }]; + const browser = await browserType.launch({ proxy }); + { + const page = await browser.newPage({ ignoreHTTPSErrors: true, clientCertificates }); + await page.goto(serverURL); + await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!'); + const host = browserName === 'webkit' && isMac ? 'localhost' : '127.0.0.1'; + expect([...new Set(proxyServer.connectHosts)]).toEqual([`${host}:${port}`]); + await page.close(); + } + proxyServer.connectHosts = []; + { + const page = await browser.newPage({ ignoreHTTPSErrors: true, clientCertificates, proxy: { ...proxy, bypass: hostname } }); + await page.goto(serverURL); + await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!'); + expect(proxyServer.connectHosts).toEqual([]); + await page.close(); + } + await browser.close(); + }); + test('should pass with matching certificates and when a socks proxy is used', async ({ browser, startCCServer, asset, browserName, isMac }) => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac }); const serverPort = parseInt(new URL(serverURL).port, 10);