cypress-terminal-report
Version:
Better terminal and file output for cypress test logs.
194 lines (193 loc) • 8.87 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const path_1 = __importDefault(require("path"));
const CtrError_1 = __importDefault(require("./CtrError"));
const constants_1 = __importDefault(require("./constants"));
const CustomOutputProcessor_1 = __importDefault(require("./outputProcessor/CustomOutputProcessor"));
const NestedOutputProcessorDecorator_1 = __importDefault(require("./outputProcessor/NestedOutputProcessorDecorator"));
const JsonOutputProcessor_1 = __importDefault(require("./outputProcessor/JsonOutputProcessor"));
const TextOutputProcessor_1 = __importDefault(require("./outputProcessor/TextOutputProcessor"));
const utils_1 = __importDefault(require("./utils"));
const consoleProcessor_1 = __importDefault(require("./outputProcessor/consoleProcessor"));
const superstruct_1 = require("superstruct");
const installLogsPrinter_schema_1 = require("./installLogsPrinter.schema");
const HtmlOutputProcessor_1 = __importDefault(require("./outputProcessor/HtmlOutputProcessor"));
const OUTPUT_PROCESSOR_TYPE = {
json: JsonOutputProcessor_1.default,
txt: TextOutputProcessor_1.default,
html: HtmlOutputProcessor_1.default,
};
let writeToFileMessages = {};
let outputProcessors = [];
const createLogger = (enabled) => enabled
? (message) => console.log(`[cypress-terminal-report:debug] ${message}`)
: () => { };
/**
* Installs the cypress plugin for printing logs to terminal.
*
* Needs to be added to plugins file.
*
* @see ./installLogsPrinter.d.ts
* @type {import('./installLogsPrinter')}
*/
function installLogsPrinter(on, options = {}) {
const resolvedOptions = {
printLogsToFile: 'onFail',
printLogsToConsole: 'onFail',
routeTrimLength: 5000,
defaultTrimLength: 800,
commandTrimLength: 800,
outputVerbose: true,
...options,
};
const { printLogsToFile, printLogsToConsole, outputCompactLogs, outputTarget, logToFilesOnAfterRun, includeSuccessfulHookLogs, compactLogs, collectTestLogs, } = resolvedOptions;
const [error] = (0, superstruct_1.validate)(resolvedOptions, installLogsPrinter_schema_1.InstallLogsPrinterSchema);
if (error) {
throw new CtrError_1.default(`Invalid plugin install options: ${utils_1.default.validatorErrToStr(error.failures())}`);
}
const logDebug = createLogger(resolvedOptions.debug);
on('task', {
[constants_1.default.TASK_NAME]: function (data) {
logDebug(`${constants_1.default.TASK_NAME}: Received ${data.messages.length} messages, for ${data.spec}:${data.test}, with state ${data.state}.`);
let messages = data.messages;
const terminalMessages = typeof compactLogs === 'number' && compactLogs >= 0
? getCompactLogs(messages, compactLogs, logDebug)
: messages;
const isHookAndShouldLog = data.isHook && (includeSuccessfulHookLogs || data.state === 'failed');
if (outputTarget && printLogsToFile !== 'never') {
if (data.state === 'failed' || printLogsToFile === 'always' || isHookAndShouldLog) {
let outputFileMessages = typeof outputCompactLogs === 'number'
? getCompactLogs(messages, outputCompactLogs, logDebug)
: outputCompactLogs === false
? messages
: terminalMessages;
logDebug(`Storing for file logging ${outputFileMessages.length} messages, for ${data.spec}:${data.test}.`);
writeToFileMessages[data.spec] = writeToFileMessages[data.spec] || {};
writeToFileMessages[data.spec][data.test] = outputFileMessages;
}
}
if (printLogsToConsole !== 'never' &&
(printLogsToConsole === 'always' ||
(printLogsToConsole === 'onFail' && data.state !== 'passed') ||
isHookAndShouldLog)) {
logDebug(`Logging to console ${terminalMessages.length} messages, for ${data.spec}:${data.test}.`);
(0, consoleProcessor_1.default)(terminalMessages, resolvedOptions, data);
}
if (collectTestLogs) {
logDebug(`Running \`collectTestLogs\` on ${terminalMessages.length} messages, for ${data.spec}:${data.test}.`);
collectTestLogs({ spec: data.spec, test: data.test, state: data.state }, terminalMessages);
}
return null;
},
[constants_1.default.TASK_NAME_OUTPUT]: () => {
logDebug(`${constants_1.default.TASK_NAME_OUTPUT}: Triggered.`);
logToFiles(resolvedOptions);
return null;
},
});
installOutputProcessors(on, resolvedOptions);
if (logToFilesOnAfterRun) {
on('after:run', () => {
logDebug(`after:run: Attempting file logging on after run.`);
logToFiles(resolvedOptions);
});
}
}
function logToFiles(options) {
outputProcessors.forEach((processor) => {
if (Object.entries(writeToFileMessages).length !== 0) {
processor.write(writeToFileMessages);
if (options.outputVerbose !== false)
logOutputTarget(processor);
}
});
writeToFileMessages = {};
}
function logOutputTarget(processor) {
let message;
let standardOutputType = Object.keys(OUTPUT_PROCESSOR_TYPE).find((type) => processor instanceof OUTPUT_PROCESSOR_TYPE[type]);
if (standardOutputType) {
message = `Wrote ${standardOutputType} logs to ${processor.getTarget()}. (${processor.getSpentTime()}ms)`;
}
else {
message = `Wrote custom logs to ${processor.getTarget()}. (${processor.getSpentTime()}ms)`;
}
console.log('cypress-terminal-report:', message);
}
function installOutputProcessors(on, options) {
if (!options.outputTarget) {
return;
}
if (!options.outputRoot) {
throw new CtrError_1.default(`Missing outputRoot configuration.`);
}
const createProcessorFromType = (file, type, options) => {
const filepath = path_1.default.join(options.outputRoot || '', file);
if (typeof type === 'string') {
return new OUTPUT_PROCESSOR_TYPE[type](filepath, options);
}
if (typeof type === 'function') {
return new CustomOutputProcessor_1.default(filepath, options, type);
}
throw new Error('Unexpected type case.');
};
Object.entries(options.outputTarget).forEach(([file, type]) => {
const requiresNested = file.match(/^[^|]+\|.*$/);
if (typeof type === 'string' && !OUTPUT_PROCESSOR_TYPE[type]) {
throw new CtrError_1.default(`Unknown output format '${type}'.`);
}
if (!['function', 'string'].includes(typeof type)) {
throw new CtrError_1.default(`Output target type can only be string or function.`);
}
if (requiresNested) {
const parts = file.split('|');
const root = parts[0];
const ext = parts[1];
outputProcessors.push(new NestedOutputProcessorDecorator_1.default(root, options.specRoot || '', ext, (nestedFile) => createProcessorFromType(nestedFile, type, options)));
}
else {
outputProcessors.push(createProcessorFromType(file, type, options));
}
});
outputProcessors.forEach((processor) => processor.initialize());
}
function getCompactLogs(logs, keepAroundCount, logDebug) {
logDebug(`Compacting ${logs.length} logs.`);
const failingIndexes = logs
.filter((log) => log.severity === constants_1.default.SEVERITY.ERROR)
.map((log) => logs.indexOf(log));
const includeIndexes = new Array(logs.length);
failingIndexes.forEach((index) => {
const from = Math.max(0, index - keepAroundCount);
const to = Math.min(logs.length - 1, index + keepAroundCount);
for (let i = from; i <= to; i++) {
includeIndexes[i] = 1;
}
});
const compactedLogs = [];
const addOmittedLog = (count) => compactedLogs.push({
type: constants_1.default.LOG_TYPES.PLUGIN_LOG_TYPE,
message: `[ ... ${count} omitted logs ... ]`,
severity: constants_1.default.SEVERITY.SUCCESS,
});
let excludeCount = 0;
for (let i = 0; i < includeIndexes.length; i++) {
if (includeIndexes[i]) {
if (excludeCount) {
addOmittedLog(excludeCount);
excludeCount = 0;
}
compactedLogs.push(logs[i]);
}
else {
++excludeCount;
}
}
if (excludeCount) {
addOmittedLog(excludeCount);
}
return compactedLogs;
}
module.exports = installLogsPrinter;