From 3feca325a9472ee36f7e866ebe375c7f083e0e36 Mon Sep 17 00:00:00 2001 From: James Diefenderfer Date: Mon, 7 Mar 2022 02:12:26 -0800 Subject: [PATCH] fix: Inherit browser-level proxy settings from incognito context (#7770) * chore: add request proxy tests * fix: inherit browser-level proxy settings from incognito context * fix: skip test on Windows --- src/common/Browser.ts | 2 +- test/proxy.spec.ts | 215 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 test/proxy.spec.ts diff --git a/src/common/Browser.ts b/src/common/Browser.ts index 36dde2bf79dad..f3f6519117be5 100644 --- a/src/common/Browser.ts +++ b/src/common/Browser.ts @@ -319,7 +319,7 @@ export class Browser extends EventEmitter { async createIncognitoBrowserContext( options: BrowserContextOptions = {} ): Promise { - const { proxyServer = '', proxyBypassList = [] } = options; + const { proxyServer, proxyBypassList } = options; const { browserContextId } = await this._connection.send( 'Target.createBrowserContext', diff --git a/test/proxy.spec.ts b/test/proxy.spec.ts new file mode 100644 index 0000000000000..34655d27c104d --- /dev/null +++ b/test/proxy.spec.ts @@ -0,0 +1,215 @@ +/** + * Copyright 2021 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import expect from 'expect'; +import http from 'http'; +import os from 'os'; +import { + getTestState, + describeFailsFirefox, + itFailsWindows, +} from './mocha-utils'; // eslint-disable-line import/extensions +import type { Server, IncomingMessage, ServerResponse } from 'http'; +import type { Browser } from '../lib/cjs/puppeteer/common/Browser.js'; +import type { AddressInfo } from 'net'; + +const HOSTNAME = os.hostname().toLowerCase(); + +/** + * Requests to localhost do not get proxied by default. Create a URL using the hostname + * instead. + */ +function getEmptyPageUrl(server: { PORT: number; EMPTY_PAGE: string }): string { + const emptyPagePath = new URL(server.EMPTY_PAGE).pathname; + + return `http://${HOSTNAME}:${server.PORT}${emptyPagePath}`; +} + +describeFailsFirefox('request proxy', () => { + let browser: Browser; + let proxiedRequestUrls: string[]; + let proxyServer: Server; + let proxyServerUrl: string; + + beforeEach(() => { + proxiedRequestUrls = []; + + proxyServer = http + .createServer( + ( + originalRequest: IncomingMessage, + originalResponse: ServerResponse + ) => { + proxiedRequestUrls.push(originalRequest.url as string); + + const proxyRequest = http.request( + originalRequest.url as string, + { + method: originalRequest.method, + headers: originalRequest.headers, + }, + (proxyResponse) => { + originalResponse.writeHead( + proxyResponse.statusCode as number, + proxyResponse.headers + ); + proxyResponse.pipe(originalResponse, { end: true }); + } + ); + + originalRequest.pipe(proxyRequest, { end: true }); + } + ) + .listen(); + + proxyServerUrl = `http://${HOSTNAME}:${ + (proxyServer.address() as AddressInfo).port + }`; + }); + + afterEach(async () => { + await browser.close(); + + await new Promise((resolve, reject) => { + proxyServer.close((error) => { + if (error) { + reject(error); + } else { + resolve(undefined); + } + }); + }); + }); + + it('should proxy requests when configured', async () => { + const { puppeteer, defaultBrowserOptions, server } = getTestState(); + const emptyPageUrl = getEmptyPageUrl(server); + + browser = await puppeteer.launch({ + ...defaultBrowserOptions, + args: [`--proxy-server=${proxyServerUrl}`], + }); + + const page = await browser.newPage(); + const response = await page.goto(emptyPageUrl); + + expect(response.ok()).toBe(true); + + expect(proxiedRequestUrls).toEqual([emptyPageUrl]); + }); + + it('should respect proxy bypass list', async () => { + const { puppeteer, defaultBrowserOptions, server } = getTestState(); + const emptyPageUrl = getEmptyPageUrl(server); + + browser = await puppeteer.launch({ + ...defaultBrowserOptions, + args: [ + `--proxy-server=${proxyServerUrl}`, + `--proxy-bypass-list=${new URL(emptyPageUrl).host}`, + ], + }); + + const page = await browser.newPage(); + const response = await page.goto(emptyPageUrl); + + expect(response.ok()).toBe(true); + + expect(proxiedRequestUrls).toEqual([]); + }); + + describe('in incognito browser context', () => { + it('should proxy requests when configured at browser level', async () => { + const { puppeteer, defaultBrowserOptions, server } = getTestState(); + const emptyPageUrl = getEmptyPageUrl(server); + + browser = await puppeteer.launch({ + ...defaultBrowserOptions, + args: [`--proxy-server=${proxyServerUrl}`], + }); + + const context = await browser.createIncognitoBrowserContext(); + const page = await context.newPage(); + const response = await page.goto(emptyPageUrl); + + expect(response.ok()).toBe(true); + + expect(proxiedRequestUrls).toEqual([emptyPageUrl]); + }); + + it('should respect proxy bypass list when configured at browser level', async () => { + const { puppeteer, defaultBrowserOptions, server } = getTestState(); + const emptyPageUrl = getEmptyPageUrl(server); + + browser = await puppeteer.launch({ + ...defaultBrowserOptions, + args: [ + `--proxy-server=${proxyServerUrl}`, + `--proxy-bypass-list=${new URL(emptyPageUrl).host}`, + ], + }); + + const context = await browser.createIncognitoBrowserContext(); + const page = await context.newPage(); + const response = await page.goto(emptyPageUrl); + + expect(response.ok()).toBe(true); + + expect(proxiedRequestUrls).toEqual([]); + }); + + /** + * See issues #7873, #7719, and #7698. + */ + itFailsWindows( + 'should proxy requests when configured at context level', + async () => { + const { puppeteer, defaultBrowserOptions, server } = getTestState(); + const emptyPageUrl = getEmptyPageUrl(server); + + browser = await puppeteer.launch(defaultBrowserOptions); + + const context = await browser.createIncognitoBrowserContext({ + proxyServer: proxyServerUrl, + }); + const page = await context.newPage(); + const response = await page.goto(emptyPageUrl); + + expect(response.ok()).toBe(true); + + expect(proxiedRequestUrls).toEqual([emptyPageUrl]); + } + ); + + it('should respect proxy bypass list when configured at context level', async () => { + const { puppeteer, defaultBrowserOptions, server } = getTestState(); + const emptyPageUrl = getEmptyPageUrl(server); + + browser = await puppeteer.launch(defaultBrowserOptions); + + const context = await browser.createIncognitoBrowserContext({ + proxyServer: proxyServerUrl, + proxyBypassList: [new URL(emptyPageUrl).host], + }); + const page = await context.newPage(); + const response = await page.goto(emptyPageUrl); + + expect(response.ok()).toBe(true); + + expect(proxiedRequestUrls).toEqual([]); + }); + }); +});