npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@lytics/playwright-reporter

v0.1.1

Published

Adapter-based Playwright reporter with pluggable storage backends

Readme

@lytics/playwright-reporter

Adapter-based Playwright reporter with pluggable storage backends

npm version License: MIT

Overview

CoreReporter is a flexible Playwright reporter that uses the adapter pattern to write test results to multiple storage backends simultaneously. It validates required annotations, tracks unique tests, handles retries, and generates comprehensive test run statistics.

Features

  • Adapter Pattern - Write to multiple backends (filesystem, database, cloud storage, etc.)
  • Annotation Validation - Enforces required testCaseId and journeyId annotations
  • Unique Test Tracking - Handles retries and tracks latest test state
  • Flaky Test Detection - Identifies tests that pass after retries
  • Structured Errors - Parses and structures error information
  • Environment Enrichment - Add custom environment data to test runs
  • Parallel Adapter Execution - All adapters run concurrently

Installation

npm install @lytics/playwright-reporter @lytics/playwright-annotations
# or
pnpm add @lytics/playwright-reporter @lytics/playwright-annotations
# or
yarn add @lytics/playwright-reporter @lytics/playwright-annotations

Quick Start

// playwright.config.ts
import { defineConfig } from '@playwright/test';
import { CoreReporter } from '@lytics/playwright-reporter';
import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem';

export default defineConfig({
  reporter: [
    ['list'], // Built-in reporter
    [
      new CoreReporter({
        adapters: [
          new FilesystemAdapter({ outputDir: './test-results' })
        ]
      })
    ]
  ],
  // ... rest of config
});

Usage

Basic Configuration

import { CoreReporter } from '@lytics/playwright-reporter';
import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem';

const reporter = new CoreReporter({
  adapters: [
    new FilesystemAdapter({ outputDir: './test-results' })
  ]
});

export default reporter;

Multiple Adapters

import { CoreReporter } from '@lytics/playwright-reporter';
import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem';
import { SlackAdapter } from '@lytics/playwright-adapters/slack';

const reporter = new CoreReporter({
  adapters: [
    new FilesystemAdapter({ outputDir: './test-results' }),
    new SlackAdapter({ 
      webhookUrl: process.env.SLACK_WEBHOOK_URL,
      notifyOnFailure: true 
    })
  ]
});

export default reporter;

Environment Enrichment

Add custom environment data to test runs:

const reporter = new CoreReporter({
  adapters: [/* ... */],
  environmentEnricher: () => ({
    branch: process.env.GITHUB_REF,
    commit: process.env.GITHUB_SHA,
    author: process.env.GITHUB_ACTOR,
    buildNumber: process.env.GITHUB_RUN_NUMBER,
    nodeVersion: process.version,
  })
});

Core Types

CoreTestResult

Individual test result with full metadata:

interface CoreTestResult {
  testCaseId: string;
  journeyId: string;
  title: string;
  annotations: TestAnnotations;
  status: "passed" | "failed" | "timedOut" | "skipped" | "cancelled" | "interrupted" | "unknown";
  projectName: string;
  durationMs: number;
  error?: TestError;
  timestamp: Date;
  buildId: string;
  reportLink?: string;
}

CoreTestRun

Test run summary with aggregate statistics:

interface CoreTestRun {
  runId: string;
  timestamp: Date;
  overallStatus: "passed" | "failed" | "timedOut" | "skipped" | "cancelled" | "interrupted" | "unknown";
  totalTests: number;           // Unique tests (deduped)
  totalExecutions: number;      // Total test executions (including retries)
  passed: number;
  failed: number;
  skipped: number;
  durationMs: number;
  passRate: number;             // 0-1
  averageTestDuration: number;
  slowestTestDuration: number;
  flakyTests: number;           // Tests that passed after retries
  environment: Record<string, unknown>;
}

TestError

Structured error information:

interface TestError {
  matcher: string;              // e.g., "toHaveText", "toBeVisible"
  expected: string;
  actual: string;
  locator: string;
  location: {
    file: string;
    line: number;
    column: number;
  };
  message: string;
  snippet: string[];            // Code snippet around error
}

Creating Custom Adapters

