Implementation:Webdriverio Webdriverio Service Lifecycle Hooks Pattern
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]tuplecapabilities(Capabilities.ResolvedTestrunnerCapabilities) -- Resolved session capabilitiesconfig(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,fileproperties - Result object with
error,result,duration,passed,retriesproperties - 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