UNPKG

@chtsinc/ch-playwright-report

Version:

Custom Playwright Reporter that logs test and execution results to MongoDB

261 lines (259 loc) 10.1 kB
"use strict"; 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