'use strict';
const Handlebars = require('handlebars');
const fs = require('fs-extra');
const lodash = require('lodash');
const Moment = require('moment');
const open = require('open');
const Duration = require('moment-duration-format');
const titleCase = require('title-case').titleCase;
const rootRequire = require('rpcm-root-require');
const LoggerWrapper = rootRequire('/platform-helpers/log4js-wrapper');
const logger = LoggerWrapper.getLogger();
rootRequire('/platform-helpers/string-extensions');
/**
* Ported from @rpii/wdio-html-reporter, we don't want to base64 encode our images (we already need
* a folder for the summary). Besides, the way he's doing it is causing npm install errors for
* prototype pollution.
*
* He uses TypeScript and we don't, so this seems easier than a fork.
*/
class HtmlGenerator {
static async htmlOutput (reportOptions, callback = () => {}) {
try {
Duration(Moment);
logger.info('wdio-html-generator.js --> htmlOutput --> Html Generation started');
const templateFile = fs.readFileSync(reportOptions.templateFilename, 'utf8');
Handlebars.registerHelper('prettyCapitalization', function (str, hbopts) {
if (typeof str !== 'string') return str;
const knownLookup = {
windows: 'Windows',
msedge: 'MSEdge',
macos: 'MacOS',
microsoftedge: 'MicrosoftEdge',
'mac os x': 'Mac OS X'
};
const lookup = knownLookup[str.toLowerCase()];
if (typeof lookup === 'string') {
return lookup;
}
// otherwise, split and capitalize
return str.toLowerCase().split(' ').map(function (word) {
return (word.charAt(0).toUpperCase() + word.slice(1));
}).join(' ');
});
Handlebars.registerHelper('isValidReport', function (suites, hbopts) {
if (suites && suites.length > 0) {
return hbopts.fn(this);
}
return hbopts.inverse(this);
});
Handlebars.registerHelper('isValidSuite', function (suite, hbopts) {
if (suite.title.length > 0 && suite.type === 'suite' && suite.tests.length > 0) {
return hbopts.fn(this);
}
return hbopts.inverse(this);
});
Handlebars.registerHelper('testStateColour', function (state, hbopts) {
if (state === 'passed') {
return 'test-pass';
} else if (state === 'failed') {
return 'test-fail';
} else if (state === 'pending') {
return 'test-pending';
} else if (state === 'skipped') {
return 'test-skipped';
}
});
Handlebars.registerHelper('testStateIcon', function (state, hbopts) {
if (state === 'passed') {
return '<span class="success">✔</span>';
} else if (state === 'failed') {
return '<span class="error">✖</span>';
} else if (state === 'pending') {
return '<span class="pending">✔</span>';
} else if (state === 'skipped') {
return '<span class="skipped">✲</span>';
}
});
Handlebars.registerHelper('suiteStateColour', function (tests, hbopts) {
const numTests = Object.keys(tests).length;
const fail = lodash.values(tests).find(test => {
return test.state === 'failed';
});
if (fail != null) {
return 'suite-fail';
}
const passes = lodash.values(tests).filter(test => {
return test.state === 'passed';
});
if (passes.length === numTests && numTests > 0) {
return 'suite-pass';
} // skipped is the lowest priority check
const skipped = lodash.values(tests).find(test => {
return test.state === 'skipped';
});
if (skipped != null) {
return 'suite-pending';
}
return 'suite-unknown';
});
Handlebars.registerHelper('humanizeDuration', function (duration, hbopts) {
return Moment.duration(duration, 'milliseconds').format('hh:mm:ss', { trim: false });
});
Handlebars.registerHelper('ifSuiteHasTests', function (testsHash, hbopts) {
if (Object.keys(testsHash).length > 0) {
return hbopts.fn(this);
}
return hbopts.inverse(this);
});
Handlebars.registerHelper('ifEventIsError', function (event, hbopts) {
if (event.type.includes('Error')) {
return hbopts.fn(this);
}
return hbopts.inverse(this);
});
Handlebars.registerHelper('ifEventIsScreenshot', function (event, hbopts) {
if (event.type === 'screenshot') {
return hbopts.fn(this);
}
return hbopts.inverse(this);
});
Handlebars.registerHelper('ifEventIsLogMessage', function (event, hbopts) {
if (event.type === 'log') {
return hbopts.fn(this);
}
return hbopts.inverse(this);
});
Handlebars.registerHelper('logClass', function (text, hbopts) {
if (text.includes('Test Iteration')) {
return 'test-iteration';
} else {
return 'log-output';
}
});
Handlebars.registerHelper('fixScreenshotPath', function (path) {
path = path || '';
return (rootRequire.rootPath + '/' + path).replace(/\\/gi, '/');
});
Handlebars.registerHelper('fixRootPath', function (file) {
return '{0}/{1}/{2}'
.format(rootRequire.rootPath, reportOptions.data.options.outputDir, file)
.replace(/\\/gi, '/')
.replace(/\.\//gi, '/')
.replace(/\/\//gi, '/');
});
Handlebars.registerHelper('addOne', function (number, hbopts) {
if (typeof number !== 'number') return number;
return number + 1;
});
Handlebars.registerHelper('shortenPath', function (path) {
path = path || '';
const splitPath = path.split('/');
return splitPath[splitPath.length - 1];
});
Handlebars.registerHelper('convertPathToTitle', function (path) {
const shortPath = Handlebars.helpers.shortenPath(path);
const title = shortPath.replace(/-/g, ' ').replace(/_/g, ' - ').replace(/\.js$/, '');
return titleCase(title);
});
Handlebars.registerHelper('debug', function (optionalValue) {
logger.trace('wdio-html-generator.js --> htmlOutput --> debug --> Handlebars.registerHelper --> Current Context');
logger.trace('wdio-html-generator.js --> htmlOutput --> debug --> Handlebars.registerHelper --> {0}'.format(this));
if (optionalValue) {
logger.trace('wdio-html-generator.js --> htmlOutput --> debug --> Handlebars.registerHelper --> Value');
logger.trace('wdio-html-generator.js --> htmlOutput --> debug --> Handlebars.registerHelper --> {0}'.format(optionalValue));
}
});
Object.keys(reportOptions.templateFuncs).forEach(key => {
Handlebars.registerHelper(key, reportOptions.templateFuncs[key]);
});
if (fs.pathExistsSync(reportOptions.outputDir)) {
const jsonFile = reportOptions.reportFile.replace('.html', '.json');
fs.outputFileSync(jsonFile, JSON.stringify(reportOptions.data));
}
const template = Handlebars.compile(templateFile);
const html = template(reportOptions.data);
if (fs.pathExistsSync(reportOptions.outputDir)) {
fs.outputFileSync(reportOptions.reportFile, html);
try {
if (reportOptions.showInBrowser) {
await open(reportOptions.reportFile);
logger.trace('wdio-html-generator.js --> htmlOutput --> browser launched');
}
} catch (ex) {
logger.error('wdio-html-generator.js --> htmlOutput --> showInBrowser error spawning: {0} {1}'.format(reportOptions.reportFile, ex.toString()));
}
}
logger.info('wdio-html-generator.js --> htmlOutput --> Html Generation completed');
logger.trace('wdio-html-generator.js --> htmlOutput --> processing callback method');
callback(null, true);
} catch (ex) {
logger.error('wdio-html-generator.js --> htmlOutput --> Html Generation processing ended in error: {0}'.format(ex.toString()));
logger.trace('wdio-html-generator.js --> htmlOutput --> processing callback method');
callback(ex);
}
}
}
module.exports = HtmlGenerator;