Skip to Content
DocumentationReporterUsage & Configuration

Usage & Configuration

Detailed guide to configuring and using the CoreReporter.

Basic Configuration

Playwright reporters must be specified as file paths, not class instances. Create a reporter file that extends CoreReporter:

Step 1: Create a reporter file

// reporter.ts import { CoreReporter } from '@lytics/playwright-reporter'; import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem'; class CustomReporter extends CoreReporter { constructor() { super({ adapters: [ new FilesystemAdapter({ outputDir: './test-results' }) ] }); } } export default CustomReporter;

Step 2: Reference it in your config

// playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ reporter: [ ['list'], ['./reporter.ts'], ], });

Configuration Options

CoreReporterConfig

interface CoreReporterConfig { /** Storage adapters to write results to */ adapters: ResultAdapter[]; /** Optional function to add environment metadata */ environmentEnricher?: () => Record<string, unknown>; }

adapters

Array of adapters that implement the ResultAdapter interface. All adapters run in parallel.

// reporter.ts class CustomReporter extends CoreReporter { constructor() { super({ adapters: [ new FilesystemAdapter({ outputDir: './test-results' }), new SlackAdapter({ webhookUrl: process.env.SLACK_WEBHOOK_URL! }), ] }); } }

environmentEnricher

Optional function that returns environment metadata to include in test runs:

// reporter.ts class CustomReporter extends CoreReporter { constructor() { super({ adapters: [/* ... */], environmentEnricher: () => ({ // CI/CD metadata branch: process.env.GITHUB_REF, commit: process.env.GITHUB_SHA, author: process.env.GITHUB_ACTOR, buildNumber: process.env.GITHUB_RUN_NUMBER, // Runtime info nodeVersion: process.version, // Custom metadata environment: process.env.TEST_ENV || 'local', triggeredBy: process.env.TRIGGER_TYPE || 'manual', }) }); } }

Environment Variables

The reporter uses these environment variables by default:

VariableUsed For
GITHUB_RUN_IDrunId and buildId
ARTIFACT_BASE_URLreportLink in test results

You can override these via environmentEnricher.

Creating Custom Adapters

Implement the ResultAdapter interface:

import type { ResultAdapter, CoreTestResult, CoreTestRun } from '@lytics/playwright-reporter'; class MyCustomAdapter implements ResultAdapter { private connection: DatabaseConnection; async initialize(): Promise<void> { // Called once before any tests run // Use for setup: connect to database, create directories, etc. this.connection = await Database.connect(); console.log('MyCustomAdapter initialized'); } async writeTestResult(result: CoreTestResult): Promise<void> { // Called after each test completes // Write individual test result await this.connection.insert('test_results', { testCaseId: result.testCaseId, journeyId: result.journeyId, status: result.status, durationMs: result.durationMs, timestamp: result.timestamp, error: result.error ? JSON.stringify(result.error) : null, }); } async writeTestRun(run: CoreTestRun): Promise<void> { // Called once after all tests complete // Write test run summary await this.connection.insert('test_runs', { runId: run.runId, totalTests: run.totalTests, passed: run.passed, failed: run.failed, passRate: run.passRate, durationMs: run.durationMs, timestamp: run.timestamp, }); } async close(): Promise<void> { // Called after writeTestRun // Cleanup: close connections, flush buffers, etc. await this.connection.close(); console.log('MyCustomAdapter closed'); } }

ResultAdapter Interface

interface ResultAdapter { /** Initialize the adapter (connect, create directories, etc.) */ initialize(): Promise<void>; /** Write an individual test result */ writeTestResult(result: CoreTestResult): Promise<void>; /** Write a full test run summary */ writeTestRun(run: CoreTestRun): Promise<void>; /** Cleanup and close connections */ close(): Promise<void>; }

Best Practice: Handle errors gracefully in adapters. A failing adapter shouldn’t crash the test run or prevent other adapters from working.

Error Parsing

parseError

Parse raw Playwright errors into structured format:

import { parseError } from '@lytics/playwright-reporter'; // In a custom adapter or post-processing script const error = parseError(result.errors[0]); if (error) { console.log(`Matcher: ${error.matcher}`); // "toHaveText" console.log(`Expected: ${error.expected}`); // "Welcome" console.log(`Actual: ${error.actual}`); // "Hello" console.log(`Locator: ${error.locator}`); // "getByRole('heading')" console.log(`File: ${error.location.file}`); // "tests/home.spec.ts" console.log(`Line: ${error.location.line}`); // 42 }

TestError Type

interface TestError { matcher: string; // Playwright matcher (e.g., "toHaveText", "toBeVisible") expected: string; // Expected value actual: string; // Actual value locator: string; // Element locator location: { file: string; // Test file path line: number; // Line number column: number; // Column number }; message: string; // Full error message snippet: string[]; // Code snippet around the error }

Multiple Projects

The reporter handles multiple Playwright projects correctly:

// playwright.config.ts export default defineConfig({ projects: [ { name: 'chromium', use: { browserName: 'chromium' } }, { name: 'firefox', use: { browserName: 'firefox' } }, { name: 'webkit', use: { browserName: 'webkit' } }, ], reporter: [ ['list'], ['./reporter.ts'], ], });

Each test result includes projectName so you can filter/group by browser.

Troubleshooting

Tests are skipped with “missing annotation” warning

Cause: Tests don’t have required journeyId and testCaseId annotations.

Solution: Add annotations to all tests:

test('my test', async ({}, testInfo) => { pushTestAnnotations(testInfo, { journeyId: 'YOUR_JOURNEY_ID', testCaseId: 'YOUR_TEST_CASE_ID', }); // Test implementation... });

Adapter initialization fails

Cause: Adapter configuration is incorrect or external service is unavailable.

Solution: Check adapter configuration and credentials. Adapters log errors to console.

Stats don’t match expected values

Cause: Retries are handled automatically.

Explanation:

  • totalTests = unique tests (deduped by testCaseId)
  • totalExecutions = all test runs (including retries)

If a test fails and retries, it counts as 1 test but 2+ executions.

Adapters not receiving results

Cause: Adapter threw an error during initialize().

Solution: Check console for initialization errors. Ensure all required configuration is provided.

Best Practices

1. Always Include a Local Adapter

Even in CI, include FilesystemAdapter for debugging:

// reporter.ts class CustomReporter extends CoreReporter { constructor() { super({ adapters: [ new FilesystemAdapter({ outputDir: './test-results' }), // ... other adapters for CI ] }); } }

2. Use Environment Enrichment

Add CI/CD context to make results more useful:

environmentEnricher: () => ({ branch: process.env.GITHUB_REF, commit: process.env.GITHUB_SHA, pr: process.env.GITHUB_PR_NUMBER, environment: process.env.TEST_ENV, })

3. Handle Adapter Failures Gracefully

Adapters should catch and log errors without throwing:

async writeTestResult(result: CoreTestResult): Promise<void> { try { await this.doWrite(result); } catch (error) { console.error(`[MyAdapter] Failed to write result: ${error}`); // Don't re-throw - allow other adapters to continue } }

4. Use Consistent Annotation Patterns

Establish team conventions for annotations and validate them:

// In a shared test setup file test.beforeEach(async ({}, testInfo) => { // Validate annotations exist if (!testInfo.annotations.some(a => a.type === 'testSuiteName')) { throw new Error('Missing testSuiteName annotation'); } });
Last updated on