import { TestCase, TestResult } from '@playwright/test/reporter'; import { BaseReporter, ReportMetrics } from './BaseReporter'; import fs from 'fs/promises'; import path from 'path'; export class HtmlReporter extends BaseReporter { private outputDir: string; private template: string; constructor(options: { outputDir: string }) { super(); this.outputDir = options.outputDir; this.template = ` Test Report
`; } async onEnd() { const counts = this.getStatusCounts(); const content = []; // Summary section content.push(`

Test Summary

Total Tests: ${counts.passed + counts.failed + counts.skipped}
Passed: ${counts.passed}
Failed: ${counts.failed}
Skipped: ${counts.skipped}

`); // Test cases this.testResults.forEach((results, title) => { results.forEach(result => { const metrics = this.metrics.get(title); const attachments = this.getAttachments(result); content.push(`

${title}

Duration: ${this.formatDuration(metrics?.duration || 0)}
Memory: ${Math.round(metrics?.memory || 0)}MB
Network Requests: ${metrics?.networkRequests || 0}
${result.error ? `
Error:
${result.error.message}
` : ''} ${this.renderAttachments(attachments)}
`); }); }); // Generate the final HTML const html = this.template.replace( '
', `
${content.join('')}
` ); // Ensure output directory exists await fs.mkdir(this.outputDir, { recursive: true }); // Write the report await fs.writeFile( path.join(this.outputDir, 'report.html'), html ); // Copy attachments to the output directory await this.copyAttachments(); } private async copyAttachments() { const attachmentsDir = path.join(this.outputDir, 'attachments'); await fs.mkdir(attachmentsDir, { recursive: true }); for (const results of this.testResults.values()) { for (const result of results) { for (const attachment of result.attachments) { if (attachment.path) { const destPath = path.join(attachmentsDir, path.basename(attachment.path)); await fs.copyFile(attachment.path, destPath); } } } } } private renderAttachments(attachments: ReturnType) { const elements = []; if (attachments.screenshots.length) { elements.push('

Screenshots:

'); attachments.screenshots.forEach(screenshot => { if (screenshot.path) { const filename = path.basename(screenshot.path); elements.push(`
Screenshot
`); } }); elements.push('
'); } if (attachments.videos.length) { elements.push('

Videos:

'); attachments.videos.forEach(video => { if (video.path) { const filename = path.basename(video.path); elements.push(`
`); } }); elements.push('
'); } if (attachments.traces.length) { elements.push('

Traces:

'); attachments.traces.forEach(trace => { if (trace.path) { const filename = path.basename(trace.path); elements.push(` `); } }); elements.push('
'); } return elements.join(''); } }