feat(proxy): allow specifying proxy (#2485)

This commit is contained in:
Pavel Feldman 2020-06-05 13:50:15 -07:00 committed by GitHub
parent 71dd9c2f02
commit fb058ffe0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 354 additions and 6 deletions

View file

@ -6,11 +6,11 @@
},
{
"name": "firefox",
"revision": "1101"
"revision": "1103"
},
{
"name": "webkit",
"revision": "1263"
"revision": "1269"
}
]
}

View file

@ -3997,6 +3997,11 @@ This methods attaches Playwright to an existing browser instance.
- `executablePath` <[string]> Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). Note that Playwright only works with the bundled Chromium, Firefox or WebKit, use at your own risk.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, Playwright does not pass its own configurations args and only uses the ones from `args`. If an array is given, then filters out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `proxy` <[Object]> Network proxy settings.
- `server` <[string]> Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy.
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `firefoxUserPrefs` <[Object]> Firefox user preferences. Learn more about the Firefox user preferences at [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`.
@ -4031,6 +4036,11 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'.
- `executablePath` <[string]> Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled Chromium, Firefox or WebKit, use at your own risk.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use any of the default arguments. If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `proxy` <[Object]> Network proxy settings.
- `server` <[string]> Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy.
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`.
- `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`.
@ -4073,6 +4083,11 @@ Launches browser that uses persistent storage located at `userDataDir` and retur
- `executablePath` <[string]> Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only guaranteed to work with the bundled Chromium, Firefox or WebKit, use at your own risk.
- `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/).
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use any of the default arguments. If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `proxy` <[Object]> Network proxy settings.
- `server` <[string]> Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy.
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `firefoxUserPrefs` <[Object]> Firefox user preferences. Learn more about the Firefox user preferences at [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`.

115
package-lock.json generated
View file

@ -725,6 +725,12 @@
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
"async": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=",
"dev": true
},
"async-each": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz",
@ -1261,6 +1267,15 @@
}
}
},
"cli": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/cli/-/cli-0.4.5.tgz",
"integrity": "sha1-ePlIXNFhtWbppsctcXDEJw6B22E=",
"dev": true,
"requires": {
"glob": ">= 3.1.4"
}
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@ -1276,6 +1291,25 @@
"integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
"dev": true
},
"cliff": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/cliff/-/cliff-0.1.10.tgz",
"integrity": "sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=",
"dev": true,
"requires": {
"colors": "~1.0.3",
"eyes": "~0.1.8",
"winston": "0.8.x"
},
"dependencies": {
"colors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
"dev": true
}
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@ -1560,6 +1594,12 @@
"randomfill": "^1.0.3"
}
},
"cycle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
"integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=",
"dev": true
},
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@ -2238,6 +2278,12 @@
"yauzl": "^2.10.0"
}
},
"eyes": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
@ -3199,6 +3245,12 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
"jpeg-js": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz",
@ -4042,6 +4094,12 @@
"find-up": "^3.0.0"
}
},
"pkginfo": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz",
"integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=",
"dev": true
},
"pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
@ -4800,6 +4858,34 @@
}
}
},
"socksv5": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/socksv5/-/socksv5-0.0.6.tgz",
"integrity": "sha1-EycjX/fo3iGsQ0oKV53GnD8HEGE=",
"dev": true,
"requires": {
"ipv6": "*"
},
"dependencies": {
"ipv6": {
"version": "3.1.1",
"bundled": true,
"dev": true,
"requires": {
"cli": "0.4.x",
"cliff": "0.1.x",
"sprintf": "0.1.x"
},
"dependencies": {
"sprintf": {
"version": "0.1.3",
"bundled": true,
"dev": true
}
}
}
}
},
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@ -4867,6 +4953,12 @@
"figgy-pudding": "^3.5.1"
}
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
"dev": true
},
"static-extend": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
@ -5725,6 +5817,29 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"winston": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-0.8.3.tgz",
"integrity": "sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=",
"dev": true,
"requires": {
"async": "0.2.x",
"colors": "0.6.x",
"cycle": "1.0.x",
"eyes": "0.1.x",
"isstream": "0.1.x",
"pkginfo": "0.3.x",
"stack-trace": "0.0.x"
},
"dependencies": {
"colors": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
"integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=",
"dev": true
}
}
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",

View file

@ -73,6 +73,7 @@
"ncp": "^2.0.0",
"node-stream-zip": "^1.8.2",
"pixelmatch": "^4.0.2",
"socksv5": "0.0.6",
"text-diff": "^1.0.1",
"ts-loader": "^6.1.2",
"typescript": "^3.8.3",

View file

@ -21,6 +21,7 @@ import { Download } from './download';
import type { BrowserServer } from './server/browserServer';
import { Events } from './events';
import { InnerLogger, Log } from './logger';
import { ProxySettings } from './types';
export type BrowserOptions = {
logger: InnerLogger,
@ -29,6 +30,7 @@ export type BrowserOptions = {
persistent?: PersistentContextOptions, // Undefined means no persistent context.
slowMo?: number,
ownedServer?: BrowserServer,
proxy?: ProxySettings,
};
export interface Browser extends EventEmitter {

View file

@ -207,6 +207,26 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements
await oldPage.close();
}
}
protected _authenticateProxyViaHeader() {
const proxy = this._browserBase._options.proxy || { username: undefined, password: undefined };
const { username, password } = proxy;
if (username) {
this._options.httpCredentials = { username, password: password! };
this._options.extraHTTPHeaders = this._options.extraHTTPHeaders || {};
const token = Buffer.from(`${username}:${password}`).toString('base64');
this._options.extraHTTPHeaders['Proxy-Authorization'] = `Basic ${token}`;
}
}
protected _authenticateProxyViaCredentials() {
const proxy = this._browserBase._options.proxy;
if (!proxy)
return;
const { username, password } = proxy;
if (username && password)
this._options.httpCredentials = { username, password };
}
}
export function assertBrowserContextIsNotOwned(context: BrowserContextBase) {
@ -272,3 +292,17 @@ export function verifyGeolocation(geolocation: types.Geolocation): types.Geoloca
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
return result;
}
export function verifyProxySettings(proxy: types.ProxySettings): types.ProxySettings {
let { server, bypass } = proxy;
if (!helper.isString(server))
throw new Error(`Invalid proxy.server: ` + server);
let url = new URL(server);
if (!['http:', 'https:', 'socks5:'].includes(url.protocol)) {
url = new URL('http://' + server);
server = `${url.protocol}//${url.host}`;
}
if (bypass)
bypass = bypass.split(',').map(t => t.trim()).join(',');
return { ...proxy, server, bypass };
}

View file

@ -286,6 +286,7 @@ export class CRBrowserContext extends BrowserContextBase {
this._browser = browser;
this._browserContextId = browserContextId;
this._evaluateOnNewDocumentSources = [];
this._authenticateProxyViaCredentials();
}
async _initialize() {

View file

@ -152,6 +152,7 @@ export class FFBrowserContext extends BrowserContextBase {
this._browser = browser;
this._browserContextId = browserContextId;
this._evaluateOnNewDocumentSources = [];
this._authenticateProxyViaHeader();
}
async _initialize() {

View file

@ -18,7 +18,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import { BrowserContext, PersistentContextOptions, validatePersistentContextOptions } from '../browserContext';
import { BrowserContext, PersistentContextOptions, validatePersistentContextOptions, verifyProxySettings } from '../browserContext';
import { BrowserServer, WebSocketWrapper } from './browserServer';
import * as browserPaths from '../install/browserPaths';
import { Logger, RootLogger, InnerLogger } from '../logger';
@ -29,11 +29,13 @@ import { launchProcess, Env, waitForLine } from './processLauncher';
import { Events } from '../events';
import { PipeTransport } from './pipeTransport';
import { Progress, runAbortableTask } from '../progress';
import { ProxySettings } from '../types';
export type BrowserArgOptions = {
headless?: boolean,
args?: string[],
devtools?: boolean,
proxy?: ProxySettings,
};
export type FirefoxUserPrefsOptions = {
@ -120,6 +122,7 @@ export abstract class BrowserTypeBase implements BrowserType {
}
async _innerLaunch(progress: Progress, options: LaunchOptions, logger: RootLogger, persistent: PersistentContextOptions | undefined, userDataDir?: string): Promise<BrowserBase> {
options.proxy = options.proxy ? verifyProxySettings(options.proxy) : undefined;
const { browserServer, downloadsPath, transport } = await this._launchServer(progress, options, !!persistent, logger, userDataDir);
if ((options as any).__testHookBeforeCreateBrowser)
await (options as any).__testHookBeforeCreateBrowser();
@ -130,6 +133,7 @@ export abstract class BrowserTypeBase implements BrowserType {
logger,
downloadsPath,
ownedServer: browserServer,
proxy: options.proxy,
};
copyTestHooks(options, browserOptions);
const browser = await this._connectToTransport(transport, browserOptions);

View file

@ -79,7 +79,7 @@ export class Chromium extends BrowserTypeBase {
_defaultArgs(options: BrowserArgOptions, isPersistent: boolean, userDataDir: string): string[] {
const { devtools, headless } = processBrowserArgOptions(options);
const { args = [] } = options;
const { args = [], proxy } = options;
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
if (userDataDirArg)
throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument');
@ -102,6 +102,20 @@ export class Chromium extends BrowserTypeBase {
'--mute-audio'
);
}
if (proxy) {
const proxyURL = new URL(proxy.server);
const isSocks = proxyURL.protocol === 'socks5:';
// https://www.chromium.org/developers/design-documents/network-settings
if (isSocks) {
// https://www.chromium.org/developers/design-documents/network-stack/socks-proxy
chromeArguments.push(`--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE ${proxyURL.hostname}"`);
}
chromeArguments.push(`--proxy-server=${proxy.server}`);
if (proxy.bypass) {
const patterns = proxy.bypass.split(',').map(t => t.trim()).map(t => t.startsWith('.') ? '*' + t : t);
chromeArguments.push(`--proxy-bypass-list=${patterns.join(';')}`);
}
}
chromeArguments.push(...args);
if (isPersistent)
chromeArguments.push('about:blank');

View file

@ -59,7 +59,7 @@ export class Firefox extends BrowserTypeBase {
_defaultArgs(options: BrowserArgOptions & FirefoxUserPrefsOptions, isPersistent: boolean, userDataDir: string): string[] {
const { devtools, headless } = processBrowserArgOptions(options);
const { args = [] } = options;
const { args = [], proxy } = options;
if (devtools)
console.warn('devtools parameter is not supported as a launch argument in Firefox. You can launch the devtools window manually.');
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
@ -67,6 +67,23 @@ export class Firefox extends BrowserTypeBase {
throw new Error('Pass userDataDir parameter instead of specifying -profile argument');
if (args.find(arg => arg.startsWith('-juggler')))
throw new Error('Use the port parameter instead of -juggler argument');
if (proxy) {
options.firefoxUserPrefs = options.firefoxUserPrefs || {};
options.firefoxUserPrefs['network.proxy.type'] = 1;
const proxyServer = new URL(proxy.server);
const isSocks = proxyServer.protocol === 'socks5:';
if (isSocks) {
options.firefoxUserPrefs['network.proxy.socks'] = proxyServer.hostname;
options.firefoxUserPrefs['network.proxy.socks_port'] = parseInt(proxyServer.port, 10);
} else {
options.firefoxUserPrefs['network.proxy.http'] = proxyServer.hostname;
options.firefoxUserPrefs['network.proxy.http_port'] = parseInt(proxyServer.port, 10);
options.firefoxUserPrefs['network.proxy.ssl'] = proxyServer.hostname;
options.firefoxUserPrefs['network.proxy.ssl_port'] = parseInt(proxyServer.port, 10);
}
if (proxy.bypass)
options.firefoxUserPrefs['network.proxy.no_proxies_on'] = proxy.bypass;
}
if (options.firefoxUserPrefs) {
const lines: string[] = [];
for (const [name, value] of Object.entries(options.firefoxUserPrefs))

View file

@ -51,7 +51,7 @@ export class WebKit extends BrowserTypeBase {
_defaultArgs(options: BrowserArgOptions, isPersistent: boolean, userDataDir: string): string[] {
const { devtools, headless } = processBrowserArgOptions(options);
const { args = [] } = options;
const { args = [], proxy } = options;
if (devtools)
console.warn('devtools parameter as a launch argument in WebKit is not supported. Also starting Web Inspector manually will terminate the execution in WebKit.');
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir='));
@ -66,6 +66,21 @@ export class WebKit extends BrowserTypeBase {
webkitArguments.push(`--user-data-dir=${userDataDir}`);
else
webkitArguments.push(`--no-startup-window`);
if (proxy) {
if (process.platform === 'darwin') {
webkitArguments.push(`--proxy=${proxy.server}`);
if (proxy.bypass)
webkitArguments.push(`--proxy-bypass-list=${proxy.bypass}`);
} else if (process.platform === 'linux') {
webkitArguments.push(`--proxy=${proxy.server}`);
if (proxy.bypass)
webkitArguments.push(...proxy.bypass.split(',').map(t => `--ignore-host=${t}`));
} else if (process.platform === 'win32') {
webkitArguments.push(`--curl-proxy=${proxy.server}`);
if (proxy.bypass)
webkitArguments.push(`--curl-noproxy=${proxy.bypass}`);
}
}
webkitArguments.push(...args);
if (isPersistent)
webkitArguments.push('about:blank');

View file

@ -178,3 +178,10 @@ export type InjectedScriptPoll<T> = {
logs: Promise<InjectedScriptLogs>,
cancel: () => void,
};
export type ProxySettings = {
server: string,
bypass?: string,
username?: string,
password?: string
}

View file

@ -208,6 +208,7 @@ export class WKBrowserContext extends BrowserContextBase {
this._browser = browser;
this._browserContextId = browserContextId;
this._evaluateOnNewDocumentSources = [];
this._authenticateProxyViaHeader();
}
async _initialize() {

118
test/proxy.spec.js Normal file
View file

@ -0,0 +1,118 @@
/**
* Copyright (c) Microsoft Corporation.
*
* 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.
*/
const socks = require('socksv5');
const utils = require('./utils');
const {FFOX, CHROMIUM, WEBKIT, MAC} = utils.testOptions(browserType);
describe('HTTP Proxy', () => {
it('should use proxy', async ({browserType, defaultBrowserOptions, server}) => {
server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>');
});
const browser = await browserType.launch({
...defaultBrowserOptions,
proxy: { server: `localhost:${server.PORT}` }
});
const page = await browser.newPage();
await page.goto('http://non-existent.com/target.html');
expect(await page.title()).toBe('Served by the proxy');
await browser.close();
});
it('should authenticate', async ({browserType, defaultBrowserOptions, server}) => {
server.setRoute('/target.html', async (req, res) => {
const auth = req.headers['proxy-authorization'];
if (!auth) {
res.writeHead(407, 'Proxy Authentication Required', {
'Proxy-Authenticate': 'Basic realm="Access to internal site"'
});
res.end();
}
res.end(`<html><title>${auth}</title></html>`);
});
const browser = await browserType.launch({
...defaultBrowserOptions,
proxy: { server: `localhost:${server.PORT}`, username: 'user', password: 'secret' }
});
const page = await browser.newPage();
await page.goto('http://non-existent.com/target.html');
expect(await page.title()).toBe('Basic ' + Buffer.from('user:secret').toString('base64'));
await browser.close();
});
it('should exclude patterns', async ({browserType, defaultBrowserOptions, server}) => {
server.setRoute('/target.html', async (req, res) => {
res.end('<html><title>Served by the proxy</title></html>');
});
const browser = await browserType.launch({
...defaultBrowserOptions,
proxy: { server: `localhost:${server.PORT}`, bypass: 'non-existent1.com, .non-existent2.com, .zone' }
});
const page = await browser.newPage();
await page.goto('http://non-existent.com/target.html');
expect(await page.title()).toBe('Served by the proxy');
{
const error = await page.goto('http://non-existent1.com/target.html').catch(e => e);
expect(error.message).toBeTruthy();
}
{
const error = await page.goto('http://sub.non-existent2.com/target.html').catch(e => e);
expect(error.message).toBeTruthy();
}
{
const error = await page.goto('http://foo.zone/target.html').catch(e => e);
expect(error.message).toBeTruthy();
}
await browser.close();
});
});
describe('SOCKS Proxy', () => {
it('should use proxy', async ({ browserType, defaultBrowserOptions, parallelIndex }) => {
const server = socks.createServer((info, accept, deny) => {
if (socket = accept(true)) {
const body = '<html><title>Served by the SOCKS proxy</title></html>';
socket.end([
'HTTP/1.1 200 OK',
'Connection: close',
'Content-Type: text/html',
'Content-Length: ' + Buffer.byteLength(body),
'',
body
].join('\r\n'));
}
});
const socksPort = 9107 + parallelIndex * 2;
server.listen(socksPort, 'localhost');
server.useAuth(socks.auth.None());
const browser = await browserType.launch({
...defaultBrowserOptions,
proxy: { server: `socks5://localhost:${socksPort}` }
});
const page = await browser.newPage();
await page.goto('http://non-existent.com');
expect(await page.title()).toBe('Served by the SOCKS proxy');
await browser.close();
server.close();
});
});

View file

@ -228,6 +228,7 @@ module.exports = {
'./logger.spec.js',
'./headful.spec.js',
'./multiclient.spec.js',
'./proxy.spec.js',
],
environments: [customEnvironment],
},

View file

@ -70,6 +70,8 @@ class MDOutline {
property.required = defaultRequired;
if (property.comment.toLowerCase().includes('defaults to '))
property.required = false;
if (property.comment.startsWith('Optional '))
property.required = false;
if (property.comment.toLowerCase().includes('if applicable.'))
property.required = false;
if (property.comment.toLowerCase().includes('if available.'))