Skip to content

Commit

Permalink
Add proxy-bypass option to prevent using proxy depending on rule (Dev…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexKamaev authored and kirovboris committed Dec 18, 2019
1 parent 07e9649 commit 37145f7
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 59 deletions.
1 change: 1 addition & 0 deletions src/cli/argument-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export default class CLIArgumentParser {
.option('--ports <port1,port2>', 'specify custom port numbers')
.option('--hostname <name>', 'specify the hostname')
.option('--proxy <host>', 'specify the host of the proxy server')
.option('--proxy-bypass <rules>', 'specify a comma-separated list of rules that define URLs accessed bypassing the proxy server')
.option('--qr-code', 'outputs QR-code that repeats URLs used to connect the remote browsers')

// NOTE: these options will be handled by chalk internally
Expand Down
3 changes: 2 additions & 1 deletion src/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ async function runTests (argParser) {
var port1 = opts.ports && opts.ports[0];
var port2 = opts.ports && opts.ports[1];
var externalProxyHost = opts.proxy;
var proxyBypass = opts.proxyBypass;

log.showSpinner();

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

runner
.useProxy(externalProxyHost)
.useProxy(externalProxyHost, proxyBypass)
.src(argParser.src)
.browsers(browsers)
.concurrency(concurrency)
Expand Down
32 changes: 24 additions & 8 deletions src/runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Reporter from '../reporter';
import Task from './task';
import { GeneralError } from '../errors/runtime';
import MESSAGE from '../errors/runtime/message';
import { assertType, is } from '../errors/runtime/type-assertions';


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

this.opts = {
externalProxyHost: null,
proxyBypass: null,
screenshotPath: null,
takeScreenshotsOnFails: false,
skipJsErrors: false,
Expand Down Expand Up @@ -118,6 +120,21 @@ export default class Runner extends EventEmitter {
assets.forEach(asset => this.proxy.GET(asset.path, asset.info));
}

_validateRunOptions () {
const concurrency = this.bootstrapper.concurrency;
const speed = this.opts.speed;
const proxyBypass = this.opts.proxyBypass;

if (typeof speed !== 'number' || isNaN(speed) || speed < 0.01 || speed > 1)
throw new GeneralError(MESSAGE.invalidSpeedValue);

if (typeof concurrency !== 'number' || isNaN(concurrency) || concurrency < 1)
throw new GeneralError(MESSAGE.invalidConcurrencyFactor);

if (proxyBypass)
assertType(is.string, null, '"proxyBypass" argument', proxyBypass);
}


// API
embeddingOptions (opts) {
Expand All @@ -142,9 +159,6 @@ export default class Runner extends EventEmitter {
}

concurrency (concurrency) {
if (typeof concurrency !== 'number' || isNaN(concurrency) || concurrency < 1)
throw new GeneralError(MESSAGE.invalidConcurrencyFactor);

this.bootstrapper.concurrency = concurrency;

return this;
Expand All @@ -165,8 +179,9 @@ export default class Runner extends EventEmitter {
return this;
}

useProxy (externalProxyHost) {
useProxy (externalProxyHost, proxyBypass) {
this.opts.externalProxyHost = externalProxyHost;
this.opts.proxyBypass = proxyBypass;

return this;
}
Expand Down Expand Up @@ -194,12 +209,13 @@ export default class Runner extends EventEmitter {
this.opts.assertionTimeout = assertionTimeout === void 0 ? DEFAULT_ASSERTION_TIMEOUT : assertionTimeout;
this.opts.pageLoadTimeout = pageLoadTimeout === void 0 ? DEFAULT_PAGE_LOAD_TIMEOUT : pageLoadTimeout;

if (typeof speed !== 'number' || isNaN(speed) || speed < 0.01 || speed > 1)
throw new GeneralError(MESSAGE.invalidSpeedValue);

this.opts.speed = speed;

var runTaskPromise = this.bootstrapper.createRunnableConfiguration()
var runTaskPromise = Promise.resolve()
.then(() => {
this._validateRunOptions();
return this.bootstrapper.createRunnableConfiguration();
})
.then(({ reporterPlugins, browserSet, tests, testedApp }) => {
this.emit('done-bootstrapping');

Expand Down
7 changes: 6 additions & 1 deletion src/runner/test-run-controller.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import EventEmitter from 'events';
import { TestRun as LegacyTestRun } from 'testcafe-legacy-api';
import checkUrl from '../utils/check-url';
import TestRun from '../test-run';


Expand Down Expand Up @@ -126,6 +127,10 @@ export default class TestRunController extends EventEmitter {

testRun.start();

return this.proxy.openSession(testRun.test.pageUrl, testRun, this.opts.externalProxyHost);
const pageUrl = testRun.test.pageUrl;
const needBypassHost = this.opts.proxyBypass && checkUrl(pageUrl, this.opts.proxyBypass.split(','));
const externalProxyHost = needBypassHost ? null : this.opts.externalProxyHost;

return this.proxy.openSession(pageUrl, testRun, externalProxyHost);
}
}
68 changes: 68 additions & 0 deletions src/utils/check-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { escapeRegExp as escapeRe } from 'lodash';

const startsWithWildcardRegExp = /^\*\./;
const endsWithWildcardRegExp = /\.\*$/;
const trailingSlashesRegExp = /\/.*$/;
const portRegExp = /:(\d+)$/;
const protocolRegExp = /^(\w+):\/\//;
const wildcardRegExp = /\\\.\\\*/g;

function parseUrl (url) {
if (!url || typeof url !== 'string')
return null;

let protocol = url.match(protocolRegExp);

protocol = protocol ? protocol[1] : null;
url = url.replace(protocolRegExp, '');
url = url.replace(trailingSlashesRegExp, '');

let port = url.match(portRegExp);

port = port ? parseInt(port[1], 10) : null;
url = url.replace(portRegExp, '');

return { protocol, url, port };
}

function prepareRule (url) {
const rule = parseUrl(url);

if (rule) {
rule.url = rule.url.replace(startsWithWildcardRegExp, '.');
rule.url = rule.url.replace(endsWithWildcardRegExp, '.');
}

return rule;
}

function urlMatchRule (sourceUrl, rule) {
if (!sourceUrl || !rule)
return false;

const matchByProtocols = !rule.protocol || !sourceUrl.protocol || rule.protocol === sourceUrl.protocol;
const matchByPorts = !rule.port || sourceUrl.port === rule.port;
const domainRequiredBeforeRule = rule.url.startsWith('.');
const domainRequiredAfterRule = rule.url.endsWith('.');

let regExStr = '^';

if (domainRequiredBeforeRule)
regExStr += '.+';

regExStr += escapeRe(rule.url).replace(wildcardRegExp, '\\..*');

if (domainRequiredAfterRule)
regExStr += '.+';

regExStr += '$';

return new RegExp(regExStr).test(sourceUrl.url) && matchByProtocols && matchByPorts;
}

export default function (url, rules) {
if (!Array.isArray(rules))
rules = [rules];

return rules.some(rule => urlMatchRule(parseUrl(url), prepareRule(rule)));
}
12 changes: 12 additions & 0 deletions test/functional/fixtures/proxy/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var os = require('os');

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

describe('Using external proxy server', function () {
it('Should open page via proxy server', function () {
Expand All @@ -12,3 +13,14 @@ describe('Using external proxy server', function () {
return runTests('testcafe-fixtures/restricted-page.test.js', null, { useProxy: TRUSTED_PROXY_URL });
});
});

describe('Using proxy-bypass', function () {
it('Should bypass using proxy by one rule', function () {
return runTests('testcafe-fixtures/index.test.js', null, { useProxy: ERROR_PROXY_URL, proxyBypass: 'localhost:3000' });
});

it('Should bypass using proxy by set of rules', function () {
return runTests('testcafe-fixtures/index.test.js', null, { useProxy: ERROR_PROXY_URL, proxyBypass: 'dummy,localhost:3000' });
});
});

3 changes: 2 additions & 1 deletion test/functional/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ before(function () {
var appCommand = opts && opts.appCommand;
var appInitDelay = opts && opts.appInitDelay;
var externalProxyHost = opts && opts.useProxy;
var proxyBypass = opts && opts.proxyBypass;
var customReporters = opts && opts.reporters;

var actualBrowsers = browsersInfo.filter(function (browserInfo) {
Expand Down Expand Up @@ -208,7 +209,7 @@ before(function () {
}

return runner
.useProxy(externalProxyHost)
.useProxy(externalProxyHost, proxyBypass)
.browsers(connections)
.filter(function (test) {
return testName ? test === testName : true;
Expand Down
4 changes: 3 additions & 1 deletion test/server/cli-argument-parser-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ describe('CLI argument parser', function () {
});

it('Should parse command line arguments', function () {
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')
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')
.then(function (parser) {
expect(parser.browsers).eql(['ie']);
expect(parser.src).eql([path.resolve(process.cwd(), 'test/server/data/file-list/file-1.js')]);
Expand All @@ -347,6 +347,7 @@ describe('CLI argument parser', function () {
expect(parser.opts.speed).eql(0.5);
expect(parser.opts.qrCode).to.be.ok;
expect(parser.opts.proxy).to.be.ok;
expect(parser.opts.proxyBypass).to.be.ok;
expect(parser.opts.debugOnFail).to.be.ok;
});
});
Expand Down Expand Up @@ -377,6 +378,7 @@ describe('CLI argument parser', function () {
{ long: '--ports' },
{ long: '--hostname' },
{ long: '--proxy' },
{ long: '--proxy-bypass' },
{ long: '--qr-code' },
{ long: '--color' },
{ long: '--no-color' }
Expand Down
Loading

0 comments on commit 37145f7

Please sign in to comment.