UNPKG

cypress-terminal-report

Version:

Better terminal and file output for cypress test logs.

194 lines (193 loc) 8.87 kB
"use strict"; 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;