upskill-event-manager/wordpress-dev/tests/e2e/reporters/HtmlReporter.ts
bengizmo d6211ee364 feat(testing): Implement HVAC_Test_User_Factory and update .gitignore
- 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.
2025-04-14 17:41:36 -03:00

175 lines
No EOL
6.6 KiB
TypeScript

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 = `
<!DOCTYPE html>
<html>
<head>
<title>Test Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; }
.summary { background: #f5f5f5; padding: 1rem; border-radius: 4px; }
.test-case { margin: 1rem 0; padding: 1rem; border: 1px solid #ddd; }
.passed { border-left: 4px solid #4CAF50; }
.failed { border-left: 4px solid #f44336; }
.skipped { border-left: 4px solid #9E9E9E; }
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
.metric { background: #fff; padding: 0.5rem; border-radius: 4px; }
.attachments { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 1rem; }
.attachment { max-width: 200px; }
.error { background: #ffebee; padding: 1rem; margin: 1rem 0; border-radius: 4px; }
</style>
</head>
<body>
<div id="report-content"></div>
</body>
</html>
`;
}
async onEnd() {
const counts = this.getStatusCounts();
const content = [];
// Summary section
content.push(`
<div class="summary">
<h2>Test Summary</h2>
<p>
Total Tests: ${counts.passed + counts.failed + counts.skipped}<br>
Passed: ${counts.passed}<br>
Failed: ${counts.failed}<br>
Skipped: ${counts.skipped}
</p>
</div>
`);
// Test cases
this.testResults.forEach((results, title) => {
results.forEach(result => {
const metrics = this.metrics.get(title);
const attachments = this.getAttachments(result);
content.push(`
<div class="test-case ${result.status}">
<h3>${title}</h3>
<div class="metrics">
<div class="metric">
<strong>Duration:</strong> ${this.formatDuration(metrics?.duration || 0)}
</div>
<div class="metric">
<strong>Memory:</strong> ${Math.round(metrics?.memory || 0)}MB
</div>
<div class="metric">
<strong>Network Requests:</strong> ${metrics?.networkRequests || 0}
</div>
</div>
${result.error ? `
<div class="error">
<strong>Error:</strong><br>
<pre>${result.error.message}</pre>
</div>
` : ''}
${this.renderAttachments(attachments)}
</div>
`);
});
});
// Generate the final HTML
const html = this.template.replace(
'<div id="report-content"></div>',
`<div id="report-content">${content.join('')}</div>`
);
// 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<typeof this.getAttachments>) {
const elements = [];
if (attachments.screenshots.length) {
elements.push('<div class="attachments"><h4>Screenshots:</h4>');
attachments.screenshots.forEach(screenshot => {
if (screenshot.path) {
const filename = path.basename(screenshot.path);
elements.push(`
<div class="attachment">
<img src="attachments/${filename}" alt="Screenshot" style="max-width: 100%">
</div>
`);
}
});
elements.push('</div>');
}
if (attachments.videos.length) {
elements.push('<div class="attachments"><h4>Videos:</h4>');
attachments.videos.forEach(video => {
if (video.path) {
const filename = path.basename(video.path);
elements.push(`
<div class="attachment">
<video controls style="max-width: 100%">
<source src="attachments/${filename}" type="${video.contentType}">
</video>
</div>
`);
}
});
elements.push('</div>');
}
if (attachments.traces.length) {
elements.push('<div class="attachments"><h4>Traces:</h4>');
attachments.traces.forEach(trace => {
if (trace.path) {
const filename = path.basename(trace.path);
elements.push(`
<div class="attachment">
<a href="attachments/${filename}" target="_blank">View Trace</a>
</div>
`);
}
});
elements.push('</div>');
}
return elements.join('');
}
}