Implementation:DevExpress Testcafe Reporter Plugin System
| Knowledge Sources | |
|---|---|
| Domains | Testing, Web_Automation |
| Last Updated | 2026-02-12 04:00 GMT |
Overview
Concrete reporter plugin system for TestCafe that manages test result formatting through event-driven lifecycle methods and pluggable output modules.
Description
The TestCafe reporter system consists of two primary classes: Reporter and ReporterPluginHost. The Reporter class (src/reporter/index.ts:L157-736) manages reporter lifecycle, coordinates event publishing, and handles plugin loading through getReporterPlugins() static method. It supports built-in reporters (spec, list, minimal, json, xunit) and external plugins loaded from npm packages.
The ReporterPluginHost class (src/reporter/plugin-host.ts:L32-236) wraps plugin implementations, providing utility methods for formatted output: write(text) for stream output with word wrapping, formatError(err) for syntax-highlighted error messages, setIndent(level) for hierarchical formatting, newline() for line breaks. It manages color support through chalk, viewport width calculation, and symbol rendering (✓, ✖, etc.).
Reporter plugins implement lifecycle methods: reportTaskStart() called at test session start, reportFixtureStart() for each fixture, reportTestDone() for completed tests (with full testRunInfo including errors, screenshots, videos, duration), and reportTaskDone() at session end with aggregate statistics. The host decorates errors with ANSI color codes, syntax highlighting for code snippets, and clickable file paths.
Usage
Use the Reporter class when initializing a test run to configure output formatting and destinations.
Code Reference
Source Location
- Repository: testcafe
- File: src/reporter/index.ts (Reporter class), src/reporter/plugin-host.ts (ReporterPluginHost)
- Lines: L157-736 (Reporter), L32-236 (ReporterPluginHost)
Signature
// Reporter class
class Reporter {
constructor({ name, plugin, outStream, messageBus, reporterPluginHooks })
static async getReporterPlugins(reporters: ReporterSource[]): Promise<ReporterPluginSource[]>
async init(): Promise<void>
async dispatchToPlugin(method: string, ...args: any[]): Promise<void>
}
// ReporterPluginHost class
class ReporterPluginHost {
constructor(plugin: any, outStream?: Writable, name?: string, pluginHooks?: ReporterPluginHooks)
write(text: string): Promise<void>
formatError(err: Error, prefix?: string): string
setIndent(indent: number): ReporterPluginHost
newline(): ReporterPluginHost
}
Import
import Reporter from './reporter';
import { ReporterPluginHost } from './reporter/plugin-host';
// Load and initialize reporters
const reporterPlugins = await Reporter.getReporterPlugins([
{ name: 'spec' },
{ name: 'json', outStream: fs.createWriteStream('report.json') }
]);
I/O Contract
Inputs
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Reporter name ('spec', 'json', 'xunit', 'list', 'minimal', or npm package) |
| plugin | object | No | Custom reporter plugin implementation |
| outStream | Writable | No | Output stream (default: process.stdout) |
| messageBus | MessageBus | No | Event bus for reporter communication |
| reporterPluginHooks | object | No | Lifecycle hooks for reporter customization |
Outputs
| Name | Type | Description |
|---|---|---|
| Formatted output | string | Test results formatted to outStream (console, file, etc.) |
| Event notifications | void | Side effects from lifecycle methods |
| Reporter instance | Reporter | Initialized reporter ready to receive events |
Usage Examples
Basic Reporter Configuration
import Reporter from './reporter';
import fs from 'fs';
// Single console reporter
const specReporter = new Reporter({
name: 'spec',
outStream: process.stdout
});
await specReporter.init();
// Multiple reporters
const reporterPlugins = await Reporter.getReporterPlugins([
{ name: 'spec' },
{ name: 'json', outStream: fs.createWriteStream('results.json') }
]);
Custom Reporter Plugin
// Define custom reporter
const customReporter = {
async reportTaskStart(startTime, userAgents, testCount) {
this.write(`Starting ${testCount} tests\n`);
},
async reportFixtureStart(name, path, meta) {
this.write(`\nFixture: ${name}\n`);
this.setIndent(1);
},
async reportTestDone(name, testRunInfo, meta) {
const { errs, durationMs, skipped } = testRunInfo;
const status = skipped ? 'skipped' : (errs.length ? 'failed' : 'passed');
const icon = errs.length ? this.symbols.err : this.symbols.ok;
this.write(`${icon} ${name} (${durationMs}ms)\n`);
if (errs.length) {
errs.forEach(err => {
this.write(this.formatError(err, ' '));
});
}
},
async reportTaskDone(endTime, passed, warnings, result) {
this.setIndent(0);
this.write(`\nTotal: ${passed}/${result.passedCount + result.failedCount}\n`);
}
};
// Use custom reporter
const reporter = new Reporter({
name: 'custom',
plugin: customReporter,
outStream: process.stdout
});
ReporterPluginHost Utilities
// Inside reporter plugin methods, 'this' is a ReporterPluginHost
async reportTestDone(name, testRunInfo) {
// Set indentation level
this.setIndent(2);
// Write formatted output
this.write(this.chalk.green(`✓ ${name}`));
this.newline();
// Format errors with syntax highlighting
if (testRunInfo.errs.length) {
testRunInfo.errs.forEach(err => {
const formattedError = this.formatError(err, ' ');
this.write(formattedError);
});
}
// Access viewport width for wrapping
console.log(`Console width: ${this.viewportWidth}`);
// Use moment for timestamps
const timestamp = this.moment().format('HH:mm:ss');
this.write(`[${timestamp}] Test completed\n`);
}
Loading Built-in and External Reporters
// Load built-in reporter
const specPlugin = await Reporter.getReporterPlugins([
{ name: 'spec' }
]);
// Load external npm package reporter
const xunitPlugin = await Reporter.getReporterPlugins([
{ name: 'xunit', outStream: fs.createWriteStream('report.xml') }
]);
// Load custom module reporter
const customPlugin = await Reporter.getReporterPlugins([
{ name: require.resolve('./my-custom-reporter') }
]);
// Multiple reporters simultaneously
const reporters = await Reporter.getReporterPlugins([
{ name: 'spec' },
{ name: 'json', outStream: fs.createWriteStream('report.json') },
{ name: 'xunit', outStream: fs.createWriteStream('report.xml') }
]);
Error Formatting Example
// ReporterPluginHost.formatError() output
const error = new Error('Expected true but got false');
error.stack = `
at Test.fn (tests/example.js:15:5)
at Runner.run (src/runner.js:200:10)
`;
// formatError() produces ANSI-colored output:
//
// Error: Expected true but got false
// at Test.fn (tests/example.js:15:5)
// at Runner.run (src/runner.js:200:10)
//
// With:
// - Red colored error message
// - Grey colored stack trace
// - Underlined file paths
// - Syntax highlighting for code snippets