@chtsinc/ch-playwright-report
Version:
Custom Playwright Reporter that logs test and execution results to MongoDB
261 lines (259 loc) • 10.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailUtil = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const mime_types_1 = __importDefault(require("mime-types"));
const date_fns_tz_1 = require("date-fns-tz");
const ch_logger_1 = require("@chtsinc/ch-logger");
const ch_emailer_1 = require("@chtsinc/ch-emailer");
const logger = (0, ch_logger_1.getLogger)();
class EmailUtil {
static async sendEmail(serviceConfig, testStatusSummary, executionReport, reportUrl, filePaths) {
try {
const config = {
mailSmtpAuthEnable: String(serviceConfig.isMailSMTPAuthEnabled ?? ""),
mailSmtpStarttlsEnable: String(serviceConfig.isMailSMTPStartTLSEnabled ?? ""),
mailSmtpSslEnable: String(serviceConfig.isMailSMTPSSLEnabled ?? ""),
sessionDebugEnable: String(serviceConfig.isSessionDebugEnabled ?? ""),
mailSmtpHost: String(serviceConfig.mailSMTPHost ?? ""),
mailSmtpPort: String(serviceConfig.mailSMTPPort ?? ""),
fromAddress: String(serviceConfig.fromAddress ?? ""),
toRecipients: (serviceConfig.toRecipients ?? []).join(","),
user: String(serviceConfig.user ?? ""),
password: String(serviceConfig.password ?? ""),
};
const smtpEmailService = new ch_emailer_1.SMTPEmailService(config);
const emailSubject = this.emailSubjectWithCurrentDate(executionReport);
const emailBody = this.emailBody(executionReport, testStatusSummary, reportUrl);
let emailOpts = {
subject: emailSubject ?? "",
body: emailBody ?? "",
};
const attachments = await EmailUtil.attachmentsFromPaths(filePaths);
if (attachments && attachments.length > 0) {
emailOpts.attachments = attachments;
}
await smtpEmailService.sendEmail(emailOpts);
}
catch (error) {
logger.error("CHPlaywrightReporter: Email Service failed", error);
throw error;
}
}
static emailSubjectWithCurrentDate(report) {
if (report?.environmentInfo) {
const env = report.environmentInfo;
const currentDate = EmailUtil.formatCurrentDateToTimezone(report.timezone);
return EmailUtil.composeEmailSubject(report.applicationName, report.project, env.suite, report.site, env.environment, env.os, env.browserName, currentDate);
}
return null;
}
static formatCurrentDateToTimezone(timezone, pattern) {
try {
const date = new Date();
const zone = timezone || "America/New_York";
const fmt = pattern || "MMM dd yyyy hh:mm:ss a zzz";
return (0, date_fns_tz_1.format)(date, fmt, { timeZone: zone });
}
catch (err) {
logger.debug("Invalid timezone or pattern", err);
return "";
}
}
static composeEmailSubject(applicationName, _project, suiteName, _siteName, environment, os, browserName, suffix) {
const subjectParts = [
"Test Execution Report -",
applicationName,
environment,
suiteName,
os,
browserName,
suffix,
];
return subjectParts.filter(Boolean).join(" | ");
}
static emailBody(report, testStatusSummary, reportUrl) {
if (!report?.environmentInfo)
return null;
const { passed, failed, skipped, other } = testStatusSummary.getSummary();
const total = (passed ?? 0) + (failed ?? 0) + (skipped ?? 0) + (other ?? 0);
const status = EmailUtil.testStatusDistribution(total ?? 0, passed ?? 0, failed ?? 0, skipped ?? 0, other ?? 0);
return EmailUtil.composeEmailBody(report, status, reportUrl);
}
static composeEmailBody(report, statusMap, reportUrl) {
const rows = [];
const appendRow = (label, value) => {
if ((typeof value === "number" && !isNaN(value)) || value) {
rows.push(`<tr>
<th style="color: #2e6c80;">${label}</th>
<td class="${label.toLowerCase()}">${value}</td>
</tr>`);
}
};
appendRow("Execution ID", report.executionId);
appendRow("Application Name", report.applicationName);
appendRow("Site Name", report.site);
appendRow("Environment", report.environmentInfo.environment);
appendRow("Suite", report.environmentInfo.suite);
appendRow("Browser", report.environmentInfo.browserName);
appendRow("Device", report.environmentInfo.deviceName);
appendRow("Device Orientation", report.environmentInfo.deviceOrientation);
Object.entries(statusMap).forEach(([key, value]) => appendRow(key, value));
appendRow("Start Time", report.executionStartTime);
appendRow("End Time", report.executionEndTime);
const reportUrlTr = reportUrl
? `<tr>
<th>Report Link</th>
<td><a href='${reportUrl}' target="_blank">View Full Report</a></td>
</tr>`
: "";
const htmlBody = `<!DOCTYPE html>
<html>${EmailUtil.getHeadTag()}<body>
<div class="container">${EmailUtil.getTopContainerTag()}<table class="summary-table">
${rows.join("")}${reportUrlTr}</table>
<p>Regards,<br/>Test Automation System</p>
${EmailUtil.getFooterTag(reportUrlTr)}
</div>
</body>
</html>
`;
return htmlBody;
}
static testStatusDistribution(total, pass, fail, skip, other) {
return {
"Total Tests": total,
Pass: pass,
Fail: fail,
Skip: skip,
Other: other,
};
}
static capitalize(word) {
if (!word)
return undefined;
return word.charAt(0).toUpperCase() + word.slice(1);
}
static getHeadTag() {
return `<head>
<meta charset="UTF-8">
<title>Test Execution Report</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 20px;
}
.container {
background-color: #fff;
border-radius: 6px;
padding: 20px;
max-width: 700px;
margin: auto;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.header {
background-color: #0078d7;
color: #fff;
padding: 10px 20px;
border-radius: 6px 6px 0 0;
}
.summary-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.summary-table th, .summary-table td {
border: 1px solid #ccc;
padding: 12px;
text-align: left;
}
.summary-table th {
background-color: #f0f0f0;
}
.pass { color: green; font-weight: bold; }
.fail { color: red; font-weight: bold; }
.skip { color: orange; font-weight: bold; }
.other { color: orange; font-weight: bold; }
.footer {
text-align: center;
margin-top: 30px;
font-size: 12px;
color: #888;
}
</style>
</head>
`;
}
static getTopContainerTag() {
return `<div class="header">
<h2>🔍 Automated Test Execution Report</h2>
</div>
<p>Dear Team,</p>
<p>Please find below the summary of the latest test execution:</p>`;
}
static getFooterTag(reportUrlTr) {
const description = reportUrlTr
? `<div>The report is available for 30 days</div>`
: "";
return `<div class="footer">
${description}
This is an automated email. Please do not reply.</div>`;
}
/**
* Synchronously build an array of AttachmentOption from one or more file paths.
* If no paths are provided, returns an empty array.
*
* @param filePaths - A single file path, an array of file paths, or undefined/null.
* @returns AttachmentOption[]
*/
static async attachmentsFromPaths(filePaths) {
const paths = [].concat(filePaths || []);
const readPromises = paths.map(async (filePath) => ({
filename: path.basename(filePath),
content: await fs.promises.readFile(filePath),
contentType: mime_types_1.default.lookup(filePath) || undefined,
}));
return Promise.all(readPromises);
}
}
exports.EmailUtil = EmailUtil;
//# sourceMappingURL=email-util.js.map