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:Webdriverio Webdriverio WDIOReporter Base Class

From Leeroopedia
Revision as of 11:58, 16 February 2026 by Admin (talk | contribs) (Auto-imported from implementations/Webdriverio_Webdriverio_WDIOReporter_Base_Class.md)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Template:Metadata

Overview

Concrete base class for creating custom test reporters provided by the @wdio/reporter package.

Description

WDIOReporter is an EventEmitter subclass that manages event subscription, output streams, and test statistics. Custom reporters extend this class, override event handler methods, and use this.write() for output. The base class tracks suites, tests, hooks, counts, and failure information.

The class handles all the boilerplate of reporter development:

  • Subscribes to all test lifecycle events in the constructor
  • Creates SuiteStats, TestStats, HookStats, and RunnerStats objects from raw event data
  • Maintains a currentSuites stack for hierarchical suite tracking
  • Manages output stream creation and cleanup (including deleting empty log files)
  • Provides the isSynchronised getter for async reporters that need to delay process shutdown

Source Files

File Purpose Lines
packages/wdio-reporter/src/index.ts WDIOReporter base class L18-295
examples/wdio/custom-reporter/my.custom.reporter.js Reference example reporter L1-27

Code Reference

Class Signature

// packages/wdio-reporter/src/index.ts:L18-295
import { EventEmitter } from 'node:events'
import type { WriteStream } from 'node:fs'
import type { Reporters, Options } from '@wdio/types'

type CustomWriteStream = { write: (content: unknown) => boolean }

export default class WDIOReporter extends EventEmitter {
    outputStream: WriteStream | CustomWriteStream
    failures: number = 0
    suites: Record<string, SuiteStats> = {}
    hooks: Record<string, HookStats> = {}
    tests: Record<string, TestStats> = {}
    currentSuites: SuiteStats[] = []
    counts = {
        suites: 0,
        tests: 0,
        hooks: 0,
        passes: 0,
        skipping: 0,
        failures: 0,
        pending: 0
    }
    retries: number = 0
    runnerStat?: RunnerStats
    isContentPresent: boolean = false
    specs: string[] = []
    currentSpec?: string

    constructor(public options: Partial<Reporters.Options>)

    /**
     * Getter to indicate if async work is complete.
     * Override in subclass to delay process shutdown.
     */
    get isSynchronised(): boolean

    /**
     * Write content to the reporter output stream.
     */
    write(content: unknown): void

    // === Overridable Event Handlers ===
    onRunnerStart(_runnerStats: RunnerStats): void
    onBeforeCommand(_commandArgs: BeforeCommandArgs): void
    onAfterCommand(_commandArgs: AfterCommandArgs): void
    onBeforeAssertion(_assertionArgs: unknown): void
    onAfterAssertion(_assertionArgs: unknown): void
    onSuiteStart(_suiteStats: SuiteStats): void
    onHookStart(_hookStat: HookStats): void
    onHookEnd(_hookStats: HookStats): void
    onTestStart(_testStats: TestStats): void
    onTestPass(_testStats: TestStats): void
    onTestFail(_testStats: TestStats): void
    onTestRetry(_testStats: TestStats): void
    onTestSkip(_testStats: TestStats): void
    onTestPending(_testStats: TestStats): void
    onTestEnd(_testStats: TestStats): void
    onSuiteRetry(_suiteStats: SuiteStats): void
    onSuiteEnd(_suiteStats: SuiteStats): void
    onRunnerEnd(_runnerStats: RunnerStats): void
}

Constructor Implementation

// packages/wdio-reporter/src/index.ts:L40-258
constructor(public options: Partial<Reporters.Options>) {
    super()

    // Ensure the report directory exists
    if (this.options.outputDir) {
        fs.mkdirSync(this.options.outputDir, { recursive: true })
    }

    // Configure output stream: use writeStream if stdout mode, else create file
    this.outputStream = (this.options.stdout || !this.options.logFile)
        && this.options.writeStream
        ? this.options.writeStream as CustomWriteStream
        : fs.createWriteStream(this.options.logFile!)

    // Subscribe to all lifecycle events
    this.on('runner:start', (runner) => { /* create RunnerStats, call onRunnerStart */ })
    this.on('suite:start', (params) => { /* create SuiteStats, call onSuiteStart */ })
    this.on('hook:start', (hook) => { /* create HookStats, call onHookStart */ })
    this.on('hook:end', (hook) => { /* update HookStats, call onHookEnd */ })
    this.on('test:start', (test) => { /* create TestStats, call onTestStart */ })
    this.on('test:pass', (test) => { /* update TestStats, call onTestPass */ })
    this.on('test:fail', (test) => { /* update TestStats, call onTestFail */ })
    this.on('test:retry', (test) => { /* update TestStats, call onTestRetry */ })
    this.on('test:pending', (test) => { /* update TestStats, call onTestSkip */ })
    this.on('test:end', (test) => { /* call onTestEnd */ })
    this.on('suite:end', (suite) => { /* complete SuiteStats, call onSuiteEnd */ })
    this.on('runner:end', (runner) => { /* complete RunnerStats, call onRunnerEnd, clean up empty files */ })

    // Command events
    this.on('client:beforeCommand', (payload) => { /* append to currentTest.output */ })
    this.on('client:afterCommand', (payload) => { /* append to currentTest.output */ })
}

