Answer
Custom Reporter in Playwright
Playwright's Reporter interface has lifecycle hooks you implement to capture test events.
Built-in Reporters (Reference)
TypeScript
reporter: [
['html'], // HTML report
['json'], // JSON output
['list'], // Console list
['dot'], // Dots in CI
['junit'], // JUnit XML
['github'], // GitHub Annotations
]
Create a Custom Reporter
TypeScript
// reporters/custom-reporter.ts
import type {
Reporter, Suite, TestCase, TestResult,
FullResult, TestStep
} from '@playwright/test/reporter';
class CustomReporter implements Reporter {
private startTime = Date.now();
private passed = 0;
private failed = 0;
private skipped = 0;
private failures: { title: string; error: string }[] = [];
onBegin(config: any, suite: Suite): void {
const total = suite.allTests().length;
console.log(`\nš Starting ${total} Playwright tests...\n`);
}
onTestBegin(test: TestCase): void {
console.log(` ā¶ ${test.title}`);
}
onTestEnd(test: TestCase, result: TestResult): void {
if (result.status === 'passed') {
this.passed++;
console.log(` ā
${test.title} (${result.duration}ms)`);
} else if (result.status === 'failed') {
this.failed++;
const error = result.errors[0]?.message || 'Unknown error';
this.failures.push({ title: test.title, error });
console.log(` ā ${test.title}`);
console.log(` Error: ${error.slice(0, 200)}`);
} else if (result.status === 'skipped') {
this.skipped++;
console.log(` ā ${test.title} (skipped)`);
}
}
async onEnd(result: FullResult): Promise<void> {
const duration = Date.now() - this.startTime;
console.log(`\n${'ā'.repeat(50)}`);
console.log(`ā
Passed: ${this.passed}`);
console.log(`ā Failed: ${this.failed}`);
console.log(`ā Skipped: ${this.skipped}`);
console.log(`ā± Duration: ${(duration / 1000).toFixed(1)}s`);
console.log(`${'ā'.repeat(50)}\n`);
if (this.failures.length > 0) {
console.log('Failed tests:');
this.failures.forEach(f => console.log(` - ${f.title}`));
}
// Send results to Slack / webhook
if (this.failed > 0) {
await this.sendSlackNotification();
}
}
private async sendSlackNotification() {
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
if (!webhookUrl) return;
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `šØ Playwright: ${this.failed} tests failed!`,
attachments: this.failures.slice(0, 5).map(f => ({
color: 'danger',
title: f.title,
text: f.error.slice(0, 300),
})),
}),
});
}
}
export default CustomReporter;
Register the Custom Reporter
TypeScript
// playwright.config.ts
export default defineConfig({
reporter: [
['html'], // Keep HTML report
['./reporters/custom-reporter.ts'], // Add custom
['json', { outputFile: 'results/report.json' }], // JSON output
],
});
Reporter with Test Attachments
TypeScript
onTestEnd(test: TestCase, result: TestResult): void {
for (const attachment of result.attachments) {
if (attachment.name === 'screenshot') {
// Process screenshot attachment
console.log(`Screenshot: ${attachment.path}`);
}
}
}
