UNPKG

qunit-harness

Version:

A library for running qunit tests on a local machine and in the SauceLabs environment.

809 lines (608 loc) 26.9 kB
'use strict'; var _inherits = require('babel-runtime/helpers/inherits').default; var _classCallCheck = require('babel-runtime/helpers/class-call-check').default; var _regeneratorRuntime = require('babel-runtime/regenerator').default; var _Object$keys = require('babel-runtime/core-js/object/keys').default; var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default').default; var _interopRequireWildcard = require('babel-runtime/helpers/interop-require-wildcard').default; exports.__esModule = true; var _bodyParser = require('body-parser'); var _bodyParser2 = _interopRequireDefault(_bodyParser); var _express = require('express'); var _express2 = _interopRequireDefault(_express); var _http = require('http'); var _http2 = _interopRequireDefault(_http); var _path = require('path'); var _path2 = _interopRequireDefault(_path); var _mustache = require('mustache'); var _mustache2 = _interopRequireDefault(_mustache); var _hoganExpress = require('hogan-express'); var _hoganExpress2 = _interopRequireDefault(_hoganExpress); var _os = require('os'); var _os2 = _interopRequireDefault(_os); var _utilsFs = require('./utils/fs'); var fs = _interopRequireWildcard(_utilsFs); var _utilsReadDir = require('./utils/read-dir'); var _utilsReadDir2 = _interopRequireDefault(_utilsReadDir); var _utilsGetTests = require('./utils/get-tests'); var _utilsGetTests2 = _interopRequireDefault(_utilsGetTests); var _utilsPathToUrl = require('./utils/path-to-url'); var _utilsPathToUrl2 = _interopRequireDefault(_utilsPathToUrl); var _events = require('events'); var _ip = require('ip'); var _ip2 = _interopRequireDefault(_ip); var _saucelabsSaucelabs = require('./saucelabs/saucelabs'); var saucelabs = _interopRequireWildcard(_saucelabsSaucelabs); var _cli = require('./cli'); var cli = _interopRequireWildcard(_cli); var _saucelabsReport = require('./saucelabs/report'); var _saucelabsReport2 = _interopRequireDefault(_saucelabsReport); var _cliReport = require('./cli/report'); var _cliReport2 = _interopRequireDefault(_cliReport); var VIEWS_PATH = _path2.default.join(__dirname, 'views'); var GLOBALS_TEMPLATE_PATH = _path2.default.join(__dirname, 'templates/globals.mustache.js'); var QUNIT_SETUP_TEMPLATE_PATH = _path2.default.join(__dirname, 'templates/qunit-setup.mustache'); var STORE_GLOBALS_TEMPLATE_PATH = _path2.default.join(__dirname, 'templates/store-globals.mustache'); var RESTORE_GLOBALS_TEMPLATE_PATH = _path2.default.join(__dirname, 'templates/restore-globals.mustache'); //Globals var contentTypes = { '.js': 'application/javascript', '.css': 'text/css', '.html': 'text/html', 'default': 'text/html' }; //Routes function onPingRequest(req, res) { var delay = req.params.delay || 0; setTimeout(function () { return res.send(delay); }, delay); } function preventCaching(req, res, next) { res.set('cache-control', 'no-cache, no-store, must-revalidate'); next(); } function onScriptRequest(req, res, filePath) { var content; return _regeneratorRuntime.async(function onScriptRequest$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.next = 2; return _regeneratorRuntime.awrap(fs.readfile(filePath)); case 2: content = context$1$0.sent; res.setHeader('content-type', contentTypes['.js']); res.send(content); case 5: case 'end': return context$1$0.stop(); } }, null, this); } function onCssRequest(req, res, filePath) { var content; return _regeneratorRuntime.async(function onCssRequest$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: context$1$0.next = 2; return _regeneratorRuntime.awrap(fs.readfile(filePath)); case 2: content = context$1$0.sent; res.setHeader('content-type', contentTypes['.css']); res.send(content); case 5: case 'end': return context$1$0.stop(); } }, null, this); } function getFile(res, filePath) { return _regeneratorRuntime.async(function getFile$(context$1$0) { while (1) switch (context$1$0.prev = context$1$0.next) { case 0: res.set('Content-Type', contentTypes[_path2.default.extname(filePath)]); context$1$0.t0 = res; context$1$0.next = 4; return _regeneratorRuntime.awrap(fs.readfile(filePath)); case 4: context$1$0.t1 = context$1$0.sent; context$1$0.t0.send.call(context$1$0.t0, context$1$0.t1); case 6: case 'end': return context$1$0.stop(); } }, null, this); } //QUnitServer var QUnitServer = (function (_EventEmitter) { _inherits(QUnitServer, _EventEmitter); function QUnitServer() { _classCallCheck(this, QUnitServer); _EventEmitter.call(this); this.serverPort = 1335; this.crossDomainServerPort = 1336; this.hostname = ''; this.crossDomainHostname = ''; this.basePath = ''; this.app = _express2.default(); this.crossDomainApp = _express2.default(); this.appServer = null; this.crossDomainAppServer = null; this.app.engine('mustache', _hoganExpress2.default); this.app.set('views', VIEWS_PATH); this.app.set('view engine', 'mustache'); this.app.use(_express2.default.static(_path2.default.join(__dirname, '/vendor'))); this.crossDomainApp.use(_express2.default.static(_path2.default.join(__dirname, '/vendor'))); this.app.use(_bodyParser2.default.json()); this.globalsTemplate = fs.readfileSync(GLOBALS_TEMPLATE_PATH, 'utf-8'); this.qunitSetupTemplate = fs.readfileSync(QUNIT_SETUP_TEMPLATE_PATH, 'utf-8'); this.storeGlobalsTemplate = fs.readfileSync(STORE_GLOBALS_TEMPLATE_PATH, 'utf-8'); this.restoreGlobalsTemplate = fs.readfileSync(RESTORE_GLOBALS_TEMPLATE_PATH, 'utf-8'); this.testResources = { scripts: [], css: [] }; this.beforeCallback = null; this.afterCallback = null; this.tasks = {}; this.tasksCounter = 0; this.pendingTests = []; this.sauselabsSettings = null; this.sauselabsTunnel = null; } //Init QUnitServer.prototype._createServers = function _createServers() { this.localhostname = 'http://localhost:' + this.serverPort; var hostname = 'http://' + _ip2.default.address() + ':'; this.hostname = hostname + this.serverPort; this.crossDomainHostname = hostname + this.crossDomainServerPort; this.appServer = this.app.listen(this.serverPort); this.crossDomainAppServer = this.crossDomainApp.listen(this.crossDomainServerPort); }; QUnitServer.prototype._setupRoutes = function _setupRoutes() { var _this = this; //Prevent caching this.app.get('/*', preventCaching); this.crossDomainApp.get('/*', preventCaching); this.app.get('/', function (req, res) { return res.redirect('/fixtures'); }); this.app.get('/start', function (req, res) { return res.redirect(302, _this.hostname + '/run-tests'); }); this.app.get('/run-tests', function (req, res) { return _this._runTests(req, res, _this.pendingTests.map(function (item) { return item; })); }); this.app.get('/run-dir/:dir', function (req, res) { return _this._runDir(req, res, decodeURIComponent(req.params['dir'])); }); this.app.post('/test-done/:id', function (req, res) { return _this._onTestDone(res, req.body.report, req.params['id']); }); this.app.get('/report/:id', function (req, res) { return _this._onReportRequest(res, req.params['id']); }); this.app.get('/test-resource(/)?*', function (req, res) { getFile(res, _path2.default.join(_path2.default.dirname(req.query['base']), req.query['filePath'])); }); this.crossDomainApp.get('/test-resource(/:name)?', function (req, res) { getFile(res, _path2.default.join(_path2.default.dirname(req.query['base']), req.query['filePath'])); }); this.app.get('/fixtures', function (req, res) { return _this._onResourceRequest(req, res, _this.basePath); }); this.app.get('/fixtures/*', function (req, res) { return _this._onResourceRequest(req, res, _this.basePath); }); this.app.all('/ping/:delay', onPingRequest); this.crossDomainApp.all('/ping/:delay', onPingRequest); }; QUnitServer.prototype._registerScript = function _registerScript(script) { this.testResources.scripts.push(script); this.app.get(script.src, function (req, res) { return onScriptRequest(req, res, script.path); }); this.crossDomainApp.get(script.src, function (req, res) { return onScriptRequest(req, res, script.path); }); }; QUnitServer.prototype._registerCss = function _registerCss(css) { this.testResources.css.push(css); this.app.get(css.src, function (req, res) { return onCssRequest(req, res, css.path); }); this.crossDomainApp.get(css.src, function (req, res) { return onCssRequest(req, res, css.path); }); }; //Request handlers QUnitServer.prototype._onResourceRequest = function _onResourceRequest(req, res, basePath) { var reqPath, resourcePath, stats, _ref, dirs, files; return _regeneratorRuntime.async(function _onResourceRequest$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: reqPath = req.params[0] || ''; resourcePath = _path2.default.join(basePath, reqPath); context$2$0.next = 4; return _regeneratorRuntime.awrap(fs.stat(resourcePath)); case 4: stats = context$2$0.sent; if (stats) { context$2$0.next = 7; break; } return context$2$0.abrupt('return', res.sendStatus(404)); case 7: if (!stats.isDirectory()) { context$2$0.next = 17; break; } context$2$0.next = 10; return _regeneratorRuntime.awrap(_utilsReadDir2.default(resourcePath)); case 10: _ref = context$2$0.sent; dirs = _ref.dirs; files = _ref.files; dirs = dirs.map(function (dir) { return { path: dir }; }); files = files.map(function (file) { return { path: file }; }); res.locals = { currentDir: req.path.replace(/^\//, ''), encodedCurrentDir: encodeURIComponent(req.path.replace(/^\//, '')), dirs: dirs, files: files }; return context$2$0.abrupt('return', res.render('dir')); case 17: if (!(resourcePath.indexOf('-test.js') > -1)) { context$2$0.next = 21; break; } context$2$0.next = 20; return _regeneratorRuntime.awrap(this._runTest(res, resourcePath, req.query['taskId'])); case 20: return context$2$0.abrupt('return', context$2$0.sent); case 21: context$2$0.next = 23; return _regeneratorRuntime.awrap(getFile(res, resourcePath)); case 23: return context$2$0.abrupt('return', context$2$0.sent); case 24: case 'end': return context$2$0.stop(); } }, null, this); }; QUnitServer.prototype._onTestDone = function _onTestDone(res, report, taskId) { var task = this.tasks[taskId]; if (task.completed === task.total) return res.end(); task.completed++; task.reports.push({ name: _utilsPathToUrl2.default(_path2.default.join('/fixtures', _path2.default.relative(this.basePath, task.tests[0]))), result: report }); task.tests.shift(); var redirectUrl = null; if (task.tests.length) redirectUrl = _utilsPathToUrl2.default('/fixtures/' + _path2.default.relative(this.basePath, task.tests[0]) + '?taskId=' + taskId);else { redirectUrl = '/report/' + taskId; var failedTaskReports = task.reports.filter(function (report) { return report.result.failed; }); var reports = task.reports; var taskPath = _utilsPathToUrl2.default(task.path).replace(/^\//, ''); this.emit('taskDone', { id: taskId, taskPath: taskPath, encodedTaskPath: encodeURIComponent(taskPath), total: task.total, completed: task.completed, passed: task.completed - failedTaskReports.length, failed: failedTaskReports.length, reports: reports, failedTaskReports: failedTaskReports }); } res.set('Content-Type', contentTypes['default']); res.end(redirectUrl); }; QUnitServer.prototype._onReportRequest = function _onReportRequest(res, taskId) { var task = this.tasks[taskId]; var failedTaskReports = task.reports.filter(function (report) { return report.result.failed; }); var reports = task.reports; var taskPath = _utilsPathToUrl2.default(task.path).replace(/^\//, ''); res.locals = { id: taskId, taskPath: taskPath, encodedTaskPath: encodeURIComponent(taskPath), total: task.total, completed: task.completed, passed: task.completed - failedTaskReports.length, failed: failedTaskReports.length, reports: reports, failedTaskReports: failedTaskReports }; res.render('report'); }; //Test running QUnitServer.prototype._runDir = function _runDir(req, res, dir) { var relativeDir, testsPath, tests; return _regeneratorRuntime.async(function _runDir$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: relativeDir = _path2.default.relative('/fixtures', '/' + dir + '/'); testsPath = _path2.default.join(this.basePath, relativeDir); context$2$0.next = 4; return _regeneratorRuntime.awrap(_utilsGetTests2.default(testsPath, _path2.default.join(this.basePath))); case 4: tests = context$2$0.sent; if (tests.length) { context$2$0.next = 7; break; } return context$2$0.abrupt('return', res.redirect(302, this.basePath + dir)); case 7: context$2$0.next = 9; return _regeneratorRuntime.awrap(this._runTests(req, res, tests, relativeDir)); case 9: case 'end': return context$2$0.stop(); } }, null, this); }; QUnitServer.prototype._runTests = function _runTests(req, res, tests, dir) { var browserName, task; return _regeneratorRuntime.async(function _runTests$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: browserName = req.query.browserName || ''; task = { id: ++this.tasksCounter, path: _path2.default.join('/fixtures', dir || ''), tests: tests, total: tests.length, completed: 0, reports: [] }; this.tasks[task.id] = task; this.emit('startedWorker', browserName, task.id.toString()); context$2$0.next = 6; return _regeneratorRuntime.awrap(this._runTest(res, tests[0], task.id)); case 6: case 'end': return context$2$0.stop(); } }, null, this); }; QUnitServer.prototype._runTest = function _runTest(res, testPath, taskId) { var test, markup, markupFileName, hostname, crossDomainHostname, relativeTestPath, globals; return _regeneratorRuntime.async(function _runTest$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: context$2$0.next = 2; return _regeneratorRuntime.awrap(fs.readfile(testPath, 'utf-8')); case 2: test = context$2$0.sent; markup = ''; if (!/-test$/.test(_path2.default.dirname(testPath))) { context$2$0.next = 12; break; } markupFileName = testPath.replace('-test.js', '.html'); context$2$0.next = 8; return _regeneratorRuntime.awrap(fs.stat(markupFileName)); case 8: if (!context$2$0.sent) { context$2$0.next = 12; break; } context$2$0.next = 11; return _regeneratorRuntime.awrap(fs.readfile(markupFileName, 'utf-8')); case 11: markup = context$2$0.sent; case 12: hostname = this.hostname; crossDomainHostname = this.crossDomainHostname; relativeTestPath = _path2.default.relative(this.basePath, testPath); globals = _mustache2.default.render(this.globalsTemplate, { crossDomainHostname: crossDomainHostname, path: encodeURIComponent(_utilsPathToUrl2.default(relativeTestPath)), testFullPath: encodeURIComponent(testPath.replace(/\\/g, '\\\\')), taskId: taskId, hostname: hostname }); res.locals = { markup: markup, test: test, taskId: taskId || '', globals: globals, qunitSetup: _mustache2.default.render(this.qunitSetupTemplate, { taskId: taskId }), storeGlobals: _mustache2.default.render(this.storeGlobalsTemplate), restoreGlobals: _mustache2.default.render(this.restoreGlobalsTemplate), scripts: this.testResources.scripts, css: this.testResources.css }; res.render('test'); case 18: case 'end': return context$2$0.stop(); } }, null, this); }; //API QUnitServer.prototype.fixtures = function fixtures(basePath) { this.basePath = basePath; return this; }; QUnitServer.prototype.port = function port(_port) { this.serverPort = _port; return this; }; QUnitServer.prototype.crossDomainPort = function crossDomainPort(port) { this.crossDomainServerPort = port; return this; }; QUnitServer.prototype.scripts = function scripts(_scripts) { var _this2 = this; if (Array.isArray(_scripts)) _scripts.forEach(function (script) { return _this2._registerScript(script); });else this._registerScript(_scripts); return this; }; QUnitServer.prototype.css = function css(_css) { var _this3 = this; if (Array.isArray(_css)) _css.forEach(function (css) { return _this3._registerCss(css); });else this._registerCss(_css); return this; }; QUnitServer.prototype.configApp = function configApp(config) { config(this.app); config(this.crossDomainApp); return this; }; QUnitServer.prototype.before = function before(callback) { this.beforeCallback = callback; return this; }; QUnitServer.prototype.after = function after(callback) { this.afterCallback = callback; return this; }; QUnitServer.prototype.saucelabs = function saucelabs(settings) { var curSettings = this.sauselabsSettings || {}; this.sauselabsSettings = { username: settings.username || curSettings.username || '', accessKey: settings.accessKey || curSettings.accessKey || '', build: settings.build || curSettings.build || 'build', tags: settings.tags || curSettings.tags || 'master', browsers: settings.browsers || curSettings.browsers || {}, name: settings.name || curSettings.name || 'QUnit tests', urls: [this.hostname + '/start'], timeout: settings.timeout || curSettings.timeout || 30 }; return this; }; QUnitServer.prototype.cli = function cli(settings) { var curSettings = this.cliSettings || {}; this.cliSettings = { browsers: settings.browsers || curSettings.browsers || {}, startUrl: [this.hostname + '/run-tests'], timeout: settings.timeout || curSettings.timeout || 30 }; return this; }; QUnitServer.prototype.create = function create() { if (!this.basePath) throw 'fixtures path is not defined'; this._createServers(); this._setupRoutes(); console.log('QUnit server listens on', this.localhostname); if (typeof this.beforeCallback === 'function') this.beforeCallback(); return this; }; QUnitServer.prototype.tests = function tests(_tests) { this.pendingTests = _tests; return this; }; QUnitServer.prototype.run = function run() { var report, error, tunnel, reportRes; return _regeneratorRuntime.async(function run$(context$2$0) { while (1) switch (context$2$0.prev = context$2$0.next) { case 0: if (!(!this.sauselabsSettings && !this.cliSettings)) { context$2$0.next = 2; break; } return context$2$0.abrupt('return'); case 2: report = null; error = null; if (!this.sauselabsSettings) { context$2$0.next = 20; break; } context$2$0.next = 7; return _regeneratorRuntime.awrap(saucelabs.openTunnel(this.sauselabsSettings)); case 7: tunnel = context$2$0.sent; context$2$0.prev = 8; context$2$0.next = 11; return _regeneratorRuntime.awrap(saucelabs.run(this.sauselabsSettings)); case 11: report = context$2$0.sent; context$2$0.next = 17; break; case 14: context$2$0.prev = 14; context$2$0.t0 = context$2$0['catch'](8); error = context$2$0.t0; case 17: try { saucelabs.closeTunnel(tunnel); } catch (err) { console.log('ERROR: Can not close saucelabs tunnel:', err); } context$2$0.next = 29; break; case 20: context$2$0.prev = 20; context$2$0.next = 23; return _regeneratorRuntime.awrap(cli.run(this.cliSettings, this)); case 23: report = context$2$0.sent; context$2$0.next = 29; break; case 26: context$2$0.prev = 26; context$2$0.t1 = context$2$0['catch'](20); error = context$2$0.t1; case 29: if (!error) { context$2$0.next = 31; break; } throw error; case 31: try { reportRes = null; if (this.sauselabsSettings) reportRes = _saucelabsReport2.default(report);else reportRes = _cliReport2.default(report); } catch (err) { console.log('ERROR: Can not create the report:', err); } if (reportRes) { context$2$0.next = 34; break; } throw 'tests failed'; case 34: case 'end': return context$2$0.stop(); } }, null, this, [[8, 14], [20, 26]]); }; QUnitServer.prototype.close = function close() { var _this4 = this; if (typeof this.afterCallback === 'function') this.afterCallback(); this.appServer.close(); this.crossDomainAppServer.close(); if (this.cliSettings) { _Object$keys(this.tasks).forEach(function (taskId) { var task = _this4.tasks[taskId]; if (task.reports.some(function (report) { return report.result.failed; })) process.exit(-1); }); process.exit(0); } }; return QUnitServer; })(_events.EventEmitter); exports.default = QUnitServer; ; module.exports = exports.default;