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 Service Lifecycle Hooks Pattern

From Leeroopedia

Template:Metadata

Overview

Interface specification for creating custom WebdriverIO services with lifecycle hooks.

Description

Custom services are JavaScript classes that implement lifecycle hook methods. The ServiceInstance interface from @wdio/types defines the available hooks. The constructor receives (options, capabilities, config). Services can be registered as class references or as [class, options] tuples.

The HookFunctions interface (which ServiceInstance extends) defines over 20 lifecycle hooks spanning the full test execution lifecycle. Services only need to implement the hooks they care about -- all hooks are optional.

Source Files

File Purpose Lines
examples/wdio/custom-service/my.custom.service.js Reference example service L1-75
packages/wdio-types/src/Services.ts TypeScript interface definitions L111-320
packages/wdio-utils/src/initializeServices.ts Service initialization logic L23-196

Code Reference

ServiceInstance Interface

// packages/wdio-types/src/Services.ts:L42-46
export interface ServiceInstance extends HookFunctions {
    options?: Record<string, any>
    capabilities?: WebdriverIO.Capabilities
    config?: TestrunnerOptions
}

HookFunctions Interface (Complete Specification)

// packages/wdio-types/src/Services.ts:L111-320
export interface HookFunctions {
    // === Launcher Hooks (run in main process) ===
    onPrepare?(
        config: TestrunnerOptions,
        capabilities: TestrunnerCapabilities
    ): unknown | Promise<unknown>

    onWorkerStart?(
        cid: string,
        capabilities: WebdriverIO.Capabilities,
        specs: string[],
        args: TestrunnerOptions,
        execArgv: string[]
    ): unknown | Promise<unknown>

    onWorkerEnd?(
        cid: string,
        exitCode: number,
        specs: string[],
        retries: number
    ): unknown | Promise<unknown>

    onComplete?(
        exitCode: number,
        config: Omit<TestrunnerOptions, 'capabilities'>,
        capabilities: TestrunnerCapabilities,
        results: any
    ): unknown | Promise<unknown>

    // === Worker Hooks (run in worker process) ===
    beforeSession?(
        config: Omit<TestrunnerOptions, 'capabilities'>,
        capabilities: RequestedStandaloneCapabilities | RequestedMultiremoteCapabilities,
        specs: string[],
        cid: string
    ): unknown | Promise<unknown>

    before?(
        capabilities: RequestedStandaloneCapabilities | RequestedMultiremoteCapabilities,
        specs: string[],
        browser: any
    ): unknown | Promise<unknown>

    beforeSuite?(suite: Suite): unknown | Promise<unknown>
    afterSuite?(suite: Suite): unknown | Promise<unknown>

    beforeTest?(test: Test, context: any): unknown | Promise<unknown>
    afterTest?(test: Test, context: any, result: TestResult): unknown | Promise<unknown>

    beforeHook?(test: any, context: any, hookName: string): unknown | Promise<unknown>
    afterHook?(test: Test, context: any, result: TestResult, hookName: string): unknown | Promise<unknown>

    beforeCommand?(commandName: string, args: any[]): unknown | Promise<unknown>
    afterCommand?(commandName: string, args: any[], result: any, error?: Error): unknown | Promise<unknown>

    beforeAssertion?(params: AssertionHookParams): unknown | Promise<unknown>
    afterAssertion?(params: AfterAssertionHookParams): unknown | Promise<unknown>

    after?(
        result: number,
        capabilities: RequestedStandaloneCapabilities | RequestedMultiremoteCapabilities,
        specs: string[]
    ): unknown | Promise<unknown>

    afterSession?(
        config: TestrunnerOptions,
        capabilities: WebdriverIO.Capabilities,
        specs: string[]
    ): unknown | Promise<unknown>

    // === Session Hooks ===
    onReload?(oldSessionId: string, newSessionId: string): unknown | Promise<unknown>
}

Full Example Service

