UNPKG

beach-day

Version:

API integration testing as fun as a day on the beach

578 lines 24.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _ = require("lodash"); var fs = require("fs"); var path = require("path"); var report_generator_1 = require("./report-generator"); var matchers_1 = require("../utils/matchers"); var project_paths_1 = require("../utils/project-paths"); // Non typescript dependencies var moment = require('moment'); var stringifyObject = require('stringify-object'); var ReporterConfig = /** @class */ (function () { function ReporterConfig(config) { if (config === void 0) { config = {}; } // Default to sensible locations and templates this.reportName = config.reportName ? config.reportName : 'Report: ' + (new Date()).toString(); this.reportDir = config.reportDir ? config.reportDir : path.join(process.cwd(), 'reports'); this.viewDataPath = config.viewDataPath ? config.viewDataPath : path.join(process.cwd(), 'reports', 'data.json'); this.headerTemplatePath = config.headerTemplatePath ? config.headerTemplatePath : path.resolve(project_paths_1.ProjectPaths.templates, 'header.mustache'); this.indexTemplatePath = config.indexTemplatePath ? config.indexTemplatePath : path.resolve(project_paths_1.ProjectPaths.templates, 'index.mustache'); this.suiteTemplatePath = config.suiteTemplatePath ? config.suiteTemplatePath : path.resolve(project_paths_1.ProjectPaths.templates, 'suite.mustache'); this.stylesPath = config.stylesPath ? config.stylesPath : path.resolve(project_paths_1.ProjectPaths.templates, 'styles.scss'); this.titleTemplatePath = config.titleTemplatePath ? config.titleTemplatePath : path.resolve(project_paths_1.ProjectPaths.templates, 'title.mustache'); this.summaryTemplatePath = config.summaryTemplatePath ? config.summaryTemplatePath : path.resolve(project_paths_1.ProjectPaths.templates, 'summary.mustache'); this.latestTemplatePath = config.latestTemplatePath ? config.latestTemplatePath : path.resolve(project_paths_1.ProjectPaths.templates, 'latest.mustache'); this.logToConsole = config.hasOwnProperty('logToConsole') ? config.logToConsole : true; this.includeAllConsoleLogs = config.hasOwnProperty('includeAllConsoleLogs') ? config.includeAllConsoleLogs : false; this.maxTestTime = config.maxTestTime != null ? config.maxTestTime : 2000; } Object.defineProperty(ReporterConfig.prototype, "viewDataDir", { get: function () { return path.dirname(this.viewDataPath); }, enumerable: true, configurable: true }); Object.defineProperty(ReporterConfig.prototype, "reportDynamicName", { get: function () { if (process.env.BEACH_DAY_REPORT_NAME) { return process.env.BEACH_DAY_REPORT_NAME + '.html'; } else { return null; } }, enumerable: true, configurable: true }); Object.defineProperty(ReporterConfig.prototype, "reportDynamicPath", { get: function () { if (this.reportDynamicName) { return path.join(this.reportDir, this.reportDynamicName); } else { return null; } }, enumerable: true, configurable: true }); Object.defineProperty(ReporterConfig.prototype, "latestReportName", { get: function () { if (this.reportDynamicName) { return this.reportDynamicName; } else { return 'beach-day-report.html'; } }, enumerable: true, configurable: true }); Object.defineProperty(ReporterConfig.prototype, "reportStaticPath", { get: function () { return path.join(this.reportDir, 'beach-day-report.html'); }, enumerable: true, configurable: true }); return ReporterConfig; }()); exports.ReporterConfig = ReporterConfig; /* API -------------------------------------------- */ // Store refs before they are overridden exports.consoleOrig = global.console; var logOrig = global.console.log; var infoOrig = global.console.info; var debugOrig = global.console.trace; var warnOrig = global.console.warn; var errorOrig = global.console.error; var ReporterConsole = /** @class */ (function () { function ReporterConsole() { var _this = this; this.logToConsole = true; this.log = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } _this.store(args, logOrig); }; this.info = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } _this.store(args, infoOrig); }; this.debug = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } _this.store(args, debugOrig); }; this.warn = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } _this.store(args, warnOrig); }; this.error = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } _this.store(args, errorOrig); }; } Object.defineProperty(ReporterConsole.prototype, "currentSpec", { set: function (value) { this._currentSpec = value; // Append the cached log lines if (this.cache) { this._currentSpec.debugData = this._currentSpec.debugData.concat(this.cache); this.cache = null; } }, enumerable: true, configurable: true }); ReporterConsole.prototype.store = function (args, logger) { if (this._currentSpec) { this._currentSpec.debugData.push(stringLogArguments(args)); } else { if (this.cache == null) this.cache = []; this.cache.push(stringLogArguments(args)); } if (this.logToConsole) { logger.apply(exports.consoleOrig, args); } }; return ReporterConsole; }()); exports.ReporterConsole = ReporterConsole; var reporterConsole = new ReporterConsole(); reporterConsole.logToConsole = false; exports.console = reporterConsole; function stringLogArguments(args) { var newArgs = []; for (var a = 0; a < args.length; a++) { var item = args[a]; if (typeof item == 'object') { var result = stringifyObject(item, { singleQuotes: false }).replace(/(\r\n|\n|\r|\t)/gm, ''); newArgs.push(result); } else { newArgs.push(item); } } return newArgs.join(', '); } // Hook to allow the env to register itself with the reporter var lastCreatedInstance; exports.ReporterAPI = { /** * Overrides the default max test time for the reporter for a specific test * Note: this can only be called inside the execution block of a test * * @param value {number} The max test time to used, milliseconds */ overrideSpecMaxTestTime: function (value) { lastCreatedInstance.overrideSpecMaxTestTime(value); }, /** * Can be used used to ensure async tasks don't run after a test timeout or failure * @returns {string} The current spec's id */ getCurrentSpecId: function () { return lastCreatedInstance.currentSpecId; }, /** * Used by the JasmineAsyncEnv to set a reference to itself on the reporter * @param env {JasmineAsyncEnv} */ setCurrentEnvironment: function (env) { lastCreatedInstance.currentEnvironment = env; }, /** * Used to retrieve the reporters config * @returns {ReporterConfig} */ getReporterConfig: function () { return lastCreatedInstance.config; } }; var BeachDayReporter = /** @class */ (function () { function BeachDayReporter(config) { if (lastCreatedInstance) throw new Error('You can only create 1 instance of the BeachDayReporter at a time'); if (config && !(config instanceof ReporterConfig)) { config = new ReporterConfig(config); } this.config = config == null ? new ReporterConfig() : config; lastCreatedInstance = this; reporterConsole.logToConsole = this.config.logToConsole; // Override with our local proxy if (this.config.includeAllConsoleLogs) { global.console.log = reporterConsole.log; global.console.info = reporterConsole.info; global.console.trace = reporterConsole.debug; global.console.warn = reporterConsole.warn; global.console.error = reporterConsole.error; } } Object.defineProperty(BeachDayReporter.prototype, "currentEnvironment", { set: function (env) { this._currentEnvironment = env; //consoleOrig.log("----------> Setting current environment: ", env); }, enumerable: true, configurable: true }); Object.defineProperty(BeachDayReporter.prototype, "currentSpecId", { get: function () { return this.currentSpec ? this.currentSpec.id + '' : null; }, enumerable: true, configurable: true }); BeachDayReporter.prototype.overrideSpecMaxTestTime = function (value) { if (this.currentSpec) { this.currentSpec.overrideMaxTestTime = value; } else { throw new Error('You cannot call overrideSpecMaxTestTime() outside of a spec execution.'); } }; BeachDayReporter.prototype.wrap = function (cb) { try { cb(); } catch (e) { exports.consoleOrig.log(e.stack); } }; /* Jasmine methods -------------------------------------------- */ BeachDayReporter.prototype.jasmineStarted = function (suiteInfo) { var _this = this; this.wrap(function () { // Register all the matchers that are used in this framework matchers_1.Matchers.registerMatchers(); _this.dataStore = { suiteInfo: suiteInfo, isSpec: false, id: (new Date()).getTime() + '', level: 0 }; _this.initIViewData(_this.dataStore); }); }; BeachDayReporter.prototype.suiteStarted = function (result) { var _this = this; this.wrap(function () { result.startTime = new Date(); // Assign the parent and child if (_this.currentSuite) { result.parent = _this.currentSuite; _this.currentSuite.viewChildren.push(result); } else { result.parent = null; _this.dataStore.viewChildren.push(result); } // Set the current suite for the end or for the next suite _this.currentSuite = result; _this.initIViewData(_this.currentSuite); }); }; BeachDayReporter.prototype.suiteDone = function (result) { var _this = this; this.wrap(function () { // Reassign back to the parent, may be null _this.currentSuite = result.parent; result.endTime = new Date(); }); }; BeachDayReporter.prototype.specStarted = function (result) { var _this = this; this.wrap(function () { result.startTime = new Date(); result.isSpec = true; _this.currentSpec = result; _this.initIViewData(_this.currentSpec); _this.currentSuite.viewChildren.push(result); reporterConsole.currentSpec = _this.currentSpec; }); }; BeachDayReporter.prototype.specDone = function (result) { var _this = this; this.wrap(function () { result.endTime = new Date(); // Clone the status so we can edit it without interfering with other reporters result.beachStatus = result['status']; // Las the disabled status and simply treat as skipped if (result.beachStatus == BeachDayReporter.STATUS_DISABLED) { result.beachStatus = BeachDayReporter.STATUS_SKIPPED; } if (_this._currentEnvironment) { // If the environment is already failed, then set the status to not run if (_this._currentEnvironment.failed === true) { if (result.beachStatus != BeachDayReporter.STATUS_SKIPPED) { result.beachStatus = BeachDayReporter.STATUS_NOT_RUN; } } else { // If the test failed, fail the entire environment if (result.beachStatus == BeachDayReporter.STATUS_FAILED) { _this._currentEnvironment.setFailed(); } } } // Clear out the current environment // as this should be set by every env.wrap execution _this.currentEnvironment = null; _this.currentSpec = null; }); }; BeachDayReporter.prototype.jasmineDone = function (result) { var _this = this; this.wrap(function () { // Strip the parents in the specs _this.recurseSuitesPopulateViewData(_this.dataStore); if (_this.config.maxTestTime) { _this.dataStore.maxTestTime = _this.formatDuration(_this.config.maxTestTime); } // Generate HTML report report_generator_1.generate(_this.dataStore, _this.config); // Generate JSON from view data if (!fs.existsSync(_this.config.viewDataDir)) { fs.mkdirSync(_this.config.viewDataDir); } fs.writeFileSync(_this.config.viewDataPath, JSON.stringify(_this.dataStore, null, 4), { encoding: 'utf8' }); }); }; /* UTIL FUNCTIONS -------------------------------------------- */ BeachDayReporter.prototype.recurseSuitesPopulateViewData = function (data, level) { var _this = this; if (level === void 0) { level = 1; } data.viewChildren.forEach(function (specOrSuite) { specOrSuite.level = level; if (!specOrSuite.isSpec) { delete specOrSuite.parent; _this.recurseSuitesPopulateViewData(specOrSuite, level + 1); _this.prettyProps(specOrSuite); } else { // Build up data var spec = specOrSuite; spec.durationMilli = spec.endTime.getTime() - spec.startTime.getTime(); spec.skippedCount = spec.beachStatus == BeachDayReporter.STATUS_SKIPPED ? 1 : 0; spec.failedCount = spec.beachStatus == BeachDayReporter.STATUS_FAILED ? 1 : 0; spec.notRunCount = spec.beachStatus == BeachDayReporter.STATUS_NOT_RUN ? 1 : 0; spec.passedCount = spec.beachStatus == BeachDayReporter.STATUS_PASSED ? 1 : 0; spec.allPassed = spec.passedCount == 1; var maxTestTime = null; if (spec.overrideMaxTestTime != null) { maxTestTime = spec.overrideMaxTestTime; } else if (_this.config.maxTestTime != null) { maxTestTime = _this.config.maxTestTime; } if (spec.durationMilli > maxTestTime) { spec.durationWarning = true; } _this.prettyProps(specOrSuite); // Add log debug data var topLogs = []; var addHeader = function (name) { if (topLogs.length > 0) topLogs.push(''); topLogs.push(name, '<hr />'); }; if (spec.beachStatus == BeachDayReporter.STATUS_PASSED || spec.beachStatus == BeachDayReporter.STATUS_FAILED) { addHeader('Timing:'); topLogs.push(['Run Time:', specOrSuite.durationFormatted]); topLogs.push(['Start Time:', moment(spec.startTime).format('HH:mm:ss.SSS')]); topLogs.push(['End Time:', moment(spec.endTime).format('HH:mm:ss.SSS')]); } // Add implementation errors if there are any addHeader('Results:'); if (spec.beachStatus == 'failed') { // Set the spec so the logs go to the right place for (var i = 0; i < spec["failedExpectations"].length; i++) { var expect = spec['failedExpectations'][i]; // If we find any failed expectations without a matcher name it means a runtime error if (expect.matcherName == '' || expect.matcherName == null) { spec.implementationErrorCount = 1; topLogs.push('[TEST IMPLEMENTATION ERROR] - ' + expect.stack); } // Specific matcher that throws implementation errors else if (expect.matcherName == 'throwImplementationError') { spec.implementationErrorCount = 1; topLogs.push('[TEST IMPLEMENTATION ERROR] - ' + expect.message); } else { topLogs.push('[ERROR] - ' + expect.message); } } } else if (spec.beachStatus == 'notRun') { topLogs.push('Test was not run due to failing tests before it'); } else if (spec.beachStatus == 'pending') { topLogs.push('Test was skipped by the developer'); } else if (spec.beachStatus == 'passed') { topLogs.push('All passed'); } if (spec.debugData.length > 0) { addHeader('Debug Data:'); } spec.debugData = topLogs.concat(spec.debugData); _this.stringLogs(spec); } // Increment and format values as all children have been processed _this.incrementIViewData(specOrSuite, data); }); this.prettyProps(data); }; BeachDayReporter.prototype.prettyProps = function (data) { data.durationFormatted = this.formatDuration(data.durationMilli); if (data.isSpec) { var spec = data; if (spec.overrideMaxTestTime != null) { data.durationFormatted += " (Test max: " + this.formatDuration(spec.overrideMaxTestTime) + ")"; } } data.startTimeFormatted = moment(data.startTime).format('Do MMMM YYYY, HH:mm:ss'); data.endTimeFormatted = moment(data.endTime).format('Do MMMM YYYY, HH:mm:ss'); }; BeachDayReporter.prototype.formatDuration = function (durationMilli) { var seconds = Math.floor(durationMilli / (1000)); var mili = durationMilli - seconds * 1000; var miliStr = _.padStart(mili + '', 3, '0'); var secondsStr = seconds + ''; if (seconds > 60) { var mins = Math.floor(seconds / 60); seconds = seconds - mins * 60; secondsStr = mins + 'm ' + secondsStr; } return secondsStr + '.' + miliStr + 's'; }; BeachDayReporter.prototype.initIViewData = function (value) { if (value) { if (!value.hasOwnProperty('isSpec')) { value.isSpec = false; } if (value.viewChildren == null) value.viewChildren = []; if (value.durationMilli == null) value.durationMilli = 0; if (value.failedCount == null) value.failedCount = 0; if (value.skippedCount == null) value.skippedCount = 0; if (value.notRunCount == null) value.notRunCount = 0; if (value.passedCount == null) value.passedCount = 0; if (value.implementationErrorCount == null) value.implementationErrorCount = 0; if (value.debugData == null) value.debugData = []; if (!value.hasOwnProperty('allPassed')) value.allPassed = true; if (!value.hasOwnProperty('durationWarning')) value.durationWarning = false; value.colorClass = function () { if (this.isSpec && this.failedCount == 0 && this.notRunCount == 0 && this.skippedCount == 0) { return 'green'; } else if (!this.isSpec && this.passedCount > 0 && this.failedCount == 0 && this.notRunCount == 0) { return 'green'; } else if (this.failedCount > 0) { return 'red'; } else { return 'muted'; } }; value.iconClass = function () { if (this.isSpec && this.failedCount == 0 && this.notRunCount == 0 && this.skippedCount == 0) { return 'glyphicon-ok green'; } else if (!this.isSpec && this.passedCount > 0 && this.failedCount == 0 && this.notRunCount == 0) { return 'glyphicon-ok green'; } else if (this.failedCount > 0) { return 'glyphicon-remove red'; } else { return 'glyphicon-question-sign muted'; } }; } return value; }; BeachDayReporter.prototype.incrementIViewData = function (source, dest) { if (dest && source) { dest.startTime = this.getDate(dest.startTime, source.startTime, true); dest.endTime = this.getDate(dest.endTime, source.endTime, false); dest.durationMilli += source.durationMilli; dest.failedCount += source.failedCount; dest.skippedCount += source.skippedCount; dest.notRunCount += source.notRunCount; dest.passedCount += source.passedCount; dest.implementationErrorCount += source.implementationErrorCount; if (!source.allPassed) { dest.allPassed = false; } if (source.durationWarning) { dest.durationWarning = true; } } }; BeachDayReporter.prototype.getDate = function (date1, date2, earliest) { if (date1 == null && date2 != null) { return date2; } else if (date1 != null && date2 == null) { return date1; } else { var d1Compare = earliest ? date1.getTime() < date2.getTime() : date1.getTime() > date2.getTime(); if (d1Compare) { return date1; } else { return date2; } } }; BeachDayReporter.prototype.stringLogs = function (data) { for (var i = 0; i < data.debugData.length; i++) { if (data.debugData[i] instanceof Array) { var args = data.debugData[i]; data.debugData[i] = stringLogArguments(args); } } }; BeachDayReporter.STATUS_PASSED = 'passed'; BeachDayReporter.STATUS_FAILED = 'failed'; BeachDayReporter.STATUS_SKIPPED = 'pending'; BeachDayReporter.STATUS_DISABLED = 'disabled'; BeachDayReporter.STATUS_NOT_RUN = 'notRun'; return BeachDayReporter; }()); exports.BeachDayReporter = BeachDayReporter; //# sourceMappingURL=beach-day-reporter.js.map