cypress-terminal-report
Version:
Better terminal and file output for cypress test logs.
181 lines (179 loc) • 6.28 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TYPE_PADDING = void 0;
exports.escapeHtml = escapeHtml;
exports.formatMessage = formatMessage;
const BaseOutputProcessor_1 = __importDefault(require("./BaseOutputProcessor"));
const constants_1 = __importDefault(require("../constants"));
const utils_1 = __importDefault(require("../utils"));
const { COLORS: BASE_COLORS, LOG_TYPES, LOG_SYMBOLS } = constants_1.default;
exports.TYPE_PADDING = Math.max(...Object.values(LOG_TYPES).map((l) => l.length)) + 3;
const COLORS = {
...BASE_COLORS,
DARK_CYAN: 'darkcyan',
LIGHT_GREY: 'lightgrey',
};
const getMessageConfigMap = (options) => ({
[LOG_TYPES.PLUGIN_LOG_TYPE]: {
typeColor: COLORS.DARK_CYAN,
icon: LOG_SYMBOLS.INFO,
},
[LOG_TYPES.BROWSER_CONSOLE_WARN]: {
typeColor: COLORS.YELLOW,
icon: LOG_SYMBOLS.WARNING,
},
[LOG_TYPES.BROWSER_CONSOLE_ERROR]: {
typeColor: COLORS.RED,
icon: LOG_SYMBOLS.ERROR,
},
[LOG_TYPES.BROWSER_CONSOLE_DEBUG]: {
typeColor: COLORS.BLUE,
icon: LOG_SYMBOLS.DEBUG,
},
[LOG_TYPES.BROWSER_CONSOLE_INFO]: {
typeColor: COLORS.DARK_CYAN,
icon: LOG_SYMBOLS.INFO,
},
[LOG_TYPES.BROWSER_CONSOLE_LOG]: {
typeColor: COLORS.DARK_CYAN,
icon: LOG_SYMBOLS.INFO,
},
[LOG_TYPES.CYPRESS_LOG]: {
typeColor: COLORS.DARK_CYAN,
icon: LOG_SYMBOLS.INFO,
},
[LOG_TYPES.CYPRESS_XHR]: {
typeColor: COLORS.LIGHT_GREY,
icon: LOG_SYMBOLS.ROUTE,
messageColor: COLORS.LIGHT_GREY,
trim: options.routeTrimLength,
},
[LOG_TYPES.CYPRESS_FETCH]: {
typeColor: COLORS.GREEN,
icon: LOG_SYMBOLS.ROUTE,
trim: options.routeTrimLength,
messageColor: COLORS.GREY,
},
[LOG_TYPES.CYPRESS_INTERCEPT]: {
typeColor: COLORS.GREEN,
icon: LOG_SYMBOLS.ROUTE,
messageColor: COLORS.GREY,
trim: options.routeTrimLength,
},
[LOG_TYPES.CYPRESS_REQUEST]: {
typeColor: COLORS.GREEN,
icon: LOG_SYMBOLS.SUCCESS,
messageColor: COLORS.GREY,
trim: options.routeTrimLength,
},
[LOG_TYPES.CYPRESS_COMMAND]: {
typeColor: COLORS.GREEN,
icon: LOG_SYMBOLS.SUCCESS,
},
});
// https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript
function escapeHtml(html) {
return html
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/**
* Format an individual Cypress message for HTML logging:
* - Convert cy.log markup syntax to HTML (bold/italic).
* - Color message depending on message type and severity.
* - Trim long messages.
* - Apply proper spacing, newlines, and HTML syntax.
*/
function formatMessage({ type, message, severity }, options) {
let { typeColor, icon, messageColor, trim = options.defaultTrimLength, } = getMessageConfigMap(options)[type];
let processedMessage = message;
if (severity === 'error') {
typeColor = COLORS.RED;
icon = LOG_SYMBOLS.ERROR;
}
else if (severity === 'warning') {
typeColor = COLORS.YELLOW;
icon = LOG_SYMBOLS.WARNING;
}
const maybeTrimLength = (msg) => trim && msg.length > trim
? msg.substring(0, trim) + ' ...\n\n... remainder of log omitted ...\n'
: msg;
const processMessage = (msg) => escapeHtml(maybeTrimLength(msg));
if (type == 'cy:log') {
processedMessage = utils_1.default.applyMessageMarkdown(processedMessage, {
bold: (str) => `<b>${str}</b>`,
italic: (str) => `<i>${str}</i>`,
processContents: processMessage,
});
}
else {
processedMessage = processMessage(processedMessage);
}
// If the message is multilined, align non-first lines with the "message column" that's
// to the right of the "type column"
processedMessage = processedMessage
.split('\n')
.map((line, index) => (index === 0 ? line : `${' '.repeat(exports.TYPE_PADDING + 1)}${line}`))
.join('\n');
processedMessage = `<pre${messageColor ? ` style="color:${messageColor};"` : ''}>${processedMessage}</pre>`;
const typeString = `<pre style="color:${typeColor};">${
// pad to make "type column" spacing even
`${type}${icon}`.padStart(exports.TYPE_PADDING, ' ')}</pre>`;
return `<p>${typeString} ${processedMessage}</p>\n`;
}
class HtmlOutputProcessor extends BaseOutputProcessor_1.default {
constructor(file, options,
/**
* Style CSS, gets placed within the `<style>` tag of the html document.
*
* @default
body { font-family: monospace; }
p { margin: 0; padding: 0; }
pre { display: inline; margin: 0; }
h2 { margin: 0; font-size: 1.2em; }
*/
style = `body { font-family: monospace; }
p { margin: 0; padding: 0; }
pre { display: inline; margin: 0; }
h2 { margin: 0; font-size: 1.2em; }`) {
super(file, options);
this.file = file;
this.options = options;
this.style = style;
this.closingContent = `
</body>
</html>`;
this.beforeClosingContentPos = -this.closingContent.length;
this.initialContent = `<html>
<head>
<style>
${style
.split('\n')
.map((line) => ' ' + line.trim())
.join('\n')}
</style>
</head>
<body>
${this.closingContent}`;
}
write(allMessages) {
for (const [spec, tests] of Object.entries(allMessages)) {
let html = `\n<h1>${escapeHtml(spec)}</h1>\n`;
for (const [test, messages] of Object.entries(tests)) {
html += `<h2>${escapeHtml(test)}</h2>\n`;
for (const message of messages) {
html += formatMessage(message, this.options);
}
html += '<br>\n';
}
this.writeSpecChunk(spec, html, this.beforeClosingContentPos);
}
}
}
exports.default = HtmlOutputProcessor;