Skip to content

Commit ff555e3

Browse files
AlexKamaevAndreyBelym
authored andcommitted
Add proxy-bypass option to prevent using proxy depending on rule (#2187)
1 parent 5a66119 commit ff555e3

File tree

10 files changed

+340
-59
lines changed

10 files changed

+340
-59
lines changed

src/cli/argument-parser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export default class CLIArgumentParser {
107107
.option('--ports <port1,port2>', 'specify custom port numbers')
108108
.option('--hostname <name>', 'specify the hostname')
109109
.option('--proxy <host>', 'specify the host of the proxy server')
110+
.option('--proxy-bypass <rules>', 'specify a comma-separated list of rules that define URLs accessed bypassing the proxy server')
110111
.option('--qr-code', 'outputs QR-code that repeats URLs used to connect the remote browsers')
111112

112113
// NOTE: these options will be handled by chalk internally

src/cli/cli.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ async function runTests (argParser) {
6868
var port1 = opts.ports && opts.ports[0];
6969
var port2 = opts.ports && opts.ports[1];
7070
var externalProxyHost = opts.proxy;
71+
var proxyBypass = opts.proxyBypass;
7172

7273
log.showSpinner();
7374

@@ -87,7 +88,7 @@ async function runTests (argParser) {
8788
reporters.forEach(r => runner.reporter(r.name, r.outStream));
8889

8990
runner
90-
.useProxy(externalProxyHost)
91+
.useProxy(externalProxyHost, proxyBypass)
9192
.src(argParser.src)
9293
.browsers(browsers)
9394
.concurrency(concurrency)

src/runner/index.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Reporter from '../reporter';
99
import Task from './task';
1010
import { GeneralError } from '../errors/runtime';
1111
import MESSAGE from '../errors/runtime/message';
12+
import { assertType, is } from '../errors/runtime/type-assertions';
1213

1314

1415
const DEFAULT_SELECTOR_TIMEOUT = 10000;
@@ -26,6 +27,7 @@ export default class Runner extends EventEmitter {
2627

2728
this.opts = {
2829
externalProxyHost: null,
30+
proxyBypass: null,
2931
screenshotPath: null,
3032
takeScreenshotsOnFails: false,
3133
skipJsErrors: false,
@@ -118,6 +120,21 @@ export default class Runner extends EventEmitter {
118120
assets.forEach(asset => this.proxy.GET(asset.path, asset.info));
119121
}
120122

123+
_validateRunOptions () {
124+
const concurrency = this.bootstrapper.concurrency;
125+
const speed = this.opts.speed;
126+
const proxyBypass = this.opts.proxyBypass;
127+
128+
if (typeof speed !== 'number' || isNaN(speed) || speed < 0.01 || speed > 1)
129+
throw new GeneralError(MESSAGE.invalidSpeedValue);
130+
131+
if (typeof concurrency !== 'number' || isNaN(concurrency) || concurrency < 1)
132+
throw new GeneralError(MESSAGE.invalidConcurrencyFactor);
133+
134+
if (proxyBypass)
135+
assertType(is.string, null, '"proxyBypass" argument', proxyBypass);
136+
}
137+
121138

122139
// API
123140
embeddingOptions (opts) {
@@ -142,9 +159,6 @@ export default class Runner extends EventEmitter {
142159
}
143160

144161
concurrency (concurrency) {
145-
if (typeof concurrency !== 'number' || isNaN(concurrency) || concurrency < 1)
146-
throw new GeneralError(MESSAGE.invalidConcurrencyFactor);
147-
148162
this.bootstrapper.concurrency = concurrency;
149163

150164
return this;
@@ -165,8 +179,9 @@ export default class Runner extends EventEmitter {
165179
return this;
166180
}
167181

168-
useProxy (externalProxyHost) {
182+
useProxy (externalProxyHost, proxyBypass) {
169183
this.opts.externalProxyHost = externalProxyHost;
184+
this.opts.proxyBypass = proxyBypass;
170185

171186
return this;
172187
}
@@ -194,12 +209,13 @@ export default class Runner extends EventEmitter {
194209
this.opts.assertionTimeout = assertionTimeout === void 0 ? DEFAULT_ASSERTION_TIMEOUT : assertionTimeout;
195210
this.opts.pageLoadTimeout = pageLoadTimeout === void 0 ? DEFAULT_PAGE_LOAD_TIMEOUT : pageLoadTimeout;
196211

197-
if (typeof speed !== 'number' || isNaN(speed) || speed < 0.01 || speed > 1)
198-
throw new GeneralError(MESSAGE.invalidSpeedValue);
199-
200212
this.opts.speed = speed;
201213

202-
var runTaskPromise = this.bootstrapper.createRunnableConfiguration()
214+
var runTaskPromise = Promise.resolve()
215+
.then(() => {
216+
this._validateRunOptions();
217+
return this.bootstrapper.createRunnableConfiguration();
218+
})
203219
.then(({ reporterPlugins, browserSet, tests, testedApp }) => {
204220
this.emit('done-bootstrapping');
205221

src/runner/test-run-controller.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import EventEmitter from 'events';
22
import { TestRun as LegacyTestRun } from 'testcafe-legacy-api';
3+
import checkUrl from '../utils/check-url';
34
import TestRun from '../test-run';
45

56

@@ -126,6 +127,10 @@ export default class TestRunController extends EventEmitter {
126127

127128
testRun.start();
128129

129-
return this.proxy.openSession(testRun.test.pageUrl, testRun, this.opts.externalProxyHost);
130+
const pageUrl = testRun.test.pageUrl;
131+
const needBypassHost = this.opts.proxyBypass && checkUrl(pageUrl, this.opts.proxyBypass.split(','));
132+
const externalProxyHost = needBypassHost ? null : this.opts.externalProxyHost;
133+
134+
return this.proxy.openSession(pageUrl, testRun, externalProxyHost);
130135
}
131136
}

src/utils/check-url.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { escapeRegExp as escapeRe } from 'lodash';
2+
3+
const startsWithWildcardRegExp = /^\*\./;
4+
const endsWithWildcardRegExp = /\.\*$/;
5+
const trailingSlashesRegExp = /\/.*$/;
6+
const portRegExp = /:(\d+)$/;
7+
const protocolRegExp = /^(\w+):\/\//;
8+
const wildcardRegExp = /\\\.\\\*/g;
9+
10+
function parseUrl (url) {
11+
if (!url || typeof url !== 'string')
12+
return null;
13+
14+
let protocol = url.match(protocolRegExp);
15+
16+
protocol = protocol ? protocol[1] : null;
17+
url = url.replace(protocolRegExp, '');
18+
url = url.replace(trailingSlashesRegExp, '');
19+
20+
let port = url.match(portRegExp);
21+
22+
port = port ? parseInt(port[1], 10) : null;
23+
url = url.replace(portRegExp, '');
24+
25+
return { protocol, url, port };
26+
}
27+
28+
function prepareRule (url) {
29+
const rule = parseUrl(url);
30+
31+
if (rule) {
32+
rule.url = rule.url.replace(startsWithWildcardRegExp, '.');
33+
rule.url = rule.url.replace(endsWithWildcardRegExp, '.');
34+
}
35+
36+
return rule;
37+
}
38+
39+
function urlMatchRule (sourceUrl, rule) {
40+
if (!sourceUrl || !rule)
41+
return false;
42+
43+
const matchByProtocols = !rule.protocol || !sourceUrl.protocol || rule.protocol === sourceUrl.protocol;
44+
const matchByPorts = !rule.port || sourceUrl.port === rule.port;
45+
const domainRequiredBeforeRule = rule.url.startsWith('.');
46+
const domainRequiredAfterRule = rule.url.endsWith('.');
47+
48+
let regExStr = '^';
49+
50+
if (domainRequiredBeforeRule)
51+
regExStr += '.+';
52+
53+
regExStr += escapeRe(rule.url).replace(wildcardRegExp, '\\..*');
54+
55+
if (domainRequiredAfterRule)
56+
regExStr += '.+';
57+
58+
regExStr += '$';
59+
60+
return new RegExp(regExStr).test(sourceUrl.url) && matchByProtocols && matchByPorts;
61+
}
62+
63+
export default function (url, rules) {
64+
if (!Array.isArray(rules))
65+
rules = [rules];
66+
67+
return rules.some(rule => urlMatchRule(parseUrl(url), prepareRule(rule)));
68+
}

test/functional/fixtures/proxy/test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var os = require('os');
22

33
const TRUSTED_PROXY_URL = os.hostname() + ':3004';
44
const TRANSPARENT_PROXY_URL = os.hostname() + ':3005';
5+
const ERROR_PROXY_URL = 'ERROR';
56

67
describe('Using external proxy server', function () {
78
it('Should open page via proxy server', function () {
@@ -12,3 +13,14 @@ describe('Using external proxy server', function () {
1213
return runTests('testcafe-fixtures/restricted-page.test.js', null, { useProxy: TRUSTED_PROXY_URL });
1314
});
1415
});
16+
17+
describe('Using proxy-bypass', function () {
18+
it('Should bypass using proxy by one rule', function () {
19+
return runTests('testcafe-fixtures/index.test.js', null, { useProxy: ERROR_PROXY_URL, proxyBypass: 'localhost:3000' });
20+
});
21+
22+
it('Should bypass using proxy by set of rules', function () {
23+
return runTests('testcafe-fixtures/index.test.js', null, { useProxy: ERROR_PROXY_URL, proxyBypass: 'dummy,localhost:3000' });
24+
});
25+
});
26+

test/functional/setup.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ before(function () {
165165
var appCommand = opts && opts.appCommand;
166166
var appInitDelay = opts && opts.appInitDelay;
167167
var externalProxyHost = opts && opts.useProxy;
168+
var proxyBypass = opts && opts.proxyBypass;
168169
var customReporters = opts && opts.reporters;
169170

170171
var actualBrowsers = browsersInfo.filter(function (browserInfo) {
@@ -208,7 +209,7 @@ before(function () {
208209
}
209210

210211
return runner
211-
.useProxy(externalProxyHost)
212+
.useProxy(externalProxyHost, proxyBypass)
212213
.browsers(connections)
213214
.filter(function (test) {
214215
return testName ? test === testName : true;

test/server/cli-argument-parser-test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ describe('CLI argument parser', function () {
333333
});
334334

335335
it('Should parse command line arguments', function () {
336-
return parse('-r list -S -q -e --hostname myhost --proxy localhost:1234 --qr-code --app run-app --speed 0.5 --debug-on-fail ie test/server/data/file-list/file-1.js')
336+
return parse('-r list -S -q -e --hostname myhost --proxy localhost:1234 --proxy-bypass localhost:5678 --qr-code --app run-app --speed 0.5 --debug-on-fail ie test/server/data/file-list/file-1.js')
337337
.then(function (parser) {
338338
expect(parser.browsers).eql(['ie']);
339339
expect(parser.src).eql([path.resolve(process.cwd(), 'test/server/data/file-list/file-1.js')]);
@@ -347,6 +347,7 @@ describe('CLI argument parser', function () {
347347
expect(parser.opts.speed).eql(0.5);
348348
expect(parser.opts.qrCode).to.be.ok;
349349
expect(parser.opts.proxy).to.be.ok;
350+
expect(parser.opts.proxyBypass).to.be.ok;
350351
expect(parser.opts.debugOnFail).to.be.ok;
351352
});
352353
});
@@ -377,6 +378,7 @@ describe('CLI argument parser', function () {
377378
{ long: '--ports' },
378379
{ long: '--hostname' },
379380
{ long: '--proxy' },
381+
{ long: '--proxy-bypass' },
380382
{ long: '--qr-code' },
381383
{ long: '--color' },
382384
{ long: '--no-color' }

0 commit comments

Comments
 (0)