Skip to content

Commit

Permalink
feat(config): browsers can specify args and cmd
Browse files Browse the repository at this point in the history
Support optional `cmd` and `args` parameters in the `browsers`
configuration. The `args` are appended to the default command-line
options for the browser. If `overrideArgs` is true, then the `args`
replace the default command-line options for the browser. Occurrences
of `$TEMPDIR` or `$URL` within the `args` are replaced by the temp
directory and URL. Use case is to pass special options, e.g., to
load browser extensions.

Closes karma-runner#278
  • Loading branch information
animous committed Jan 24, 2013
1 parent 5dd63ad commit bc9dd65
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 28 deletions.
12 changes: 12 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ var normalizeConfig = function(config) {
};
};

var normalizeBrowser = function(browser) {
if (helper.isString(browser)) {
browser = {name: browser};
}

return browser;
};

config.files = config.files.map(createPatternObject).map(createPatternMapper(basePathResolve));
config.exclude = config.exclude.map(basePathResolve);
config.junitReporter.outputFile = basePathResolve(config.junitReporter.outputFile);
Expand Down Expand Up @@ -98,6 +106,10 @@ var normalizeConfig = function(config) {
log.warn('"reporter" is deprecated, use "reporters" instead');
}

if (helper.isDefined(config.browsers)) {
config.browsers = config.browsers.map(normalizeBrowser);
}

return config;
};

Expand Down
13 changes: 7 additions & 6 deletions lib/launcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ var log = require('./logger').create('launcher');
var BaseBrowser = require('./launchers/Base');