Implement the ResultAdapter interface:

import { ResultAdapter, CoreTestResult, CoreTestRun } from '@lytics/playwright-reporter';

class MyCustomAdapter implements ResultAdapter {
  async initialize(): Promise<void> {
    // Connect to your storage backend
    console.log('Initializing adapter...');
  }

  async writeTestResult(result: CoreTestResult): Promise<void> {
    // Write individual test result
    console.log(`Test ${result.testCaseId}: ${result.status}`);
  }

  async writeTestRun(run: CoreTestRun): Promise<void> {
    // Write test run summary
    console.log(`Run ${run.runId}: ${run.passed}/${run.totalTests} passed`);
  }

  async close(): Promise<void> {
    // Cleanup connections
    console.log('Closing adapter...');
  }
}

// Use your custom adapter
const reporter = new CoreReporter({
  adapters: [new MyCustomAdapter()]
});

Annotation Requirements

The reporter requires tests to have proper annotations. Use @lytics/playwright-annotations:

import { pushSuiteAnnotation, pushTestAnnotations } from '@lytics/playwright-annotations';

test.describe('My Feature', () => {
  test.beforeEach(async ({}, testInfo) => {
    pushSuiteAnnotation(testInfo, 'MY_FEATURE');
  });

  test('validates user can perform action', async ({}, testInfo) => {
    pushTestAnnotations(testInfo, {
      journeyId: 'MY_FEATURE_ACTION',
      testCaseId: 'MY_FEATURE_ACTION_VALID',
    });

    // Test implementation...
  });
});

Tests missing required annotations will be skipped with a warning.

API Reference

CoreReporter

Constructor:

new CoreReporter(config: CoreReporterConfig)

Config:

interface CoreReporterConfig {
  adapters: ResultAdapter[];
  environmentEnricher?: () => Record<string, unknown>;
}

Methods:

  • onBegin() - Called once before test suite starts
  • onTestBegin(test) - Called before each test
  • onTestEnd(test, result) - Called after each test
  • onEnd(result) - Called once after test suite completes

parseError

Parse raw Playwright errors into structured format:

import { parseError } from '@lytics/playwright-reporter';

const error = parseError(result.errors[0]);
if (error) {
  console.log(`Expected: ${error.expected}`);
  console.log(`Actual: ${error.actual}`);
  console.log(`Locator: ${error.locator}`);
}

How It Works

Test Execution Flow

1. onBegin()
   └─> Initialize all adapters in parallel

2. For each test:
   onTestBegin(test)
   └─> Track test start
   
   onTestEnd(test, result)
   └─> Validate annotations
   └─> Track unique test state (handle retries)
   └─> Map to CoreTestResult
   └─> Write to all adapters in parallel

3. onEnd(result)
   └─> Calculate aggregate statistics
   └─> Map to CoreTestRun
   └─> Write to all adapters in parallel
   └─> Close all adapters

Retry Handling

The reporter intelligently handles test retries:

  • Tracks unique tests by testCaseId
  • Only the latest execution of each test is counted in final stats
  • Detects flaky tests (passed after retry)
  • Reports both totalTests (unique) and totalExecutions (including retries)

Example:

Test A: fail → retry → pass  ✅ (flaky)
Test B: fail → retry → fail  ❌
Test C: pass                  ✅

totalTests: 3
totalExecutions: 5
passed: 2
failed: 1
flakyTests: 1

Environment Variables

The reporter uses these environment variables (can be customized via environmentEnricher):

  • GITHUB_RUN_ID - Used for runId and buildId
  • ARTIFACT_BASE_URL - Used for reportLink

Adapters

See these packages for ready-to-use adapters:

Troubleshooting

Tests are skipped with "missing annotation" warning

Solution: Ensure all tests have required annotations:

pushTestAnnotations(testInfo, {
  journeyId: 'YOUR_JOURNEY_ID',
  testCaseId: 'YOUR_TEST_CASE_ID',
});

Adapter initialization fails

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

Stats don't match expected values

Cause: Retries are handled automatically. The reporter tracks:

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

Contributing

See CONTRIBUTING.md in the repository root.

License

MIT

Related Packages