Reporters
Reporters
Reporters format and output script execution results. Stepwright includes built-in reporters and supports custom implementations.
Built-in Reporters
ConsoleReporter
Outputs results to the terminal:
import { Stepwright, ConsoleReporter } from '@korvol/stepwright';
const script = Stepwright.create('My Script') .reporter(new ConsoleReporter()) .step('Navigate', ...) .step('Click', ...);Console Options
new ConsoleReporter({ colors: true, // Enable colored output timestamps: true, // Show timestamps verbose: false, // Show detailed step info})Example Output
[10:30:00] Starting: My Script[10:30:01] ✓ Navigate (1.2s)[10:30:02] ✓ Click (0.5s)[10:30:02] ✓ Verify (0.3s)[10:30:02] ────────────────────[10:30:02] Passed: 3/3 steps[10:30:02] Duration: 2.0sJSONReporter
Outputs results as JSON:
import { Stepwright, JSONReporter } from '@korvol/stepwright';
const script = Stepwright.create('My Script') .reporter(new JSONReporter({ outputPath: './results/output.json', }));JSON Options
new JSONReporter({ outputPath: './results.json', // Write to file pretty: true, // Pretty-print JSON includeArtifacts: true, // Include artifact paths})Example Output
{ "name": "My Script", "success": true, "duration": 2000, "stepsCompleted": 3, "totalSteps": 3, "steps": [ { "name": "Navigate", "status": "passed", "duration": 1200 }, { "name": "Click", "status": "passed", "duration": 500 }, { "name": "Verify", "status": "passed", "duration": 300 } ]}Multiple Reporters
Add multiple reporters for different outputs:
Stepwright.create('My Script') .reporter(new ConsoleReporter()) // Terminal output .reporter(new JSONReporter({ // JSON file outputPath: './results.json' })) .reporter(new CustomSlackReporter()) // Custom Slack notificationsDefault Reporter
If no reporter is specified, ConsoleReporter is used by default:
// These are equivalent:Stepwright.create('Script').step(...).run();
Stepwright.create('Script') .reporter(new ConsoleReporter()) .step(...) .run();Reporter Interface
Interface Definition
interface Reporter { /** Called when script starts */ onStart?(config: StepwrightConfig): void | Promise<void>;
/** Called when a step starts */ onStepStart?(step: StepResult): void | Promise<void>;
/** Called when a step ends */ onStepEnd?(step: StepResult): void | Promise<void>;
/** Called when script ends */ onEnd?(result: StepwrightResult): void | Promise<void>;}Lifecycle Events
onStart() └─ onStepStart(step1) └─ onStepEnd(step1) └─ onStepStart(step2) └─ onStepEnd(step2) └─ ...onEnd()Creating Custom Reporters
Basic Custom Reporter
import type { Reporter, StepwrightConfig, StepwrightResult, StepResult } from '@korvol/stepwright';
class MyReporter implements Reporter { onStart(config: StepwrightConfig) { console.log('Starting:', config.name); }
onStepStart(step: StepResult) { console.log('Running:', step.name); }
onStepEnd(step: StepResult) { const icon = step.status === 'passed' ? '✓' : '✗'; console.log(`${icon} ${step.name} (${step.duration}ms)`); }
onEnd(result: StepwrightResult) { console.log('Completed:', result.success ? 'PASS' : 'FAIL'); }}
// UsageStepwright.create('Script') .reporter(new MyReporter()) .step(...);Async Reporter
Reporters can use async methods:
class AsyncReporter implements Reporter { async onEnd(result: StepwrightResult) { await sendToDatabase(result); await notifySlack(result); }}Stateful Reporter
Track state across events:
class StatefulReporter implements Reporter { private startTime = 0; private failedSteps: string[] = [];
onStart() { this.startTime = Date.now(); this.failedSteps = []; }
onStepEnd(step: StepResult) { if (step.status === 'failed') { this.failedSteps.push(step.name); } }
onEnd(result: StepwrightResult) { const duration = Date.now() - this.startTime; console.log(`Total time: ${duration}ms`); if (this.failedSteps.length > 0) { console.log('Failed steps:', this.failedSteps.join(', ')); } }}Example: Slack Reporter
class SlackReporter implements Reporter { private webhookUrl: string;
constructor(webhookUrl: string) { this.webhookUrl = webhookUrl; }
async onEnd(result: StepwrightResult) { const message = { text: result.success ? `✓ ${result.steps[0]?.name} passed (${result.stepsCompleted}/${result.totalSteps})` : `✗ ${result.failedStep?.name} failed: ${result.failedStep?.error.message}`, };
await fetch(this.webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(message), }); }}Example: GitHub Actions Reporter
class GitHubActionsReporter implements Reporter { onStepEnd(step: StepResult) { if (step.status === 'failed') { // GitHub Actions annotation format console.log(`::error title=${step.name}::${step.error?.message}`); } }
onEnd(result: StepwrightResult) { // Set output for workflow console.log(`::set-output name=success::${result.success}`); console.log(`::set-output name=steps_completed::${result.stepsCompleted}`); }}Best Practices
Combine Console and File Output
.reporter(new ConsoleReporter()) // Real-time feedback.reporter(new JSONReporter({ // Persistent record outputPath: `./results/${Date.now()}.json`}))Handle Reporter Errors
class SafeReporter implements Reporter { async onEnd(result: StepwrightResult) { try { await sendToExternalService(result); } catch (error) { console.error('Reporter error:', error); // Don't rethrow - script should still succeed } }}Keep Reporters Lightweight
Avoid heavy operations in onStepStart/onStepEnd:
// Bad: Slow I/O in step eventsclass SlowReporter implements Reporter { async onStepEnd(step: StepResult) { await fs.writeFile(`step-${step.index}.json`, ...); // Slow! }}
// Good: Batch writes in onEndclass FastReporter implements Reporter { private steps: StepResult[] = [];
onStepEnd(step: StepResult) { this.steps.push(step); }
async onEnd() { await fs.writeFile('all-steps.json', JSON.stringify(this.steps)); }}Next Steps
- CLI - Run scripts from command line
- API Reference: Reporter - Full reporter type definitions
- CI/CD Integration - Use reporters in pipelines