var ScriptBrowser = function(id, emitter, timeout, retry, script) {
var ScriptBrowser = function(id, emitter, timeout, retry, browserConfig) {
BaseBrowser.apply(this, arguments);

this.name = script;
this.name = browserConfig.cmd;

this._getCommand = function() {
return script;
return browserConfig.cmd;
};
};

Expand All @@ -17,13 +17,14 @@ var Launcher = function(emitter) {
var browsers = [];


this.launch = function(names, hostname, port, urlRoot, timeout, retryLimit) {
this.launch = function(browserConfigs, hostname, port, urlRoot, timeout, retryLimit) {
var url = 'http://' + hostname + ':' + port + urlRoot;
var Cls, browser;

names.forEach(function(name) {
browserConfigs.forEach(function(browserConfig) {
var name = browserConfig.name;
Cls = exports[name + 'Browser'] || ScriptBrowser;
browser = new Cls(Launcher.generateId(), emitter, timeout, retryLimit, name);
browser = new Cls(Launcher.generateId(), emitter, timeout, retryLimit, browserConfig);

log.info('Starting browser %s', browser.name);

Expand Down
29 changes: 27 additions & 2 deletions lib/launchers/Base.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var spawn = require('child_process').spawn;
var path = require('path');
var fs = require('fs');
var rimraf = require('rimraf');
var helper = require('../helper');

var log = require('../logger').create('launcher');
var env = process.env;
Expand All @@ -13,7 +14,7 @@ var FINISHED = 4;
var BEING_TIMEOUTED = 5;


var BaseBrowser = function(id, emitter, captureTimeout, retryLimit) {
var BaseBrowser = function(id, emitter, captureTimeout, retryLimit, config) {

var self = this;
var capturingUrl;
Expand Down Expand Up @@ -89,7 +90,7 @@ var BaseBrowser = function(id, emitter, captureTimeout, retryLimit) {


this._getCommand = function() {
var cmd = path.normalize(env[self.ENV_CMD] || self.DEFAULT_CMD[process.platform]);
var cmd = (config && config.cmd) || path.normalize(env[self.ENV_CMD] || self.DEFAULT_CMD[process.platform]);

if (!cmd) {
log.error('No binary for %s browser on your platform.\n\t' +
Expand Down Expand Up @@ -145,12 +146,36 @@ var BaseBrowser = function(id, emitter, captureTimeout, retryLimit) {
rimraf(self._tempDir, done);
};

function configArgs(tmpDir, url) {
var args = [];
if (helper.isDefined(config) && helper.isDefined(config.args)) {
// Rewrite all the variables in the arguments.
args = config.args.map(function(s) {
s = s.replace('$TEMPDIR', tmpDir);
s = s.replace('$URL', url);
return s;
});
}
return args;
}

this._getOptions = function(url) {
var opts = this._getDefaultOptions(url);
var args = configArgs(this._tempDir, url);
if (args.length) {
var override = helper.isDefined(config.overrideArgs) && config.overrideArgs;
opts = override ? args : opts.concat(args);
}
return opts;
};

this._getDefaultOptions = function(url) {
return [url];
};
};




// PUBLISH
module.exports = BaseBrowser;
2 changes: 1 addition & 1 deletion lib/launchers/Chrome.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var BaseBrowser = require('./Base');
var ChromeBrowser = function() {
BaseBrowser.apply(this, arguments);

this._getOptions = function(url) {
this._getDefaultOptions = function(url) {
// Chrome CLI options
// http://peter.sh/experiments/chromium-command-line-switches/
return [
Expand Down
4 changes: 2 additions & 2 deletions lib/launchers/ChromeCanary.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ var ChromeBrowser = require('./Chrome');
var ChromeCanaryBrowser = function() {
ChromeBrowser.apply(this, arguments);

var parentOptions = this._getOptions;
this._getOptions = function(url) {
var parentOptions = this._getDefaultOptions;
this._getDefaultOptions = function(url) {
// disable crankshaft optimizations, as it causes lot of memory leaks (as of Chrome 23.0)
return parentOptions.call(this, url).concat(['--js-flags="--nocrankshaft --noopt"']);
};
Expand Down
6 changes: 5 additions & 1 deletion lib/launchers/Firefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ var FirefoxBrowser = function(id) {
}

fs.createWriteStream(self._tempDir + '/prefs.js', {flags: 'a'}).write(PREFS);
self._execCommand(command, [url, '-profile', self._tempDir, '-no-remote']);
self._execCommand(command, self._getOptions(url));
});
};

this._getDefaultOptions = function(url) {
return [url, '-profile', this._tempDir, '-no-remote'];
};
};

FirefoxBrowser.prototype = {
Expand Down
2 changes: 1 addition & 1 deletion lib/launchers/Opera.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ var PREFS =
var OperaBrowser = function() {
BaseBrowser.apply(this, arguments);

this._getOptions = function(url) {
this._getDefaultOptions = function(url) {
// Opera CLI options
// http://www.opera.com/docs/switches/
return [
Expand Down
2 changes: 1 addition & 1 deletion lib/launchers/PhantomJS.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var PhantomJSBrowser = function() {
fs.writeFileSync(captureFile, captureCode);

// and start phantomjs
this._execCommand(this._getCommand(), [captureFile]);
this._execCommand(this._getCommand(), this._getOptions(captureFile));
};
};

Expand Down
2 changes: 1 addition & 1 deletion lib/launchers/Safari.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var SafariBrowser = function() {
var staticHtmlPath = self._tempDir + '/redirect.html';

fs.writeFile(staticHtmlPath, content, function(err) {
self._execCommand(self._getCommand(), [staticHtmlPath]);
self._execCommand(self._getCommand(), self._getOptions(staticHtmlPath));
});
});
};
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/coverageRequirejs/testacular.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exclude = [
];

autoWatch = true;
browsers = ['Chrome'];
browsers = [{ name: 'Chrome' }];
singleRun = false;

reporters = ['progress', 'coverage'];
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/qunit/testacular.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ exclude = [

autoWatch = true;

browsers = ['Chrome']
browsers = [{ name: 'Chrome'}]
13 changes: 10 additions & 3 deletions test/unit/config.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe 'config', ->
'config4.js': fsMock.file 0, 'port = 123; autoWatch = true; basePath = "/abs/base"'
'config5.js': fsMock.file 0, 'port = {f: __filename, d: __dirname}' # piggyback on port prop
'config6.js': fsMock.file 0, 'reporters = "junit";'
'config7.js': fsMock.file 0, 'browsers = ["Chrome", "Firefox"];'
'config7.js': fsMock.file 0, 'browsers = ["Chrome", {name: "Firefox"}];'
conf:
'invalid.js': fsMock.file 0, '={function'
'exclude.js': fsMock.file 0, 'exclude = ["one.js", "sub/two.js"];'
Expand Down Expand Up @@ -142,9 +142,16 @@ describe 'config', ->

it 'should override config with cli options, but not deep merge', ->
# regression https://github.com/vojtajina/testacular/issues/283
config = e.parseConfig '/home/config7.js', {browsers: ['Safari']}
config = e.parseConfig '/home/config7.js', {browsers: [{name: 'Safari'}]}

expect(config.browsers).to.deep.equal ['Safari']
expect(config.browsers).to.deep.equal [{name: 'Safari'}]


it 'should normalize browser configurations', ->
# features https://github.com/testacular/testacular/issues/278
config = e.parseConfig '/home/config7.js', {}

expect(config.browsers).to.deep.equal [{name: "Chrome"}, {name: "Firefox"}]


it 'should resolve files and excludes to overriden basePath from cli', ->
Expand Down
16 changes: 8 additions & 8 deletions test/unit/launcher.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,20 @@ describe 'launcher', ->
describe 'launch', ->

it 'should start all browsers', ->
l.launch ['Chrome', 'ChromeCanary'], 'localhost', 1234
l.launch [{ name: 'Chrome' }, { name: 'ChromeCanary' }], 'localhost', 1234

expect(mockSpawn).to.have.been.calledTwice
expect(mockSpawn.getCall(0).args[0]).to.equal 'google-chrome'
expect(mockSpawn.getCall(1).args[0]).to.equal 'google-chrome-canary'


it 'should allow launching a script', ->
l.launch ['/usr/local/bin/special-browser'], 'localhost', 1234, '/'
l.launch [{ cmd: '/usr/local/bin/special-browser' }], 'localhost', 1234, '/'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/special-browser', ['http://localhost:1234/?id=1']


it 'should use the non default host', ->
l.launch ['/usr/local/bin/special-browser'], '127.0.0.1', 1234, '/'
l.launch [{ cmd: '/usr/local/bin/special-browser' }], '127.0.0.1', 1234, '/'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/special-browser', ['http://127.0.0.1:1234/?id=1']


Expand All @@ -90,7 +90,7 @@ describe 'launcher', ->
exitSpy = sinon.spy()

it 'should kill all running processe', ->
l.launch ['Chrome', 'ChromeCanary'], 'localhost', 1234
l.launch [{ name: 'Chrome' }, { name: 'ChromeCanary' }], 'localhost', 1234
l.kill()

expect(mockSpawn._processes.length).to.equal 2
Expand All @@ -99,7 +99,7 @@ describe 'launcher', ->


it 'should call callback when all processes killed', ->
l.launch ['Chrome', 'ChromeCanary'], 'localhost', 1234
l.launch [{ name: 'Chrome' }, { name: 'ChromeCanary' }], 'localhost', 1234
l.kill exitSpy

expect(exitSpy).not.to.have.been.called
Expand All @@ -116,7 +116,7 @@ describe 'launcher', ->


it 'should call callback even if a process had already been killed', (done) ->
l.launch ['Chrome', 'ChromeCanary'], 'localhost', 1234, '/', 0, 1 # disable retry
l.launch [{ name: 'Chrome' }, { name: 'ChromeCanary' }], 'localhost', 1234, '/', 0, 1 # disable retry
mockSpawn._processes[0].emit 'close', 1
mockSpawn._processes[1].emit 'close', 1

Expand All @@ -130,7 +130,7 @@ describe 'launcher', ->
describe 'areAllCaptured', ->

it 'should return true if only if all browsers captured', ->
l.launch ['Chrome', 'ChromeCanary'], 'localhost', 1234
l.launch [{ name: 'Chrome' }, { name: 'ChromeCanary' }], 'localhost', 1234

expect(l.areAllCaptured()).to.equal false

Expand All @@ -144,7 +144,7 @@ describe 'launcher', ->
describe 'onExit', ->

it 'should kill all browsers', (done) ->
l.launch ['Chrome', 'ChromeCanary'], 'localhost', 1234, '/', 0, 1
l.launch [{ name: 'Chrome' }, { name: 'ChromeCanary' }], 'localhost', 1234, '/', 0, 1

emitter.emitAsync('exit').then done

Expand Down
60 changes: 60 additions & 0 deletions test/unit/launchers/Base.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,66 @@ describe 'launchers Base', ->
expect(browser._start).to.have.been.calledWith '/capture/url?id=123'


it 'should use the cmd specified in the config rather than the DEFAULT_CMD', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', cmd: '/usr/local/bin/chrome' }
browser.DEFAULT_CMD = darwin: '/usr/bin/browser'

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/chrome', ['/here?id=123']


it 'should default to appending the command-line arguments specified in the config to the default arguments', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--country=CA'] }
browser.DEFAULT_CMD = darwin: '/usr/bin/browser'

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith path.normalize('/usr/bin/browser'), ['/here?id=123', '--country=CA']


it 'should append the command-line arguments specified in the config to the default arguments', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--country=CA'], overrideArgs: false }
browser.DEFAULT_CMD = darwin: '/usr/bin/browser'

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith path.normalize('/usr/bin/browser'), ['/here?id=123', '--country=CA']


it 'should replace the default arguments with the command-line arguments specified in the config', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--country=CA'], overrideArgs: true }
browser.DEFAULT_CMD = darwin: '/usr/bin/browser'

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith path.normalize('/usr/bin/browser'), ['--country=CA']


it 'should use the cmd from the config and append the command-line arguments specified in the config to the default arguments', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--country=CA'], cmd: '/usr/local/bin/chrome' }

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/chrome', ['/here?id=123', '--country=CA']


it 'should use the cmd from the config and replace the default arguments with the command-line arguments specified in the config', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--country=CA'], overrideArgs: true, cmd: '/usr/local/bin/chrome' }

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/chrome', ['--country=CA']


it 'should use the cmd from the config and append the command-line arguments specified in the config to the default arguments, substituting for $TEMPDIR', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--user-data-dir=$TEMPDIR', '--country=CA'], cmd: '/usr/local/bin/chrome' }

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/chrome', ['/here?id=123', '--user-data-dir='+path.normalize('/tmp/testacular-123'), '--country=CA']


it 'should use the cmd from the config and replace the default arguments with the command-line arguments specified in the config, substituting $TEMPDIR and $URL', ->
browser = new m.BaseBrowser 123, null, 0, 1, { name: 'Chrome', args: ['--user-data-dir=$TEMPDIR', '--country=CA', '$URL'], overrideArgs: true, cmd: '/usr/local/bin/chrome' }

browser.start '/here'
expect(mockSpawn).to.have.been.calledWith '/usr/local/bin/chrome', ['--user-data-dir='+path.normalize('/tmp/testacular-123'), '--country=CA', '/here?id=123']


describe 'kill', ->
it 'should just fire done if already killed', (done) ->
browser = new m.BaseBrowser 123, new events.EventEmitter, 0, 1 # disable retry
Expand Down

0 comments on commit bc9dd65

Please sign in to comment.