import { Reporter, TestCase, TestResult, TestStep, TestError } from '@playwright/test/reporter'; import * as fs from 'fs'; import * as path from 'path'; class CustomReporter implements Reporter { private reports: TestReport[] = []; onBegin(config: any, suite: any) { console.log('Starting the test run with', suite.allTests().length, 'tests'); } onTestBegin(test: TestCase) { console.log(`Starting test: ${test.title}`); } onTestEnd(test: TestCase, result: TestResult) { const report: TestReport = { title: test.title, status: result.status, duration: result.duration, error: result.error ? this.formatError(result.error) : undefined, steps: result.steps.map(step => this.formatStep(step)), retries: test.retries, timestamp: new Date().toISOString(), }; this.reports.push(report); this.logTestResult(report); } onEnd(result: any) { const summary = this.generateSummary(); this.saveReports(summary); console.log('Testing completed. Reports generated.'); } private formatError(error: TestError): ErrorReport { return { message: error.message || 'Unknown error', stack: error.stack, value: error.value?.toString(), }; } private formatStep(step: TestStep): StepReport { return { title: step.title, duration: step.duration, error: step.error ? this.formatError(step.error) : undefined, }; } private generateSummary(): TestSummary { const total = this.reports.length; const passed = this.reports.filter(r => r.status === 'passed').length; const failed = this.reports.filter(r => r.status === 'failed').length; const skipped = this.reports.filter(r => r.status === 'skipped').length; return { total, passed, failed, skipped, timestamp: new Date().toISOString(), duration: this.reports.reduce((sum, r) => sum + (r.duration || 0), 0), tests: this.reports, }; } private logTestResult(report: TestReport) { const status = this.getStatusSymbol(report.status); console.log(`${status} ${report.title} (${report.duration}ms)`); if (report.error) { console.error('Error:', report.error.message); if (report.error.stack) { console.error('Stack:', report.error.stack); } } } private getStatusSymbol(status: string): string { switch (status) { case 'passed': return '✓'; case 'failed': return '✗'; case 'skipped': return '-'; default: return '?'; } } private saveReports(summary: TestSummary) { const reportsDir = path.join(process.cwd(), 'test-results'); // Ensure reports directory exists if (!fs.existsSync(reportsDir)) { fs.mkdirSync(reportsDir, { recursive: true }); } // Save JSON report const jsonReport = path.join(reportsDir, 'test-report.json'); fs.writeFileSync(jsonReport, JSON.stringify(summary, null, 2)); // Save Markdown report const mdReport = path.join(reportsDir, 'test-report.md'); fs.writeFileSync(mdReport, this.generateMarkdownReport(summary)); } private generateMarkdownReport(summary: TestSummary): string { const now = new Date().toISOString(); let md = `# Test Execution Report\n\n`; md += `Generated: ${now}\n\n`; md += `## Summary\n\n`; md += `- Total Tests: ${summary.total}\n`; md += `- Passed: ${summary.passed}\n`; md += `- Failed: ${summary.failed}\n`; md += `- Skipped: ${summary.skipped}\n`; md += `- Total Duration: ${summary.duration}ms\n\n`; md += `## Test Results\n\n`; summary.tests.forEach(test => { md += `### ${test.title}\n\n`; md += `- Status: ${test.status}\n`; md += `- Duration: ${test.duration}ms\n`; if (test.error) { md += `- Error: ${test.error.message}\n`; if (test.error.stack) { md += `\`\`\`\n${test.error.stack}\n\`\`\`\n`; } } if (test.steps.length > 0) { md += `\nSteps:\n`; test.steps.forEach(step => { md += `- ${step.title} (${step.duration}ms)\n`; if (step.error) { md += ` Error: ${step.error.message}\n`; } }); } md += `\n`; }); return md; } } interface TestReport { title: string; status: string; duration?: number; error?: ErrorReport; steps: StepReport[]; retries: number; timestamp: string; } interface ErrorReport { message: string; stack?: string; value?: string; } interface StepReport { title: string; duration?: number; error?: ErrorReport; } interface TestSummary { total: number; passed: number; failed: number; skipped: number; timestamp: string; duration: number; tests: TestReport[]; } export default CustomReporter;