Version
1.64.0-next (main @ 07f1a61); code path unchanged since 1.46
Steps to reproduce
When clientCertificates is set, all context traffic is funneled through Playwright's local SOCKS interceptor (socksClientCertificatesInterceptor.ts), which then has to apply the user's proxy settings itself. It gets two cases wrong.
Case A: launch-level proxy is dropped
const browser = await chromium.launch({ proxy: { server: 'http://my-proxy:3128' } });
const context = await browser.newContext({
clientCertificates: [{ origin: 'https://unrelated.example.com', certPath, keyPath }],
});
const page = await context.newPage();
await page.goto('http://localhost:PORT/empty.html');
Case B: proxy.bypass is ignored
const context = await browser.newContext({
proxy: { server: 'http://my-proxy:3128', bypass: 'localhost' },
clientCertificates: [{ origin: 'https://unrelated.example.com', certPath, keyPath }],
});
const page = await context.newPage();
await page.goto('http://localhost:PORT/empty.html');
Repro using the repo's proxyServer fixture (tests/library), with forwardTo(port, { allowConnectRequests: true }) so CONNECT tunnels are recorded:
| Case |
Proxy received |
| launch proxy, no clientCertificates (control) |
requestUrls=["http://localhost:PORT/empty.html"] |
| launch proxy + clientCertificates |
connectHosts=[] requestUrls=[] (page loaded directly) |
context proxy with bypass: 'localhost', no clientCertificates (control) |
nothing |
| same + clientCertificates |
connectHosts=["localhost:PORT"] |
Expected behavior
Traffic with clientCertificates follows the same proxy rules as without: a launch-level proxy is used when the context has none, and hosts in proxy.bypass are connected to directly.
Actual behavior
- With a launch-level proxy and no context-level proxy, every request goes direct and the proxy never sees it.
- With a context-level proxy that has a
bypass list, bypassed hosts are tunneled through the proxy anyway.
Both are silent: no error, just wrong routing.
Additional context
Root cause:
ClientCertificatesProxy only receives the context options (browser.ts newContext), so this._proxy = contextOptions.proxy never sees browser.options.proxy.
_getProxyAgent() calls createProxyAgent(this._proxy) without a forUrl, and createProxyAgent only evaluates proxy.bypass when a URL is supplied.
I intend to work on this and will send a PR.
Environment
- Operating System: macOS (Darwin 25.6.0)
- Node.js: 24.8.0
- Browser: Chromium (bundled r1246)
- Playwright: main @ 07f1a6154
Version
1.64.0-next (main @ 07f1a61); code path unchanged since 1.46
Steps to reproduce
When
clientCertificatesis set, all context traffic is funneled through Playwright's local SOCKS interceptor (socksClientCertificatesInterceptor.ts), which then has to apply the user's proxy settings itself. It gets two cases wrong.Case A: launch-level proxy is dropped
Case B:
proxy.bypassis ignoredRepro using the repo's
proxyServerfixture (tests/library), withforwardTo(port, { allowConnectRequests: true })so CONNECT tunnels are recorded:requestUrls=["http://localhost:PORT/empty.html"]connectHosts=[] requestUrls=[](page loaded directly)bypass: 'localhost', no clientCertificates (control)connectHosts=["localhost:PORT"]Expected behavior
Traffic with
clientCertificatesfollows the same proxy rules as without: a launch-level proxy is used when the context has none, and hosts inproxy.bypassare connected to directly.Actual behavior
bypasslist, bypassed hosts are tunneled through the proxy anyway.Both are silent: no error, just wrong routing.
Additional context
Root cause:
ClientCertificatesProxyonly receives the context options (browser.tsnewContext), sothis._proxy = contextOptions.proxynever seesbrowser.options.proxy._getProxyAgent()callscreateProxyAgent(this._proxy)without aforUrl, andcreateProxyAgentonly evaluatesproxy.bypasswhen a URL is supplied.I intend to work on this and will send a PR.
Environment