UNPKG

playwright-archive

Version:

A lightweight CLI tool to archive and serve your Playwright test run history with a web interface. Useful for CI environments and local development.

274 lines (221 loc) 9.26 kB
const fs = require('fs-extra'); const path = require('path'); class RunResultsGenerator { constructor(data, config) { this.data = data || { config: {}, stats: {}, suites: [] }; this.config = config?.runResults || {}; } async generate() { if (!this.config.enabled) { return; } try { const outputPath = path.resolve(process.cwd(), this.config.path || './playwright-results.txt'); const format = this.config.format || 'text'; const include = this.config.include || ['status', 'summary']; const content = format === 'json' ? this.generateJson(include) : this.generateText(include); await fs.outputFile(outputPath, content); console.log(`✅ Run results saved to ${outputPath}`); } catch (error) { console.error("Failed to generate run results:", error); // Don't throw, just log the error to prevent crashing the process } } generateJson(include) { const result = {}; if (include.includes('status')) { result.status = this.getOverallStatus(); } if (include.includes('summary')) { result.summary = this.getSummary(); } if (include.includes('duration')) { result.duration = this.formatDuration(this.data.stats?.duration); } if (include.includes('failedTests')) { result.failedTests = this.getFailedTests(); } if (include.includes('flakyTests')) { result.flakyTests = this.getFlakyTests(); } if (include.includes('projectStats')) { result.projectStats = this.getProjectStats(); } if (include.includes('errorMessages')) { result.errorMessages = this.getErrorMessages(); } return JSON.stringify(result, null, 2); } generateText(include) { if (this.config.template) { return this.generateFromTemplate(); } const sections = []; sections.push('Test Run Results'); sections.push('---------------'); if (include.includes('status')) { sections.push(`Status: ${this.getOverallStatus()}`); } if (include.includes('duration')) { sections.push(`Duration: ${this.formatDuration(this.data.stats?.duration)}`); } if (include.includes('summary')) { const summary = this.getSummary(); sections.push('\nSummary:'); sections.push(`* Total: ${summary.total}`); sections.push(`* Passed: ${summary.passed}`); sections.push(`* Failed: ${summary.failed}`); sections.push(`* Skipped: ${summary.skipped}`); if (summary.flaky > 0) { sections.push(`* Flaky: ${summary.flaky}`); } } if (include.includes('failedTests')) { const failedTests = this.getFailedTests(); if (failedTests.length > 0) { sections.push('\nFailed Tests:'); failedTests.forEach(test => { sections.push(`* ${test}`); }); } } if (include.includes('flakyTests')) { const flakyTests = this.getFlakyTests(); if (flakyTests.length > 0) { sections.push('\nFlaky Tests:'); flakyTests.forEach(test => { sections.push(`* ${test}`); }); } } if (include.includes('projectStats')) { const projectStats = this.getProjectStats(); if (Object.keys(projectStats).length > 0) { sections.push('\nProject Statistics:'); Object.entries(projectStats).forEach(([project, stats]) => { sections.push(`\n${project}:`); sections.push(`* Passed: ${stats.passed}`); sections.push(`* Failed: ${stats.failed}`); sections.push(`* Skipped: ${stats.skipped}`); }); } } if (include.includes('errorMessages')) { const errors = this.getErrorMessages(); if (errors.length > 0) { sections.push('\nError Messages:'); errors.forEach((error, index) => { sections.push(`\n${index + 1}) ${error}`); }); } } return sections.join('\n'); } generateFromTemplate() { let content = this.config.template; const summary = this.getSummary(); // Replace template variables const variables = { status: this.getOverallStatus(), duration: this.formatDuration(this.data.stats?.duration), totalTests: summary.total, passedTests: summary.passed, failedTests: summary.failed, skippedTests: summary.skipped, flakyTests: summary.flaky, hasFailedTests: summary.failed > 0, failedTestsList: this.getFailedTests().map(test => `* ${test}`).join('\n'), errorMessages: this.getErrorMessages().map((err, i) => `${i + 1}) ${err}`).join('\n'), }; // Handle conditional blocks {condition?content:} in template content = content.replace(/\{(\w+)\?([\s\S]*?)\:}/g, (match, condition, text) => { return variables[condition] ? text : ''; }); // Replace variables Object.entries(variables).forEach(([key, value]) => { content = content.replace(new RegExp(`\\{${key}\\}`, 'g'), value); }); return content; } getOverallStatus() { return (this.data?.stats?.unexpected || 0) > 0 ? 'Failed' : 'Passed'; } getSummary() { const stats = this.data?.stats || {}; return { total: (stats.expected || 0) + (stats.unexpected || 0) + (stats.skipped || 0), passed: stats.expected || 0, failed: stats.unexpected || 0, skipped: stats.skipped || 0, flaky: stats.flaky || 0 }; } getFailedTests() { if (!Array.isArray(this.data?.suites)) return []; return this.data.suites .filter(suite => suite && Array.isArray(suite.specs) && suite.specs.some(spec => spec && !spec.ok)) .map(suite => suite.title || "Unknown Test") .filter(Boolean); } getFlakyTests() { if (!Array.isArray(this.data?.suites)) return []; return this.data.suites .filter(suite => suite && Array.isArray(suite.specs) && suite.specs.some(spec => spec && spec.flaky)) .map(suite => suite.title || "Unknown Test") .filter(Boolean); } getProjectStats() { const stats = {}; const projects = this.data?.config?.projects; if (!Array.isArray(projects)) return stats; projects.forEach(project => { if (!project || !Array.isArray(project.suites)) return; const projectStats = { passed: 0, failed: 0, skipped: 0 }; project.suites.forEach(suite => { if (!suite || !Array.isArray(suite.specs)) return; suite.specs.forEach(spec => { if (!spec) return; if (!spec.ok) projectStats.failed++; else if (spec.skipped) projectStats.skipped++; else projectStats.passed++; }); }); if (projectStats.passed + projectStats.failed + projectStats.skipped > 0) { stats[project.name || "Unknown Project"] = projectStats; } }); return stats; } getErrorMessages() { if (!Array.isArray(this.data?.suites)) return []; const errors = []; this.data.suites.forEach(suite => { if (!suite || !Array.isArray(suite.specs)) return; suite.specs.forEach(spec => { if (spec && !spec.ok && spec.error?.message) { const title = suite.title || "Unknown Test"; errors.push(`${title}: ${spec.error.message}`); } }); }); return errors; } formatDuration(ms) { if (!ms) return '0s'; const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const parts = []; if (hours > 0) parts.push(`${hours}h`); if (minutes % 60 > 0) parts.push(`${minutes % 60}m`); if (seconds % 60 > 0) parts.push(`${seconds % 60}s`); return parts.join(' ') || '0s'; } } module.exports = RunResultsGenerator;