Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Commit

Permalink
refactor(launcher): some launcher clean-ups and refactors
Browse files Browse the repository at this point in the history
Simplify the ConfigParser and remove the config map for now.
Add unit tests for the ConfigParser and runner, and an integration test for driverProviders.
Make sure the promises returned by the runner all chain nicely.
Make sure the exit code for the entire launcher process is correct.
Change the config so that multiConfig set must be explicit - this prevents weird
issues from merging objects into arrays.
  • Loading branch information
juliemr committed Feb 15, 2014
1 parent 77393d0 commit 6848180
Show file tree
Hide file tree
Showing 17 changed files with 949 additions and 808 deletions.
31 changes: 28 additions & 3 deletions lib/cli.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* The command line interface for interacting with the Protractor runner.
* It takes care of parsing the config file and command line options.
* It takes care of parsing command line options.
*
* Values from command line options override values from the config.
*/
Expand Down Expand Up @@ -67,13 +67,16 @@ if (argv.version) {
process.exit(0);
}

// WebDriver capabilities properties require dot notation.
// WebDriver capabilities properties require dot notation, but optimist parses
// that into an object. Re-flatten it.
var flattenObject = function(obj) {
var prefix = arguments[1] || '';
var out = arguments[2] || {};
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
typeof obj[prop] === 'object' ? flattenObject(obj[prop], prefix + prop + '.', out) : out[prefix + prop] = obj[prop];
typeof obj[prop] === 'object' ?
flattenObject(obj[prop], prefix + prop + '.', out) :
out[prefix + prop] = obj[prop];
}
}
return out;
Expand All @@ -83,5 +86,27 @@ if (argv.capabilities) {
argv.capabilities = flattenObject(argv.capabilities);
}

/**
* Helper to resolve comma separated lists of file pattern strings relative to
* the cwd.
*
* @private
* @param {Array} list
*/
var processFilePatterns_ = function(list) {
var patterns = list.split(',');
patterns.forEach(function(spec, index, arr) {
arr[index] = path.resolve(process.cwd(), spec);
});
return patterns;
};

if (argv.specs) {
argv.specs = processFilePatterns_(argv.specs);
}
if (argv.excludes) {
argv.excludes = processFilePatterns_(argv.excludes);
}

// Run the launcher
require('./launcher').init(argv);
196 changes: 55 additions & 141 deletions lib/configParser.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
var path = require('path'),
glob = require('glob'),
config_ = {
configDir: './',
jasmineNodeOpts: {}
},
//this allows for ease of maintaining public apis of the config while still
// allowing for easy variable renames or future config api changes while
// supported backwards compatibility
configMap_ = {
util = require('util'),
protractor = require('./protractor.js');


//structure is internal name -> supported config apis
'specs': ['specs'],
'exclude': ['exclude'],
'capabilities': ['capabilities'],
'seleniumHost': ['seleniumAddress'],
'rootElement': ['rootElement'],
'baseUrl': ['baseUrl'],
'timeout': ['allScriptsTimeout'],
'browserParams': ['params'],
'framework': ['framework'],
'jasmineOpts': ['jasmineNodeOpts'],
'mochaOpts': ['mochaOpts'],
'seleniumLocal.jar': ['seleniumServerJar'],
'seleniumLocal.args': ['seleniumArgs'],
'seleniumLocal.port': ['seleniumPort'],
'sauceAccount.user': ['sauceUser'],
'sauceAccount.key': ['sauceKey'],
'chromeDriver': ['chromeDriver'],
'chromeOnly': ['chromeOnly'],
'configDir': ['configDir'],
'cucumberOpts.require': ['cucumberOpts.require'],
'cucumberOpts.format': ['cucumberOpts.format'],
'cucumberOpts.tags': ['cucumberOpts.tags']
};
module.exports = ConfigParser = function() {
// Default configuration.
this.config_= {
specs: [],
capabilities: {
browserName: 'chrome'
},
multiCapabilities: [],
rootElement: 'body',
allScriptsTimeout: 11000,
params: {},
framework: 'jasmine',
jasmineNodeOpts: {
isVerbose: false,
showColors: true,
includeStackTrace: true,
stackFilter: protractor.filterStackTrace,
defaultTimeoutInterval: (30 * 1000)
},
seleniumArgs: [],
cucumberOpts: {},
mochaOpts: {
ui: 'bdd',
reporter: 'list'
},
chromeDriver: null,
configDir: './'
};
};