write() Method

// packages/wdio-reporter/src/index.ts:L271-276
write(content: unknown) {
    if (content) {
        this.isContentPresent = true
    }
    this.outputStream.write(content)
}

Full Example Custom Reporter

// examples/wdio/custom-reporter/my.custom.reporter.js
import WDIOReporter from '@wdio/reporter'

export default class CustomReporter extends WDIOReporter {
    constructor(options) {
        super(options)
        console.log('initialized custom reporter with the following reporter options:', options)
        this.write('Some log line')
    }

    onRunnerStart() { console.log('onRunnerStart') }
    onBeforeCommand() { console.log('onBeforeCommand') }
    onAfterCommand() { console.log('onAfterCommand') }
    onSuiteStart() { console.log('onSuiteStart') }
    onHookStart() { console.log('onHookStart') }
    onHookEnd() { console.log('onHookEnd') }
    onTestStart() { console.log('onTestStart') }
    onTestPass() { console.log('onTestPass') }
    onTestFail() { console.log('onTestFail') }
    onTestRetry() { console.log('onTestRetry') }
    onTestSkip() { console.log('onTestSkip') }
    onTestEnd() { console.log('onTestEnd') }
    onSuiteEnd() { console.log('onSuiteEnd') }
    onRunnerEnd() { console.log('onRunnerEnd') }
}

I/O Contract

Import:

import WDIOReporter from '@wdio/reporter'

Constructor Options:

Option Type Description
outputDir string Directory for report files (created automatically if missing)
logFile string Full path to the log file for this reporter
stdout boolean If true, use the provided writeStream instead of file output
writeStream WriteStream Custom write stream for output (used when stdout is true)

Inputs to Event Handlers:

  • RunnerStats -- Session ID, capabilities, specs, config, timing, failures, retries
  • SuiteStats -- Title, file, full title, duration, child tests/hooks, parent suite
  • TestStats -- Title, full title, duration, state, errors, output (commands), retries
  • HookStats -- Title, duration, errors, parent suite

Outputs:

  • Content written via this.write() to the configured output stream
  • File output cleaned up automatically if no content was written

Synchronization:

  • Override get isSynchronised() to return false while async operations are pending
  • The test runner will wait for isSynchronised to return true before exiting

Registration

// wdio.conf.ts

// Class reference
reporters: [CustomReporter]

// Class reference with options
reporters: [[CustomReporter, { outputDir: './reports' }]]

// String name (resolved via initializePlugin)
reporters: ['allure']

// String name with options
reporters: [['junit', { outputDir: './junit-reports', outputFileFormat: (opts) => `results-${opts.cid}.xml` }]]

Usage Examples

Async reporter with delayed shutdown:

import WDIOReporter from '@wdio/reporter'

export default class AsyncReporter extends WDIOReporter {
    constructor(options) {
        super(options)
        this._pendingUploads = 0
    }

    get isSynchronised() {
        // Delay process exit until all uploads complete
        return this._pendingUploads === 0
    }

    onTestFail(testStats) {
        this._pendingUploads++
        fetch('https://my-dashboard.example.com/api/failures', {
            method: 'POST',
            body: JSON.stringify({
                test: testStats.fullTitle,
                error: testStats.errors?.[0]?.message,
                duration: testStats.duration
            })
        }).finally(() => {
            this._pendingUploads--
        })
    }

    onRunnerEnd(runnerStats) {
        this.write(JSON.stringify({
            specs: this.specs,
            passes: this.counts.passes,
            failures: this.counts.failures,
            skipped: this.counts.skipping
        }))
    }
}

Reporter that generates a summary file:

import WDIOReporter from '@wdio/reporter'

export default class SummaryReporter extends WDIOReporter {
    constructor(options) {
        super(options)
        this.results = []
    }

    onTestPass(testStats) {
        this.results.push({ title: testStats.title, status: 'passed', duration: testStats.duration })
    }

    onTestFail(testStats) {
        this.results.push({ title: testStats.title, status: 'failed', error: testStats.errors?.[0]?.message })
    }

    onRunnerEnd() {
        this.write(JSON.stringify(this.results, null, 2))
    }
}

Related Pages

Page Connections

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