A WebdriverIO runner to run tests locally within isolated worker processes
npx @tessl/cli install tessl/npm-wdio--local-runner@9.19.0WebdriverIO Local Runner executes test files locally within isolated worker processes to achieve maximum concurrency and test isolation. It spawns a separate worker process per capability, with each worker maintaining its own browser session for complete test isolation.
npm install --save-dev @wdio/local-runnerimport LocalRunner from "@wdio/local-runner";
import type { WorkerInstance, RunArgs } from "@wdio/local-runner";
// Worker process runner (separate entry point)
import { runner } from "@wdio/local-runner/run";For CommonJS:
const LocalRunner = require("@wdio/local-runner");
const { runner } = require("@wdio/local-runner/run");import LocalRunner from "@wdio/local-runner";
// Initialize the runner with WebdriverIO config
const runner = new LocalRunner({} as never, {
autoXvfb: true,
outputDir: "./logs",
runnerEnv: { NODE_ENV: "test" }
} as WebdriverIO.Config);
// Initialize the runner
await runner.initialize();
// Run a test worker
const worker = await runner.run({
cid: "0-0",
command: "run",
configFile: "/path/to/wdio.conf.js",
args: {},
caps: { browserName: "chrome" },
specs: ["./test/example.spec.js"],
execArgv: [],
retries: 0
});
// Shutdown all workers when done
await runner.shutdown();WebdriverIO Local Runner is built around several key components:
Core runner functionality for managing worker processes and coordinating test execution across multiple isolated environments.
class LocalRunner {
workerPool: Record<string, WorkerInstance>;
stdout: WritableStreamBuffer;
stderr: WritableStreamBuffer;
constructor(options: never, config: WebdriverIO.Config);
initialize(): Promise<void>;
getWorkerCount(): number;
run(args: RunArgs): Promise<WorkerInstance>;
shutdown(): Promise<boolean>;
}
interface RunArgs extends Workers.WorkerRunPayload {
command: string;
args: Workers.WorkerMessageArgs;
cid: string;
}Individual worker process management with lifecycle control, message passing, and browser session handling.
class WorkerInstance extends EventEmitter implements Workers.Worker {
cid: string;
config: WebdriverIO.Config;
configFile: string;
caps: WebdriverIO.Capabilities;
capabilities: WebdriverIO.Capabilities;
specs: string[];
execArgv: string[];
retries: number;
stdout: WritableStreamBuffer;
stderr: WritableStreamBuffer;
childProcess?: ChildProcess;
sessionId?: string;
server?: Record<string, string>;
instances?: Record<string, { sessionId: string }>;
isMultiremote?: boolean;
isBusy: boolean;
isKilled: boolean;
isReady: Promise<boolean>;
isSetup: Promise<boolean>;
constructor(
config: WebdriverIO.Config,
payload: Workers.WorkerRunPayload,
stdout: WritableStreamBuffer,
stderr: WritableStreamBuffer,
xvfbManager: XvfbManager
);
startProcess(): Promise<ChildProcess>;
postMessage(
command: string,
args: Workers.WorkerMessageArgs,
requiresSetup?: boolean
): Promise<void>;
}Worker child process executor that runs the actual test framework in isolated processes.
// Exported from './run' entry point
interface RunnerInterface extends NodeJS.EventEmitter {
sigintWasCalled: boolean;
[key: string]: unknown;
}
const runner: RunnerInterface;Advanced stream handling, output transformation, and interactive debugging capabilities.
function runnerTransformStream(
cid: string,
inputStream: Readable,
aggregator?: string[]
): Readable;
class ReplQueue {
isRunning: boolean;
add(childProcess: ChildProcess, config: ReplConfig, onStart: () => void, onResult: (ev: unknown) => void): void;
next(): void;
}
const SHUTDOWN_TIMEOUT: 5000;
const DEBUGGER_MESSAGES: string[];
const BUFFER_OPTIONS: { initialSize: number; incrementAmount: number; };Stream Management and Debugging
interface Workers.WorkerRunPayload {
cid: string;
configFile: string;
caps: WebdriverIO.Capabilities;
specs: string[];
execArgv: string[];
retries: number;
}
interface Workers.WorkerMessageArgs {
[key: string]: any;
}
interface Workers.WorkerMessage {
name: string;
origin: string;
content?: any;
params?: any;
}
interface Workers.WorkerCommand {
cid: string;
command: string;
configFile: string;
args: Workers.WorkerMessageArgs;
caps: WebdriverIO.Capabilities;
specs: string[];
retries: number;
}
interface Workers.Worker {
cid: string;
isBusy: boolean;
// Additional worker interface properties
}
interface WebdriverIO.Config {
autoXvfb?: boolean;
xvfbAutoInstall?: string | boolean | object;
xvfbAutoInstallMode?: string;
xvfbAutoInstallCommand?: string;
xvfbMaxRetries?: number;
xvfbRetryDelay?: number;
outputDir?: string;
runnerEnv?: Record<string, any>;
watch?: boolean;
groupLogsByTestSpec?: boolean;
}
interface WebdriverIO.Capabilities {
browserName?: string;
[key: string]: any;
}
// External dependency types
interface WritableStreamBuffer {
// From 'stream-buffers' package
getContents(): Buffer;
}
interface XvfbManager {
// From '@wdio/xvfb' package
init(capabilities: WebdriverIO.Capabilities): Promise<boolean>;
shouldRun(): boolean;
}
interface ProcessFactory {
// From '@wdio/xvfb' package
createWorkerProcess(
scriptPath: string,
args: string[],
options: {
cwd: string;
env: Record<string, string>;
execArgv: string[];
stdio: any[];
}
): Promise<ChildProcess>;
}
interface ReplConfig {
// From '@wdio/repl' package
prompt?: string;
[key: string]: any;
}
interface ChildProcess {
// From Node.js 'child_process' module
pid?: number;
stdout: Readable | null;
stderr: Readable | null;
send(message: any): boolean;
kill(signal?: string): boolean;
on(event: string, listener: Function): this;
}
interface Readable {
// From Node.js 'stream' module
pipe(destination: any): any;
on(event: string, listener: Function): this;
}
interface Transform extends Readable {
// From Node.js 'stream' module
}
class EventEmitter {
// From Node.js 'events' module
on(event: string, listener: Function): this;
emit(event: string, ...args: any[]): boolean;
}