@lytics/playwright-adapters
Storage adapters for the Playwright reporter framework.
Installation
npm
npm install @lytics/playwright-adaptersAvailable Adapters
Write test results to JSON files on the local filesystem. Great for local development and CI artifacts.
FilesystemAdapterSend test summaries to Slack channels. Includes pass/fail stats, failed test details, and flaky test tracking.
SlackAdapterWrite test results to Google Cloud Firestore. Supports automatic retry with exponential backoff.
FirestoreAdapterQuick Start
Playwright reporters must be specified as file paths. Create a reporter file:
Single Adapter
// 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;// playwright.config.ts
export default {
reporter: [['list'], ['./reporter.ts']],
};Multiple Adapters
All adapters run in parallel:
// reporter.ts
import { CoreReporter } from '@lytics/playwright-reporter';
import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem';
import { SlackAdapter } from '@lytics/playwright-adapters/slack';
import { FirestoreAdapter } from '@lytics/playwright-adapters/firestore';
class CustomReporter extends CoreReporter {
constructor() {
super({
adapters: [
// Always write to filesystem for debugging
new FilesystemAdapter({ outputDir: './test-results' }),
// Send Slack notifications in production
new SlackAdapter({
webhookUrl: process.env.SLACK_WEBHOOK_URL!,
environment: 'Production',
productionOnly: true,
}),
// Store in Firestore for dashboards
new FirestoreAdapter({
projectId: 'my-gcp-project',
collections: {
testRuns: 'e2e_test_runs',
testCases: 'e2e_test_cases',
latestTestCases: 'e2e_latest_test_cases',
},
}),
]
});
}
}
export default CustomReporter;Import Paths
Each adapter has its own import path:
// Filesystem adapter
import { FilesystemAdapter } from '@lytics/playwright-adapters/filesystem';
// Slack adapter
import { SlackAdapter } from '@lytics/playwright-adapters/slack';
// Firestore adapter
import { FirestoreAdapter } from '@lytics/playwright-adapters/firestore';Creating Custom Adapters
Implement the ResultAdapter interface from @lytics/playwright-reporter:
import type { ResultAdapter, CoreTestResult, CoreTestRun } from '@lytics/playwright-reporter';
export class MyCustomAdapter implements ResultAdapter {
async initialize(): Promise<void> {
// Setup: connect to database, create directories, etc.
}
async writeTestResult(result: CoreTestResult): Promise<void> {
// Write individual test result
}
async writeTestRun(run: CoreTestRun): Promise<void> {
// Write test run summary
}
async close(): Promise<void> {
// Cleanup: close connections, flush buffers, etc.
}
}Best Practice: Handle errors gracefully in adapters. A failing adapter shouldnβt crash the test run or prevent other adapters from working.
Example: Console Adapter
A simple adapter that logs to console:
import type { ResultAdapter, CoreTestResult, CoreTestRun } from '@lytics/playwright-reporter';
export class ConsoleAdapter implements ResultAdapter {
async initialize(): Promise<void> {
console.log('π Test run starting...');
}
async writeTestResult(result: CoreTestResult): Promise<void> {
const icon = result.status === 'passed' ? 'β
' : 'β';
console.log(`${icon} ${result.testCaseId}: ${result.status} (${result.durationMs}ms)`);
}
async writeTestRun(run: CoreTestRun): Promise<void> {
console.log('\nπ Test Run Summary');
console.log(` Total: ${run.totalTests}`);
console.log(` Passed: ${run.passed}`);
console.log(` Failed: ${run.failed}`);
console.log(` Pass Rate: ${(run.passRate * 100).toFixed(1)}%`);
console.log(` Duration: ${run.durationMs}ms`);
if (run.flakyTests > 0) {
console.log(` Flaky: ${run.flakyTests}`);
}
}
async close(): Promise<void> {
console.log('π Test run complete.');
}
}Adapter Lifecycle
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. initialize() β
β Called once before any tests run β
β Use for: connections, directory creation, validation β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. writeTestResult(result) - called N times β
β Called after each test completes β
β Use for: writing individual test results β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. writeTestRun(run) β
β Called once after all tests complete β
β Use for: writing summary, sending notifications β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. close() β
β Called after writeTestRun β
β Use for: cleanup, closing connections, flushing buffers β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββError Handling
Adapters should handle errors gracefully:
async writeTestResult(result: CoreTestResult): Promise<void> {
try {
await this.doWrite(result);
} catch (error) {
// Log but don't throw - allow other adapters to continue
console.error(`[MyAdapter] Failed to write result: ${error}`);
}
}If an adapter throws during initialize(), the reporter will log the error but continue with other adapters.