A WebdriverIO runner to run tests locally within isolated worker processes
—
Core runner functionality for managing worker processes and coordinating test execution across multiple isolated environments.
Main class that manages the worker pool, handles Xvfb initialization, and coordinates test execution.
/**
* Main runner class for managing worker processes
*/
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>;
}Creates a new LocalRunner instance with configuration.
/**
* Create a LocalRunner instance
* @param options - First parameter (unused, pass {} as never)
* @param config - WebdriverIO configuration object
*/
constructor(options: never, config: WebdriverIO.Config);Usage Example:
import LocalRunner from "@wdio/local-runner";
const runner = new LocalRunner({} as never, {
autoXvfb: true,
outputDir: "./logs",
runnerEnv: { NODE_ENV: "test" },
xvfbAutoInstall: true,
groupLogsByTestSpec: false
} as WebdriverIO.Config);Initialize the local runner environment. This method performs minimal setup as Xvfb initialization is handled lazily during the first worker creation.
/**
* Initialize local runner environment
* @returns Promise that resolves when initialization is complete
*/
initialize(): Promise<void>;Usage Example:
await runner.initialize();
console.log('Runner initialized');Get the number of currently active worker processes.
/**
* Get count of active workers in the pool
* @returns Number of active worker instances
*/
getWorkerCount(): number;Usage Example:
const activeWorkers = runner.getWorkerCount();
console.log(`Currently running ${activeWorkers} workers`);Create and start a new worker process to execute tests.
/**
* Create and run a worker process
* @param args - Run arguments including command, capabilities, and specs
* @returns Promise resolving to WorkerInstance
*/
run(args: RunArgs): Promise<WorkerInstance>;Usage Example:
const worker = await runner.run({
cid: "0-0",
command: "run",
configFile: "/path/to/wdio.conf.js",
args: {},
caps: {
browserName: "chrome",
"goog:chromeOptions": { args: ["--headless"] }
},
specs: ["./test/login.spec.js", "./test/dashboard.spec.js"],
execArgv: [],
retries: 2
});
// Worker will be added to workerPool automatically
console.log(`Started worker ${worker.cid}`);Shutdown all worker processes with graceful timeout handling.
/**
* Shutdown all worker processes
* @returns Promise resolving to boolean indicating successful shutdown
*/
shutdown(): Promise<boolean>;Usage Example:
// Shutdown all workers (waits up to 5 seconds for graceful shutdown)
const shutdownSuccess = await runner.shutdown();
if (shutdownSuccess) {
console.log('All workers shut down successfully');
} else {
console.log('Some workers did not shut down gracefully');
}Virtual display configuration for headless browser testing.
interface XvfbConfig {
autoXvfb?: boolean; // Enable/disable Xvfb (default: enabled)
xvfbAutoInstall?: string | boolean | {
mode: string;
command: string;
}; // Auto-install Xvfb if missing
xvfbAutoInstallMode?: string; // Installation mode (e.g., 'sudo')
xvfbAutoInstallCommand?: string; // Custom installation command
xvfbMaxRetries?: number; // Maximum retry attempts
xvfbRetryDelay?: number; // Delay between retry attempts
}Configuration for worker output and logging.
interface OutputConfig {
outputDir?: string; // Directory for log files
runnerEnv?: Record<string, any>; // Environment variables for workers
groupLogsByTestSpec?: boolean; // Group logs by test specification
}Configuration for file watching and session reuse.
interface WatchConfig {
watch?: boolean; // Enable watch mode
}Xvfb initialization failures are handled gracefully and logged as warnings.
// Xvfb errors don't cause runner failure
const runner = new LocalRunner({} as never, { autoXvfb: true });
try {
await runner.run({
cid: "0-0",
command: "run",
// ... other options
});
} catch (error) {
// Xvfb errors won't cause this to throw
console.log('Worker started despite Xvfb issues');
}Worker shutdown includes timeout handling to prevent hanging processes.
// Shutdown will resolve after 5 seconds maximum
const shutdownPromise = runner.shutdown();
// You can also handle partial shutdown scenarios
const success = await shutdownPromise;
if (!success) {
console.log('Some workers required forceful termination');
}const runner = new LocalRunner({} as never, config);
// Start multiple workers for different browser capabilities
const chromeWorker = await runner.run({
cid: "chrome-0",
command: "run",
caps: { browserName: "chrome" },
specs: ["./test/chrome-specific.spec.js"],
// ... other options
});
const firefoxWorker = await runner.run({
cid: "firefox-0",
command: "run",
caps: { browserName: "firefox" },
specs: ["./test/firefox-specific.spec.js"],
// ... other options
});
console.log(`Running ${runner.getWorkerCount()} workers`); // 2const runner = new LocalRunner({} as never, {
runnerEnv: {
NODE_ENV: "test",
API_BASE_URL: "https://staging.example.com",
FORCE_COLOR: "1"
},
outputDir: "./test-logs"
});Install with Tessl CLI
npx tessl i tessl/npm-wdio--local-runner