</>

Technology

Playwright

Difficulty

Advanced

Interview Question

How do you create a custom reporter in Playwright?

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}`);
    }
  }
}

Follow AutomateQA

Related Topics