diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index 8dc285c4f2615..16b450430c714 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -277,6 +277,8 @@ export class Chromium extends BrowserType { proxyBypassRules.push('<-loopback>'); if (proxy.bypass) proxyBypassRules.push(...proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t)); + if (!process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK && !proxyBypassRules.includes('<-loopback>')) + proxyBypassRules.push('<-loopback>'); if (proxyBypassRules.length > 0) chromeArguments.push(`--proxy-bypass-list=${proxyBypassRules.join(';')}`); } diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index df4ce6bff0094..aca10d61021d7 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -90,10 +90,19 @@ export class CRBrowser extends Browser { async newContext(options: types.BrowserContextOptions): Promise { validateBrowserContextOptions(options, this.options); + + let proxyBypassList = undefined; + if (options.proxy) { + if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK) + proxyBypassList = options.proxy.bypass; + else + proxyBypassList = '<-loopback>' + (options.proxy.bypass ? `,${options.proxy.bypass}` : ''); + } + const { browserContextId } = await this._session.send('Target.createBrowserContext', { disposeOnDetach: true, proxyServer: options.proxy ? options.proxy.server : undefined, - proxyBypassList: options.proxy ? options.proxy.bypass : undefined, + proxyBypassList, }); const context = new CRBrowserContext(this, browserContextId, options); await context._initialize(); diff --git a/tests/browsercontext-proxy.spec.ts b/tests/browsercontext-proxy.spec.ts index dc06d3eee40d4..7f1aa0c34dfd1 100644 --- a/tests/browsercontext-proxy.spec.ts +++ b/tests/browsercontext-proxy.spec.ts @@ -91,6 +91,42 @@ it('should use proxy', async ({ contextFactory, server, proxyServer }) => { await context.close(); }); +it('should use proxy for localhost', async ({ contextFactory, server, proxyServer }) => { + proxyServer.forwardTo(server.PORT); + const context = await contextFactory({ + proxy: { server: `localhost:${proxyServer.PORT}` } + }); + const page = await context.newPage(); + await page.goto(`http://localhost:${server.PORT}/target.html`); + expect(proxyServer.requestUrls).toContain(`http://localhost:${server.PORT}/target.html`); + expect(await page.title()).toBe('Served by the proxy'); + await context.close(); +}); + +it('should use proxy for loopback address', async ({ contextFactory, server, proxyServer }) => { + proxyServer.forwardTo(server.PORT); + const context = await contextFactory({ + proxy: { server: `localhost:${proxyServer.PORT}` } + }); + const page = await context.newPage(); + await page.goto(`http://127.0.0.1:${server.PORT}/target.html`); + expect(proxyServer.requestUrls).toContain(`http://127.0.0.1:${server.PORT}/target.html`); + expect(await page.title()).toBe('Served by the proxy'); + await context.close(); +}); + +it('should use proxy for link-local address', async ({ contextFactory, server, proxyServer }) => { + proxyServer.forwardTo(server.PORT); + const context = await contextFactory({ + proxy: { server: `localhost:${proxyServer.PORT}` } + }); + const page = await context.newPage(); + await page.goto(`http://169.254.3.4:4321/target.html`); + expect(proxyServer.requestUrls).toContain(`http://169.254.3.4:4321/target.html`); + expect(await page.title()).toBe('Served by the proxy'); + await context.close(); +}); + it('should use ipv6 proxy', async ({ contextFactory, server, proxyServer, browserName }) => { it.fail(browserName === 'firefox', 'page.goto: NS_ERROR_UNKNOWN_HOST'); it.fail(!!process.env.INSIDE_DOCKER, 'docker does not support IPv6 by default'); diff --git a/tests/proxy.spec.ts b/tests/proxy.spec.ts index ccb8690005f7d..dc7185ed597ad 100644 --- a/tests/proxy.spec.ts +++ b/tests/proxy.spec.ts @@ -73,6 +73,51 @@ it('should work with IP:PORT notion', async ({ browserType, server }) => { await browser.close(); }); +it('should use proxy for localhost', async ({ browserType, server, proxyServer }) => { + server.setRoute('/target.html', async (req, res) => { + res.end('Served by the proxy'); + }); + proxyServer.forwardTo(server.PORT); + const browser = await browserType.launch({ + proxy: { server: `localhost:${proxyServer.PORT}` } + }); + const page = await browser.newPage(); + await page.goto(`http://localhost:${server.PORT}/target.html`); + expect(proxyServer.requestUrls).toContain(`http://localhost:${server.PORT}/target.html`); + expect(await page.title()).toBe('Served by the proxy'); + await browser.close(); +}); + +it('should use proxy for loopback address', async ({ browserType, server, proxyServer }) => { + server.setRoute('/target.html', async (req, res) => { + res.end('Served by the proxy'); + }); + proxyServer.forwardTo(server.PORT); + const browser = await browserType.launch({ + proxy: { server: `localhost:${proxyServer.PORT}` } + }); + const page = await browser.newPage(); + await page.goto(`http://127.0.0.1:${server.PORT}/target.html`); + expect(proxyServer.requestUrls).toContain(`http://127.0.0.1:${server.PORT}/target.html`); + expect(await page.title()).toBe('Served by the proxy'); + await browser.close(); +}); + +it('should use proxy for link-local address', async ({ browserType, server, proxyServer }) => { + server.setRoute('/target.html', async (req, res) => { + res.end('Served by the proxy'); + }); + proxyServer.forwardTo(server.PORT); + const browser = await browserType.launch({ + proxy: { server: `localhost:${proxyServer.PORT}` } + }); + const page = await browser.newPage(); + await page.goto(`http://169.254.3.4:4321/target.html`); + expect(proxyServer.requestUrls).toContain(`http://169.254.3.4:4321/target.html`); + expect(await page.title()).toBe('Served by the proxy'); + await browser.close(); +}); + it('should authenticate', async ({ browserType, server }) => { server.setRoute('/target.html', async (req, res) => { const auth = req.headers['proxy-authorization'];