// examples/wdio/custom-service/my.custom.service.js
export default class CustomService {
    constructor (config) {
        console.log('custom service options:', config.someOption)
    }
    onPrepare () {
        console.log('execute onPrepare(config, capabilities)')
    }
    onWorkerStart () {
        console.log('execute onWorkerStart(cid, caps, specs, args, execArgv)')
    }
    onWorkerEnd () {
        console.log('execute onWorkerEnd(cid, exitCode, specs, retries)')
    }
    beforeSession () {
        console.log('execute beforeSession(config, capabilities, specs, cid)')
    }
    before () {
        console.log('execute before(capabilities, specs)')
    }
    beforeSuite () {
        console.log('execute beforeSuite(suite)')
    }
    beforeHook () {
        console.log('execute beforeHook(test, context, hookName)')
    }
    afterHook () {
        console.log('execute afterHook(test, context, { error, result, duration, passed, retries }, hookName)')
    }
    beforeTest () {
        console.log('execute beforeTest(test, context)')
    }
    beforeCommand () {
        console.log('execute beforeCommand(commandName, args)')
    }
    afterCommand () {
        console.log('execute afterCommand(commandName, args, result, error)')
    }
    afterTest () {
        console.log('execute afterTest(test, context, { error, result, duration, passed, retries })')
    }
    afterSuite () {
        console.log('execute afterSuite(suite)')
    }
    after () {
        console.log('execute after(result, capabilities, specs)')
    }
    afterSession () {
        console.log('execute afterSession(config, capabilities, specs)')
    }
    onComplete () {
        console.log('execute onComplete(exitCode, config, capabilities, results)')
    }
    onReload() {
        console.log('execute onReload(oldSessionId, newSessionId)')
    }
    // Cucumber-specific hooks
    beforeFeature () {
        console.log('execute beforeFeature(uri, feature, scenarios)')
    }
    beforeScenario () {
        console.log('execute beforeScenario(uri, feature, scenario, sourceLocation, context)')
    }
    beforeStep () {
        console.log('execute beforeStep({ uri, feature, step }, context)')
    }
    afterStep () {
        console.log('execute afterStep({ uri, feature, step }, context, { error, result, duration, passed, retries })')
    }
    afterScenario () {
        console.log('execute afterScenario(uri, feature, scenario, result, sourceLocation, context)')
    }
    afterFeature () {
        console.log('execute afterFeature(uri, feature, scenarios)')
    }
}

I/O Contract

Constructor Inputs:

  • options (object) -- Service-specific options from the [ServiceClass, options] tuple
  • capabilities (Capabilities.ResolvedTestrunnerCapabilities) -- Resolved session capabilities
  • config (Options.Testrunner) -- Full WDIO configuration object

Hook Inputs: Each hook receives context-specific arguments as documented in the interface. Common patterns include:

  • Test object with title, parent, fullTitle, file properties
  • Result object with error, result, duration, passed, retries properties
  • Config and capabilities for session-level hooks

Hook Outputs: All hooks return unknown | Promise<unknown>. Return values are generally ignored by the framework -- hooks are used for side effects.

Registration

// wdio.conf.ts

// Class reference (most common for custom services)
services: [CustomService]

// Class reference with options
services: [[CustomService, { someOption: true }]]

// String name (resolved via initializePlugin)
services: ['browserstack']

// String name with options
services: [['sauce', { setJobName: true }]]

// Object literal (inline hooks)
services: [{ beforeTest: (test) => console.log(test.title) }]

Usage Examples

Service with launcher and worker separation:

// my-db-service.ts
class DBServiceLauncher {
    async onPrepare() {
        // Start test database -- runs once in the main process
        await startTestDatabase()
    }
    async onComplete() {
        // Clean up database -- runs once after all workers finish
        await stopTestDatabase()
    }
}

export default class DBServiceWorker {
    async before() {
        // Each worker connects to the database
        this.db = await connectToTestDatabase()
    }
    async beforeTest(test) {
        // Reset data before each test
        await this.db.reset()
    }
    async after() {
        await this.db.disconnect()
    }
}

// Expose launcher class
export const launcher = DBServiceLauncher

Related Pages

Page Connections

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