/**
* Merge config objects together.
Expand All @@ -57,145 +57,76 @@ var merge_ = function(into, from) {
/**
* Resolve a list of file patterns into a list of individual file paths.
*
* @param {Array/String} patterns
* @param {Boolean} opt_omitWarnings whether to omit did not match warnings
* @param {Array.<string> | string} patterns
* @param {=boolean} opt_omitWarnings Whether to omit did not match warnings
* @param {=string} opt_relativeTo Path to resolve patterns against
*
* @return {Array} The resolved file paths.
*/
var resolveFilePatterns = function(patterns, opt_omitWarnings) {
ConfigParser.resolveFilePatterns =
function(patterns, opt_omitWarnings, opt_relativeTo) {
var resolvedFiles = [];
var cwd = opt_relativeTo || process.cwd();

patterns = (typeof patterns === 'string') ?
[patterns] : patterns;

if (patterns) {
for (var i = 0; i < patterns.length; ++i) {
var matches = glob.sync(patterns[i], {cwd: config_.configDir});
var matches = glob.sync(patterns[i], {cwd: cwd});
if (!matches.length && !opt_omitWarnings) {
util.puts('Warning: pattern ' + patterns[i] + ' did not match any files.');
}
for (var j = 0; j < matches.length; ++j) {
resolvedFiles.push(path.resolve(config_.configDir, matches[j]));
resolvedFiles.push(path.resolve(cwd, matches[j]));
}
}
}
return resolvedFiles;
};

/**
* Helper to resolve file pattern strings relative to the cwd
*
* @private
* @param {Array} list
*/
var processFilePatterns_ = function(list) {
var patterns = list.split(',');
patterns.forEach(function(spec, index, arr) {
arr[index] = path.resolve(process.cwd(), spec);
});
return patterns;
};

/**
* Add the options in the parameter config to this runner instance.
*
* @private
* @param {Object} additionalConfig
* @param {string} relativeTo the file path to resolve paths against
*/
var addConfig_ = function(additionalConfig) {
ConfigParser.prototype.addConfig_ = function(additionalConfig, relativeTo) {
// All filepaths should be kept relative to the current config location.
// This will not affect absolute paths.
['seleniumServerJar', 'chromeDriver', 'onPrepare'].forEach(function(name) {
if (additionalConfig[name] && additionalConfig.configDir &&
typeof additionalConfig[name] === 'string') {
if (additionalConfig[name] && typeof additionalConfig[name] === 'string') {
additionalConfig[name] =
path.resolve(additionalConfig.configDir, additionalConfig[name]);
path.resolve(relativeTo, additionalConfig[name]);
}
});

// Make sure they're not trying to add in deprecated config vals
// Make sure they're not trying to add in deprecated config vals.
if (additionalConfig.jasmineNodeOpts &&
additionalConfig.jasmineNodeOpts.specFolders) {
throw new Error('Using config.jasmineNodeOpts.specFolders is deprecated ' +
'since Protractor 0.6.0. Please switch to config.specs.');
}
merge_(config_,additionalConfig);
merge_(this.config_, additionalConfig);
};


/**
* Merges in passed in configuration data with existing class defaults
* @public
* @param {Object} config - A set of properties collected that will be merged
* with AbstractTestRunner defaults
*/
var loadConfig = function(configObj, configToLoad) {

if (!configToLoad || !configObj) {
return;
}

/* helper to set the correct value for string dot notation */
function setConfig_(obj, str, val) {
str = str.split('.');
while (str.length > 1) {
obj = obj[str.shift()];
}
obj[str.shift()] = val;
}

/* helper to retrieve the correct value for string dot notation */
function getConfig_(obj, str) {
var arr = str.split(".");
while(arr.length && (obj = obj[arr.shift()]));
return obj;
}

/* helper to determine whether a config value is empty based on type */
function isEmpty_(val) {
return ( val !== null &&
val !== '' &&
val !== undefined &&
!(val instanceof Array &&
!val.length) &&
!(val instanceof Object &&
!Object.keys(val).length)
);
}

//object definition driven merging
var key,configDef,configAlias,i;
for (key in configMap_) {

configDef = configMap_[key];
for (i=0; i<configDef.length; i++) {
configAlias = configDef[i];
var configVal = getConfig_(configToLoad,configAlias);
if (isEmpty_(configVal)) {
//override config default w/ passed in config
setConfig_(configObj,key,configVal);
}
}
}
};




/**
* Public function specialized towards merging in a file's config
*
* @public
* @param {String} filename
*/
var addFileConfig = function(filename) {
ConfigParser.prototype.addFileConfig = function(filename) {
if (!filename) {
return;
return;
}
var filePath = path.resolve(process.cwd(), filename);
var fileConfig = require(filePath).config;
fileConfig.configDir = path.dirname(filePath);
addConfig_(fileConfig);
this.addConfig_(fileConfig, fileConfig.configDir);
return this;
};


Expand All @@ -205,19 +136,9 @@ var addFileConfig = function(filename) {
* @public
* @param {Object} argv
*/
var addArgvConfig = function(argv) {
if (!argv) {
return;
}
// Interpret/parse spec include/exclude patterns
if (argv.specs) {
argv.specs = processFilePatterns_(argv.specs);
}
if (argv.exclude) {
argv.exclude = processFilePatterns(argv.exclude);
}

addConfig_(argv);
ConfigParser.prototype.addConfig = function(argv) {
this.addConfig_(argv, process.cwd());
return this;
};


Expand All @@ -227,13 +148,6 @@ var addArgvConfig = function(argv) {
* @public
* @return {Object} config
*/
var getConfig = function() {
return config_;
ConfigParser.prototype.getConfig = function() {
return this.config_;
};


exports.addArgvConfig = addArgvConfig;
exports.addFileConfig = addFileConfig;
exports.getConfig = getConfig;
exports.loadConfig = loadConfig;
exports.resolveFilePatterns = resolveFilePatterns;
Loading

0 comments on commit 6848180

Please sign in to comment.