Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:DevExpress Testcafe Reporter Plugin System

From Leeroopedia
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

Related Pages

Implements Principle

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment