feat(proxy): allow specifying proxy (#2485)
This commit is contained in:
parent
71dd9c2f02
commit
fb058ffe0d
|
|
@ -6,11 +6,11 @@
|
|||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"revision": "1101"
|
||||
"revision": "1103"
|
||||
},
|
||||
{
|
||||
"name": "webkit",
|
||||
"revision": "1263"
|
||||
"revision": "1269"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
15
docs/api.md
15
docs/api.md
|
|
@ -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
115
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ export class CRBrowserContext extends BrowserContextBase {
|
|||
this._browser = browser;
|
||||
this._browserContextId = browserContextId;
|
||||
this._evaluateOnNewDocumentSources = [];
|
||||
this._authenticateProxyViaCredentials();
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ export class FFBrowserContext extends BrowserContextBase {
|
|||
this._browser = browser;
|
||||
this._browserContextId = browserContextId;
|
||||
this._evaluateOnNewDocumentSources = [];
|
||||
this._authenticateProxyViaHeader();
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -178,3 +178,10 @@ export type InjectedScriptPoll<T> = {
|
|||
logs: Promise<InjectedScriptLogs>,
|
||||
cancel: () => void,
|
||||
};
|
||||
|
||||
export type ProxySettings = {
|
||||
server: string,
|
||||
bypass?: string,
|
||||
username?: string,
|
||||
password?: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
118
test/proxy.spec.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
|
@ -228,6 +228,7 @@ module.exports = {
|
|||
'./logger.spec.js',
|
||||
'./headful.spec.js',
|
||||
'./multiclient.spec.js',
|
||||
'./proxy.spec.js',
|
||||
],
|
||||
environments: [customEnvironment],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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.'))
|
||||
|
|
|
|||
Loading…
Reference in a new issue