A WebdriverIO runner to run tests locally within isolated worker processes
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
WebdriverIO 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;
}