import { TestCase, TestResult } from '@playwright/test/reporter'; import { BaseReporter, ReportMetrics } from './BaseReporter'; import fs from 'fs/promises'; import path from 'path'; export class MarkdownReporter extends BaseReporter { private outputFile: string; private sections: string[] = []; constructor(options: { outputFile: string }) { super(); this.outputFile = options.outputFile; } async onEnd() { const counts = this.getStatusCounts(); // Test Summary Section this.sections.push(`# Test Execution Report\n ## Summary - **Total Tests:** ${counts.passed + counts.failed + counts.skipped} - **Passed:** ${counts.passed} - **Failed:** ${counts.failed} - **Skipped:** ${counts.skipped} - **Execution Time:** ${this.calculateTotalDuration()} `); // Test Results by Status this.addStatusSection('Passed Tests', 'passed'); this.addStatusSection('Failed Tests', 'failed'); this.addStatusSection('Skipped Tests', 'skipped'); // Performance Metrics this.sections.push(`\n## Performance Metrics\n`); this.metrics.forEach((metrics, title) => { this.sections.push(`### ${title} - Duration: ${this.formatDuration(metrics.duration)} - Memory Usage: ${Math.round(metrics.memory)}MB - Network Requests: ${metrics.networkRequests} `); }); // Attachments Summary this.sections.push(`\n## Test Artifacts\n`); await this.summarizeAttachments(); // Error Details this.sections.push(`\n## Error Details\n`); this.testResults.forEach((results, title) => { results.forEach(result => { if (result.error) { this.sections.push(`### Error in ${title} \`\`\` ${result.error.message} ${result.error.stack || ''} \`\`\` `); } }); }); // Log Integration this.sections.push(`\n## Log Integration\n`); await this.addLogSummary(); // Write the report await fs.mkdir(path.dirname(this.outputFile), { recursive: true }); await fs.writeFile(this.outputFile, this.sections.join('\n')); } private addStatusSection(title: string, status: string) { const tests = Array.from(this.testResults.entries()) .filter(([_, results]) => results.some(r => r.status === status)); if (tests.length > 0) { this.sections.push(`\n## ${title}\n`); tests.forEach(([title, results]) => { results.forEach(result => { if (result.status === status) { const metrics = this.metrics.get(title); this.sections.push(`### ${title} - Duration: ${this.formatDuration(metrics?.duration || 0)} - Status: ${status.toUpperCase()} ${result.error ? `- Error: ${result.error.message}\n` : ''} `); } }); }); } } private calculateTotalDuration(): string { const total = Array.from(this.metrics.values()) .reduce((sum, metrics) => sum + metrics.duration, 0); return this.formatDuration(total); } private async summarizeAttachments() { const summary = { screenshots: 0, videos: 0, traces: 0 }; this.testResults.forEach((results) => { results.forEach(result => { const attachments = this.getAttachments(result); summary.screenshots += attachments.screenshots.length; summary.videos += attachments.videos.length; summary.traces += attachments.traces.length; }); }); this.sections.push(`### Attachment Summary - Screenshots: ${summary.screenshots} - Videos: ${summary.videos} - Traces: ${summary.traces} > Note: All attachments are stored in the \`test-results/attachments\` directory `); } private async addLogSummary() { let errorCount = 0; let warningCount = 0; this.testResults.forEach((results) => { results.forEach(result => { result.attachments .filter(a => a.name === 'log-entries') .forEach(log => { if (log.body) { const entries = JSON.parse(log.body.toString()); errorCount += entries.filter((e: any) => e.level === 'ERROR').length; warningCount += entries.filter((e: any) => e.level === 'WARNING').length; } }); }); }); this.sections.push(`### Log Summary - Total Errors: ${errorCount} - Total Warnings: ${warningCount} > Note: Detailed logs are available in the following locations: > - WordPress Debug Log: \`wp-content/debug.log\` > - PHP Error Log: \`php_errors.log\` > - Nginx Access Log: \`access.log\` > - Nginx Error Log: \`error.log\` `); } }