Skip to content

Commit

Permalink
feat(proxy): unify local network proxy behavior
Browse files Browse the repository at this point in the history
When configuring a proxy, Chromium requires a magic tokens to get some
local network requests to go through the proxy. This has tripped up a
few users, so we make the behavior default to the expected: proxy
everything including the local requests. This matches the other vendors
as well.

NB: This can be disabled via
`PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK=1`

Supercedes: microsoft#8345
Fixes: microsoft#10632
  • Loading branch information
rwoll committed Dec 4, 2021
1 parent 8afd0b7 commit 0fae359
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/playwright-core/src/server/chromium/chromium.ts
Expand Up @@ -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(';')}`);
}
Expand Down
11 changes: 10 additions & 1 deletion packages/playwright-core/src/server/chromium/crBrowser.ts
Expand Up @@ -90,10 +90,19 @@ export class CRBrowser extends Browser {

async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
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();
Expand Down
36 changes: 36 additions & 0 deletions tests/browsercontext-proxy.spec.ts
Expand Up @@ -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');
Expand Down
45 changes: 45 additions & 0 deletions tests/proxy.spec.ts
Expand Up @@ -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('<html><title>Served by the proxy</title></html>');
});
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('<html><title>Served by the proxy</title></html>');
});
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('<html><title>Served by the proxy</title></html>');
});
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'];
Expand Down

0 comments on commit 0fae359

Please sign in to comment.