mimik
Version:
Write end-to-end automation tests in natural language
339 lines (307 loc) • 11.4 kB
JavaScript
/*jshint node:true*/
;
var utils = require('../lib/utils'),
logger = require('winston').loggers.get('wdlauncher'),
driverConfig = require('../wdconfig.js'),
async = require('async'),
os = require('os'),
url = require('url'),
path = require('path'),
fs = require('fs'),
http = require('http'),
fsTools = require('fs-tools'),
stdout = process.stdout,
DOWNLOAD_DIR = path.resolve(process.cwd(), 'selenium'),
symbols = {
tick: utils.color('green', isWindows() ? ' \u221A ' : ' \u2713 '),
cross: utils.color('red', isWindows() ? ' \u00D7 ' : ' \u2716 '),
disc: utils.color('grey', isWindows() ? '\u2022' : '\u25CF'),
circle: utils.color('grey', '\u25CB')
};
var Manager = function(config) {
this.config = config || {};
this.config.output = this.config.output || DOWNLOAD_DIR;
};
Manager.prototype.start = function(cb) {
if(this.config.autoInstall) {
this.install(this.doStart.bind(this, cb));
} else {
this.doStart(cb);
}
};
Manager.prototype.doStart = function(cb) {
var me = this;
this.checkStatus(function(err, status) {
if(err) {
return cb(err);
}
if (status.selenium.status !== 'current') {
return cb(new Error(driverConfig.selenium.name + ' is ' + status.selenium.status+ '. Use --auto-install to automatically install missing binaries'));
}
var args = driverConfig.selenium.args(driverConfig.selenium, me.config.output);
if (me.config.port) {
args.push('-port', me.config.port);
}
if (me.config.debug) {
args.push('-debug');
}
utils.each(driverConfig, function(driver, key) {
// only check drivers that are applicable for the current platform
if(key !== 'selenium' && driver.applicable() && status[key].status === 'current') {
args = args.concat(driver.args(driver, me.config.output));
}
});
console.log(utils.color('grey', ' Starting ' + driverConfig.selenium.name + '. Press Ctrl+C to abort.'));
me.proc = me.exec('java', args);
// error event not emitted on windows. For Windows, use the spawn's stdin configuration to show the error directly
me.proc.once('error', function(err) {
if(err) {
if(err.code === 'ENOENT') {
console.error('"java" command not found. Java JDK is required to run ' + driverConfig.selenium.name);
} else {
console.error(err);
}
}
process.exit(err ? 1 : 0);
});
me.proc.once('exit', function(code) {
if(code !== 0) {
console.log(driverConfig.selenium.name + ' has exited with code ' + code);
}
process.exit(code);
});
cb(null);
});
};
Manager.prototype.exec = function(command, args) {
var spawn = require('child_process').spawn,
options = {
stdio: [process.stdin, process.stdout, process.stderr]
};
if (isWindows()) {
// see https://github.com/joyent/node/issues/2318
args = ['/c', command].concat(args);
command = 'cmd';
// Since the error event not emitted on windows, we rely on the spawn's stdin configuration to show the error directly
options = {
stdio: [process.stdin, process.stdout, process.stderr]
};
}
return spawn(command, args, options);
};
Manager.prototype.stop = function() {
if(this.proc) {
this.proc.kill('SIGINT');
}
};
Manager.prototype.status = function(cb) {
this.checkStatus(function(err, status) {
if(err) {
return console.error(err.stack);
}
utils.each(status, function(value, key) {
var driver = driverConfig[key];
if(driver) {
switch(value.status) {
case 'current':
console.log(symbols.tick + driver.name + ' is current');
break;
case 'outdated':
console.log(symbols.cross + driver.name + ' is outdated. Found ' + value.file);
break;
case 'missing':
console.log(symbols.cross + driver.name + ' is missing');
break;
}
}
});
cb(err);
});
};
Manager.prototype.checkStatus = function(cb) {
var status = {},
success = true,
files = [];
try {
files = fs.readdirSync(this.config.output);
} catch(e) {}
// if(!java) {
// return console.error('Could not find `java`, make sure it is installed in your $PATH');
// });
utils.each(driverConfig, function(driver, key) {
// only check drivers that are applicable for the current platform
if(driver.applicable()) {
var driverUrl = driver.url(driver.version);
var driverName = getDriverFileName(driverUrl);
if(~files.indexOf(driverName)) {
status[key] = {
status: 'current',
file: driverName
};
} else {
success = false;
status[key] = {
status: 'missing',
file: null
};
files.forEach(function(file) {
if(file.match(driver.filePattern)) {
status[key] = {
status: 'outdated',
file: file
};
}
});
}
}
});
status.success = success;
cb(null, status);
};
Manager.prototype.install = function(cb) {
var me = this;
me.checkStatus(function(err, status) {
if(err) {
return console.error(err.stack);
}
if(!status.success || me.config.overwrite) {
console.log(utils.color('grey', status.success ? ' Reinstalling binaries...\n' : ' Installing missing binaries...\n'));
return me.doInstall(status, function(err) {
console.log();
cb(err);
});
}
console.log(utils.color('grey', ' All binaries are properly installed'));
cb(null);
});
};
Manager.prototype.doInstall = function(status, cb) {
var me = this;
var tasks = [function(cb) {
me.createDirectory(me.config.output, cb);
}
];
utils.each(driverConfig, function(driver, key) {
if(driver.applicable() && (me.config.overwrite || (driver.install && status[key].status !== 'current'))) {
tasks.push(function(cb) {
me.downloadDriver(driver, me.config.output, function(err, filePath) {
if(!err) {
logger.debug('Downloaded ' + driver.name + ' to ' + filePath);
if(driver.extract) {
console.log(utils.color('grey', ' Processing ') + driver.name);
me.extractTo(filePath, path.resolve(me.config.output, driver.bin));
logger.debug('Extracted ' + filePath + ' to ' + me.config.output);
}
}
cb(err);
});
});
}
});
async.series(
tasks,
cb
);
};
Manager.prototype.dirExists = function(dir, cb) {
fs.stat(dir, function (err, stat) {
cb(!err && stat.isDirectory());
});
};
Manager.prototype.createDirectory = function(dir, cb) {
this.dirExists(dir, function(exists) {
if(exists) {
return cb(null, dir);
}
fsTools.mkdir(dir, function(err) {
if (err) {
logger.error('[wdmanager] could not create directory ' + dir, err);
console.log('Could not create directory', dir);
console.error(err);
}
cb(err, dir);
});
});
};
function getDriverFileName(driverUrl) {
return url.parse(driverUrl).pathname.split('/').pop();
}
Manager.prototype.downloadDriver = function(driver, target, cb) {
var me = this,
driverUrl = driver.url(driver.version),
options = {
host: url.parse(driverUrl).host,
port: 80,
path: url.parse(driverUrl).pathname
};
var driverName = getDriverFileName(driverUrl);
var filePath = path.resolve(target, driverName);
var file = fs.openSync(filePath, 'w');
var progress = 0,
total = 0;
me.showDownloadProgress(driver.name, progress, total);
var request = http.get(options);
request.on('response', function(res) {
var status = res.statusCode;
if (status === 200) {
total = parseInt(res.headers['content-length'], 10);
res.on('data', function(chunk) {
progress += chunk.length;
me.showDownloadProgress(driver.name, progress, total);
fs.writeSync(file, chunk, 0, chunk.length, null);
})
.on('end', function() {
fs.closeSync(file);
cb(null, filePath);
});
} else {
request.abort();
cb(new Error('Could not download ' + driverUrl + '. Received status: ' + status));
}
});
request.on('error', function(err) {
me.showDownloadProgress(driver.name, progress, total, 'error');
cb(err, filePath);
});
};
Manager.prototype.showDownloadProgress = function(name, current, total, status) {
var width = 20,
isWin = isWindows(),
percent = total > 0 ? Math.floor(100 * current/total) : 0,
ratio = total > 0 ? Math.floor(width * current/total) : 0,
mark = status === 'error' ? symbols.cross : (ratio === width ? symbols.tick : ' ' ),
nl = (status === 'error' || status === ' done' || ratio === width) ? '\n' : '',
tpl = mark + utils.color('grey', 'Downloading ') + name + (isWin ? '' : ' {completed}{remaining}') + ' {percent}% ' + nl,
completed = new Array(ratio+1).join(symbols.disc),
remaining = new Array(width - ratio+1).join(symbols.circle);
if(!isWin || percent % 2 === 0) {
stdout.clearLine();
stdout.cursorTo(0);
stdout.write(utils.format(tpl, {
completed: completed,
remaining: remaining,
percent: percent
}));
}
};
Manager.prototype.extractTo = function(source, destination) {
// reading archives
var AdmZip = require('adm-zip'),
zip = new AdmZip(source);
zip.extractAllTo(path.dirname(destination), /*overwrite*/true);
this.fixFilePermissions(destination);
};
Manager.prototype.fixFilePermissions = function(file) {
if(!isWindows()) {
var stat = fs.statSync(file);
// Ref: https://github.com/joyent/node/issues/3045
// S_IXUSR 00100 owner has execute permission
if (!(stat.mode & parseInt('100', 8))) {
fs.chmodSync(file, '755');
}
}
};
function isWindows() {
return (/^win/).test(os.platform());
}
module.exports = Manager;