karma-htmlfile-reporter
Version:
A Karma plugin. Report results in styled html format.
296 lines (243 loc) • 11.6 kB
JavaScript
var os = require('os');
var path = require('path');
var fs = require('fs');
var builder = require('xmlbuilder');
var HTMLReporter = function(baseReporterDecorator, config, emitter, logger, helper, formatError) {
var outputFile = config.htmlReporter.outputFile;
var pageTitle = config.htmlReporter.pageTitle || 'Unit Test Results';
var subPageTitle = config.htmlReporter.subPageTitle || false;
var groupSuites = config.htmlReporter.groupSuites || false;
var useLegacyStyle = config.htmlReporter.useLegacyStyle || false;
var useCompactStyle = config.htmlReporter.useCompactStyle || false;
var showOnlyFailed = config.htmlReporter.showOnlyFailed || false;
var log = logger.create('reporter.html');
var html;
var body;
var suites;
var htmlCreated = false;
var lastSuiteName;
var pendingFileWritings = 0;
var fileWritingFinished = function() {};
var allMessages = [];
var allErrors = [];
var self = this;
baseReporterDecorator(this);
// TODO: remove if public version of this method is available
var basePathResolve = function(relativePath) {
if (helper.isUrlAbsolute(relativePath)) {
return relativePath;
}
if (!helper.isDefined(config.basePath) || !helper.isDefined(relativePath)) {
return '';
}
return path.resolve(config.basePath, relativePath);
};
var htmlHelpers = {
createHead: function() {
var head = html.ele('head');
head.ele('meta', {charset: 'utf-8'});
head.ele('title', {}, pageTitle + (subPageTitle ? ' - ' + subPageTitle : ''));
head.ele('style', {type: 'text/css'}, 'html,body{font-family:Arial,sans-serif;font-size:1rem;margin:0;padding:0;}body{padding:10px 40px;}h1{margin-bottom:0;}h2{margin-top:0;margin-bottom:0;color:#999;}table{width:100%;margin-top:20px;margin-bottom:20px;table-layout:fixed;}tr.header{background:#ddd;font-weight:bold;border-bottom:none;}td{padding:7px;border-top:none;border-left:1px black solid;border-bottom:1px black solid;border-right:none;word-break:break-all;word-wrap:break-word;}tr.pass td{color:#003b07;background:#86e191;}tr.skip td{color:#7d3a00;background:#ffd24a;}tr.fail td{color:#5e0e00;background:#ff9c8a;}tr:first-child td{border-top:1px black solid;}td:last-child{border-right:1px black solid;}tr.overview{font-weight:bold;color:#777;}tr.overview td{padding-bottom:0px;border-bottom:none;}tr.system-out td{color:#777;}tr.system-errors td{color:#f00;}hr{height:2px;margin:30px 0;background:#000;border:none;}section{margin-top:40px;}h3{margin:6px 0;}.overview{color:#333;font-weight:bold;}.system-out{margin:0.4rem 0;}.system-errors{color:#a94442}.spec{padding:0.8rem;margin:0.3rem 0;}.spec--pass{color:#3c763d;background-color:#dff0d8;border:1px solid #d6e9c6;}.spec--skip{color:#8a6d3b;background-color:#fcf8e3;border:1px solid #faebcc;}.spec--fail{color:#a94442;background-color:#f2dede;border:1px solid #ebccd1;}.spec--group{color:#636363;background-color:#f0f0f0;border:1px solid #e6e6e6;margin:0;}.spec--group:not(:first-of-type){margin:20px 0 0 0;}.spec__title{display:inline;}.spec__suite{display:inline;}.spec__descrip{font-weight:normal;}.spec__status{float:right;}.spec__log{padding-left: 2.3rem;}.hidden{display:none;}body.compact .spec p{margin-top:0;margin-bottom:0.5rem;}body.compact .spec,body.compact tr,body.compact .overview,body.compact .system-out,body.compact .system-errors{font-size:0.85rem;}body.compact .spec{padding:0.3rem 0.5rem;}body.compact section{margin-top:30px;}');
},
createBody: function() {
body = html.ele('body', {class:useCompactStyle ? 'compact' : ''});
body.ele('h1', {}, pageTitle);
if (subPageTitle) {
body.ele('h2', {}, subPageTitle);
}
}
};
var createHtmlResults = function(browser) {
var suite;
var header;
var overview;
var timestamp = (new Date()).toLocaleString();
if (!suites) {
self.onRunStart(browser);
}
if (useLegacyStyle) {
suite = suites[browser.id] = body.ele('table', {cellspacing:'0', cellpadding:'0', border:'0'});
suite.ele('tr', {class:'overview'}).ele('td', {colspan:'3', title:browser.fullName}, 'Browser: ' + browser.name);
suite.ele('tr', {class:'overview'}).ele('td', {colspan:'3'}, 'Timestamp: ' + timestamp);
suites[browser.id]['results'] = suite.ele('tr').ele('td', {colspan:'3'});
header = suite.ele('tr', {class:'header'});
header.ele('td', {}, 'Status');
header.ele('td', {}, 'Spec');
header.ele('td', {}, 'Suite / Results');
body.ele('hr');
} else {
suite = suites[browser.id] = body.ele('section', {});
overview = suite.ele('header', {class:'overview'});
// Assemble the Overview
overview.ele('div', {class:'browser'}, 'Browser: ' + browser.name);
overview.ele('div', {class:'timestamp'}, 'Timestamp: ' + timestamp);
// Create paragraph tag for test results to be placed in later
suites[browser.id]['results'] = overview.ele('p', {class:'results'});
}
};
var initializeHtmlForBrowser = function(browser) {
if (!htmlCreated) {
html = builder.create('html', null, 'html', { headless: true });
html.doctype();
htmlHelpers.createHead();
htmlHelpers.createBody();
htmlCreated = true;
}
};
this.adapters = [function(msg) {
allMessages.push(msg);
}];
this.onRunStart = function(browsers) {
suites = {};
browsers.forEach(initializeHtmlForBrowser);
};
this.onBrowserStart = function(browser) {
initializeHtmlForBrowser(browser);
createHtmlResults(browser);
};
this.onBrowserError = function(browser, error) {
initializeHtmlForBrowser(browser);
createHtmlResults(browser);
allErrors.push(formatError(error));
};
this.onBrowserComplete = function(browser) {
var suite = suites[browser.id];
var result = browser.lastResult;
if (suite && suite['results']) {
suite['results'].txt(result.total + ' tests / ');
suite['results'].txt((result.disconnected || result.error ? 1 : 0) + ' errors / ');
suite['results'].txt(result.failed + ' failures / ');
suite['results'].txt(result.skipped + ' skipped / ');
suite['results'].txt('runtime: ' + ((result.netTime || 0) / 1000) + 's');
if (allMessages.length > 0) {
if (useLegacyStyle) {
suite.ele('tr', {class:'system-out'}).ele('td', {colspan:'3'}).raw('<strong>System output:</strong><br />' + allMessages.join('<br />'));
} else {
suite.ele('div', {class:'system-out'}).raw('<strong>System output:</strong><br />' + allMessages.join('<br />'));
}
allMessages = [];
}
if (allErrors.length > 0) {
if (useLegacyStyle) {
suite.ele('tr', {class:'system-errors'}).ele('td', {colspan:'3'}).raw('<strong>Errors:</strong><br />' + allErrors.join('<br />'));
} else {
suite.ele('div', {class:'system-errors'}).raw('<strong>Errors:</strong><br />' + allErrors.join('<br />'));
}
allErrors = [];
}
}
suites[browser.id] = null;
};
this.onRunComplete = function(browsers) {
var htmlToOutput = html;
if (htmlToOutput) {
pendingFileWritings++;
config.basePath = path.resolve(config.basePath || '.');
outputFile = basePathResolve(outputFile);
helper.normalizeWinPath(outputFile);
helper.mkdirIfNotExists(path.dirname(outputFile), function() {
if(!htmlToOutput) {
log.warn('Cannot write HTML report\n\t No output!');
if (!--pendingFileWritings) {
fileWritingFinished();
}
return;
}
fs.writeFile(outputFile, htmlToOutput.end({pretty: true}), function(err) {
if (err) {
log.warn('Cannot write HTML report\n\t' + err.message);
} else {
log.debug('HTML results written to "%s".', outputFile);
}
if (!--pendingFileWritings) {
fileWritingFinished();
}
});
});
} else {
log.error('HTML report was not created\n\t');
}
html = null;
allMessages.length = 0;
allErrors.length = 0;
htmlCreated = false;
};
this.specSuccess = this.specSkipped = this.specFailure = function(browser, result) {
var currentSuite = result.suite;
var suiteName = currentSuite.concat();
var currentSuiteName = currentSuite[0];
var isNewSuite = false;
var specClass = result.skipped ? 'skip' : (result.success ? 'pass' : 'fail');
var specStatus = result.skipped ? 'Skipped' : (result.success ? ('Passed in ' + ((result.time || 0) / 1000) + 's') : 'Failed');
var spec;
var specGroup;
var specHeader;
var specTitle;
var suiteColumn;
if (lastSuiteName !== currentSuiteName) {
isNewSuite = true;
lastSuiteName = currentSuiteName;
}
if (currentSuite.length > 1) {
suiteName.shift();
}
if (!showOnlyFailed || (showOnlyFailed && !result.success)) {
if (useLegacyStyle) {
spec = suites[browser.id].ele('tr', {class:specClass});
spec.ele('td', {}, specStatus);
spec.ele('td', {}, result.description);
suiteColumn = spec.ele('td', {}).raw(currentSuite.join(' » '));
} else {
if (groupSuites && isNewSuite) {
specGroup = suites[browser.id].ele('div', {class: 'spec spec--group'});
specGroup.ele('h3', {class:'spec__header'}).raw(currentSuiteName);
}
spec = suites[browser.id].ele('div', {class: 'spec spec--' + specClass, style: (groupSuites ? ('margin-left:' + ((currentSuite.length - 1) * 20) + 'px;') : '')});
// Create spec header
specHeader = spec.ele('h3', {class:'spec__header'});
// Assemble the spec title
specTitle = specHeader.ele('div', {class:'spec__title'});
specTitle.ele('p', {class:'spec__suite' + (groupSuites ? ((suiteName[0] !== currentSuiteName || suiteName.length > 1) ? '' : ' hidden') : '')}).raw(suiteName.join(' » '));
specTitle.ele('em', {class:'spec__descrip'}, result.description);
// Display spec result
specHeader.ele('div', {class:'spec__status'}, specStatus);
}
}
if (!result.success) {
if (useLegacyStyle) {
result.log.forEach(function(err) {
suiteColumn.raw('<br />' + formatError(err).replace(/</g,'<').replace(/>/g,'>'));
});
} else {
// Error Messages
suiteColumn = spec.ele('p', {class:'spec__log'});
result.log.forEach(function(err, index) {
var message = (index === 0) ? '' : '<br />';
message += formatError(err).replace(/</g,'<').replace(/>/g,'>').replace(/(?:\r\n|\r|\n)/g, '<br />');
suiteColumn.raw(message);
});
}
}
};
// wait for writing all the html files, before exiting
this.onExit = function (done) {
if (pendingFileWritings) {
fileWritingFinished = done;
} else {
done();
}
};
};
HTMLReporter.prototype._repeat = function(n, str) {
var res = [];
var i;
for (i = 0; i < n; ++i) {
res.push(str);
}
return res.join('');
};
HTMLReporter.$inject = ['baseReporterDecorator', 'config', 'emitter', 'logger', 'helper', 'formatError'];
// PUBLISH DI MODULE
module.exports = {
'reporter:html': ['type', HTMLReporter]
};