- Add HVAC_Test_User_Factory class with: * User creation with specific roles * Multiple role support * Persona management system * Account cleanup integration - Create comprehensive test suite in HVAC_Test_User_Factory_Test.php - Update testing improvement plan documentation - Add implementation decisions to project memory bank - Restructure .gitignore with: * Whitelist approach for better file management * Explicit backup exclusions * Specific bin directory inclusions Part of the Account Management component from the testing framework improvement plan.
180 lines
No EOL
4.7 KiB
TypeScript
180 lines
No EOL
4.7 KiB
TypeScript
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; |