Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/playwright-core/src/server/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ export abstract class Browser extends SdkObject {
let context: BrowserContext | undefined;
try {
if (options.clientCertificates?.length) {
clientCertificatesProxy = await ClientCertificatesProxy.create(progress, options);
// The interceptor replaces the browser proxy, so it must apply the browser-level proxy itself.
clientCertificatesProxy = await ClientCertificatesProxy.create(progress, { ...options, proxy: options.proxy ?? this.options.proxy });
options = { ...options };
options.proxyOverride = clientCertificatesProxy.proxySettings();
options.internalIgnoreHTTPSErrors = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,8 @@ export class ClientCertificatesProxy {
) {
verifyClientCertificates(contextOptions.clientCertificates);
this.ignoreHTTPSErrors = contextOptions.ignoreHTTPSErrors;
this._proxy = contextOptions.proxy;
// 'per-context' is a Chromium launch-time placeholder, not a real proxy.
this._proxy = contextOptions.proxy?.server === 'per-context' ? undefined : contextOptions.proxy;
this._initSecureContexts(contextOptions.clientCertificates);
this._socksProxy = new SocksProxy();
this._socksProxy.setPattern('*');
Expand All @@ -312,9 +313,8 @@ export class ClientCertificatesProxy {
}

_getProxyAgent(host: string, port: number) {
const proxyFromOptions = createProxyAgent(this._proxy);
if (proxyFromOptions)
return proxyFromOptions;
if (this._proxy)
return createProxyAgent(this._proxy, new URL(`https://${host.includes(':') ? `[${host}]` : host}:${port}`));
const proxyFromEnv = getProxyForUrl(`https://${host}:${port}`);
if (proxyFromEnv)
return createProxyAgent({ server: proxyFromEnv });
Expand Down
41 changes: 41 additions & 0 deletions tests/library/client-certificates.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,47 @@ test.describe('browser', () => {
await page.close();
});

test('should use the browser-level proxy with client certificates', async ({ browserType, startCCServer, asset, browserName, proxyServer, isMac }) => {
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
proxyServer.forwardTo(parseInt(new URL(serverURL).port, 10), { allowConnectRequests: true });
const browser = await browserType.launch({ proxy: { server: `localhost:${proxyServer.PORT}` } });
try {
const page = await browser.newPage({
ignoreHTTPSErrors: true,
clientCertificates: [{
origin: new URL(serverURL).origin,
certPath: asset('client-certificates/client/trusted/cert.pem'),
keyPath: asset('client-certificates/client/trusted/key.pem'),
}],
});
expect(proxyServer.connectHosts).toEqual([]);
await page.goto(serverURL);
const host = browserName === 'webkit' && isMac ? 'localhost' : '127.0.0.1';
expect([...new Set(proxyServer.connectHosts)]).toEqual([`${host}:${new URL(serverURL).port}`]);
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
} finally {
await browser.close();
}
});

test('should respect proxy bypass with client certificates', async ({ browser, startCCServer, asset, browserName, proxyServer, isMac }) => {
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
proxyServer.forwardTo(parseInt(new URL(serverURL).port, 10), { allowConnectRequests: true });
const page = await browser.newPage({
ignoreHTTPSErrors: true,
clientCertificates: [{
origin: new URL(serverURL).origin,
certPath: asset('client-certificates/client/trusted/cert.pem'),
keyPath: asset('client-certificates/client/trusted/key.pem'),
}],
proxy: { server: `localhost:${proxyServer.PORT}`, bypass: new URL(serverURL).hostname },
});
await page.goto(serverURL);
expect(proxyServer.connectHosts).toEqual([]);
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
await page.close();
});

test('should pass with matching certificates and when a http proxy is used from env', async ({ mode, browser, startCCServer, asset, browserName, proxyServer, isMac }) => {
test.skip(mode !== 'default', 'Out of process transport does not allow us to set env vars dynamically');
process.env.HTTPS_PROXY = `http://localhost:${proxyServer.PORT}`;
Expand Down