From 7fcde2ec9337053ea797fe8d00ea97b1eeb1df14 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Wed, 23 Sep 2026 13:53:59 -0700 Subject: [PATCH] chore(proxy): drop legacy 'per-context' launch proxy handling 'per-context' was a placeholder launch proxy required by an old Chromium workaround. Treat it as no launch proxy at launch time and remove the string checks in fetch and client certificates, which never matched the normalized 'http://per-context' value. --- .../playwright-core/src/server/browser.ts | 4 +-- .../src/server/browserContext.ts | 4 --- .../playwright-core/src/server/browserType.ts | 3 +++ packages/playwright-core/src/server/fetch.ts | 9 +------ tests/library/browsercontext-proxy.spec.ts | 25 +++++++++++++++++++ 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index 66546b7f4bf63..b2446893519c1 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, effectiveProxy, validateBrowserContextOptions } from './browserContext'; +import { BrowserContext, validateBrowserContextOptions } from './browserContext'; import { Download } from './download'; import { SdkObject } from './instrumentation'; import { Page } from './page'; @@ -108,7 +108,7 @@ export abstract class Browser extends SdkObject { let context: BrowserContext | undefined; try { if (options.clientCertificates?.length) { - clientCertificatesProxy = await ClientCertificatesProxy.create(progress, { ...options, proxy: effectiveProxy(options.proxy, this.options.proxy) }); + clientCertificatesProxy = await ClientCertificatesProxy.create(progress, { ...options, proxy: options.proxy || this.options.proxy }); options = { ...options, proxyOverride: clientCertificatesProxy.proxySettings(), internalIgnoreHTTPSErrors: true }; } context = await progress.race(this.doCreateNewContext(options)); diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index 5cde633b4bd89..cd59af0ba7fe0 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -828,10 +828,6 @@ 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/browserType.ts b/packages/playwright-core/src/server/browserType.ts index bed4f1d3e86a4..42360d96ef4b6 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -302,6 +302,9 @@ export abstract class BrowserType extends SdkObject { headless = false; if (downloadsPath && !path.isAbsolute(downloadsPath)) downloadsPath = path.join(process.cwd(), downloadsPath); + // Legacy placeholder for "every context sets its own proxy", no longer required. + if (proxy?.server === 'per-context' || proxy?.server === 'http://per-context') + proxy = undefined; if (options.socksProxyPort) proxy = { server: `socks5://127.0.0.1:${options.socksProxyPort}` }; return { ...options, headless, downloadsPath, proxy }; diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 4905f22f8369a..c7bb3a8c53912 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -168,13 +168,6 @@ export abstract class APIRequestContext extends SdkObject { this.emit(APIRequestContext.Events.Dispose); } - private _proxyAgentForUrl(url: URL): http.Agent | undefined { - const proxy = this._defaultOptions().proxy; - // We skip 'per-context' in order to not break existing users. 'per-context' was previously used to - // workaround an upstream Chromium bug. Can be removed in the future. - return createProxyAgent(proxy?.server === 'per-context' ? undefined : proxy, url); - } - private _ensureAgent(protocol: string): http.Agent { let agent = this._agentForProtocol.get(protocol); if (!agent) { @@ -367,7 +360,7 @@ export abstract class APIRequestContext extends SdkObject { ...options, ...happyEyeballsOptions, ...getMatchingTLSOptionsForOrigin(this._defaultOptions().clientCertificates, url.origin), - agent: this._proxyAgentForUrl(url) ?? this._ensureAgent(url.protocol), + agent: createProxyAgent(this._defaultOptions().proxy, url) ?? this._ensureAgent(url.protocol), }; if (options.__testHookLookup) requestOptions.lookup = lookupWithTestHook(options.__testHookLookup); diff --git a/tests/library/browsercontext-proxy.spec.ts b/tests/library/browsercontext-proxy.spec.ts index c5f25e3f5a156..c0861471a6b1d 100644 --- a/tests/library/browsercontext-proxy.spec.ts +++ b/tests/library/browsercontext-proxy.spec.ts @@ -42,6 +42,31 @@ it('should work when passing the proxy only on the context level', async ({ brow } }); +for (const launchProxy of ['per-context', 'http://per-context']) { + it(`should ignore legacy '${launchProxy}' launch proxy`, async ({ browserType, server, proxyServer }) => { + proxyServer.forwardTo(server.PORT, { allowConnectRequests: true }); + const browser = await browserType.launch({ proxy: { server: launchProxy } }); + try { + const context = await browser.newContext({ proxy: { server: proxyServer.HOST } }); + const page = await context.newPage(); + await page.goto('http://non-existent.com/target.html'); + expect(await page.title()).toBe('Served by the proxy'); + const response = await context.request.get('http://non-existent.com/target.html'); + expect(await response.text()).toContain('Served by the proxy'); + expect(proxyServer.connectHosts).toContain('non-existent.com:80'); + + const directContext = await browser.newContext(); + const directPage = await directContext.newPage(); + await directPage.goto(server.PREFIX + '/target.html'); + expect(await directPage.title()).toBe('Served by the proxy'); + const directResponse = await directContext.request.get(server.PREFIX + '/target.html'); + expect(directResponse.ok()).toBe(true); + } finally { + await browser.close(); + } + }); +} + it('should throw for bad server value', async ({ contextFactory }) => { const error = await contextFactory({ // @ts-expect-error server must be a string