Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@wdio/local-runner

Package Overview
Dependencies
Maintainers
3
Versions
643
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wdio/local-runner - npm Package Compare versions

Comparing version 9.0.0-alpha.426 to 9.0.0

509

build/index.js

@@ -1,82 +0,437 @@

import logger from '@wdio/logger';
import { WritableStreamBuffer } from 'stream-buffers';
import WorkerInstance from './worker.js';
import { SHUTDOWN_TIMEOUT, BUFFER_OPTIONS } from './constants.js';
const log = logger('@wdio/local-runner');
export default class LocalRunner {
_options;
_config;
workerPool = {};
stdout = new WritableStreamBuffer(BUFFER_OPTIONS);
stderr = new WritableStreamBuffer(BUFFER_OPTIONS);
constructor(_options, _config) {
this._options = _options;
this._config = _config;
// src/index.ts
import logger2 from "@wdio/logger";
import { WritableStreamBuffer } from "stream-buffers";
// src/worker.ts
import url from "node:url";
import path from "node:path";
import child from "node:child_process";
import { EventEmitter } from "node:events";
import logger from "@wdio/logger";
// src/transformStream.ts
import split from "split2";
import { Transform } from "node:stream";
// src/constants.ts
var SHUTDOWN_TIMEOUT = 5e3;
var DEBUGGER_MESSAGES = [
"Debugger listening on",
"Debugger attached",
"Waiting for the debugger"
];
var BUFFER_OPTIONS = {
initialSize: 1e3 * 1024,
// start at 100 kilobytes.
incrementAmount: 100 * 1024
// grow by 10 kilobytes each time buffer overflows.
};
// src/transformStream.ts
function runnerTransformStream(cid, inputStream, aggregator) {
return inputStream.pipe(split(/\r?\n/, (line) => `${line}
`)).pipe(ignore(DEBUGGER_MESSAGES)).pipe(map((line) => {
const newLine = `[${cid}] ${line}`;
aggregator?.push(newLine);
return newLine;
}));
}
function ignore(patternsToIgnore) {
return new Transform({
decodeStrings: false,
transform(chunk, encoding, next) {
if (patternsToIgnore.some((m) => chunk.startsWith(m))) {
return next();
}
return next(null, chunk);
},
final(next) {
this.unpipe();
next();
}
/**
* nothing to initialize when running locally
*/
initialize() { }
getWorkerCount() {
return Object.keys(this.workerPool).length;
});
}
function map(mapper) {
return new Transform({
decodeStrings: false,
transform(chunk, encoding, next) {
return next(null, mapper(chunk));
},
final(next) {
this.unpipe();
next();
}
async run({ command, args, ...workerOptions }) {
/**
* adjust max listeners on stdout/stderr when creating listeners
*/
const workerCnt = this.getWorkerCount();
if (workerCnt >= process.stdout.getMaxListeners() - 2) {
process.stdout.setMaxListeners(workerCnt + 2);
process.stderr.setMaxListeners(workerCnt + 2);
});
}
// src/repl.ts
import WDIORepl from "@wdio/repl";
var WDIORunnerRepl = class extends WDIORepl {
childProcess;
callback;
commandIsRunning = false;
constructor(childProcess, options) {
super(options);
this.childProcess = childProcess;
}
_getError(params) {
if (!params.error) {
return null;
}
const err = new Error(params.message);
err.stack = params.stack;
return err;
}
eval(cmd, context, filename, callback) {
if (this.commandIsRunning) {
return;
}
this.commandIsRunning = true;
this.childProcess.send({
origin: "debugger",
name: "eval",
content: { cmd }
});
this.callback = callback;
}
onResult(params) {
const error = this._getError(params);
if (this.callback) {
this.callback(error, params.result);
}
this.commandIsRunning = false;
}
start(context) {
this.childProcess.send({
origin: "debugger",
name: "start"
});
return super.start(context);
}
};
// src/replQueue.ts
var ReplQueue = class {
_repls = [];
runningRepl;
add(childProcess, options, onStart, onEnd) {
this._repls.push({ childProcess, options, onStart, onEnd });
}
next() {
if (this.isRunning || this._repls.length === 0) {
return;
}
const nextRepl = this._repls.shift();
if (!nextRepl) {
return;
}
const { childProcess, options, onStart, onEnd } = nextRepl;
const runningRepl = this.runningRepl = new WDIORunnerRepl(childProcess, options);
onStart();
runningRepl.start().then(() => {
const ev = {
origin: "debugger",
name: "stop"
};
runningRepl.childProcess.send(ev);
onEnd(ev);
delete this.runningRepl;
this.next();
});
}
get isRunning() {
return Boolean(this.runningRepl);
}
};
// src/stdStream.ts
import { Transform as Transform2 } from "node:stream";
// src/utils.ts
function removeLastListener(target, eventName) {
const listener = target.listeners(eventName).reverse()[0];
if (listener) {
target.removeListener(eventName, listener);
}
}
// src/stdStream.ts
var RunnerStream = class extends Transform2 {
constructor() {
super();
this.on("pipe", () => {
removeLastListener(this, "close");
removeLastListener(this, "drain");
removeLastListener(this, "error");
removeLastListener(this, "finish");
removeLastListener(this, "unpipe");
});
}
_transform(chunk, encoding, callback) {
callback(void 0, chunk);
}
_final(callback) {
this.unpipe();
callback();
}
};
// src/worker.ts
var log = logger("@wdio/local-runner");
var replQueue = new ReplQueue();
var __dirname = path.dirname(url.fileURLToPath(import.meta.url));
var ACCEPTABLE_BUSY_COMMANDS = ["workerRequest", "endSession"];
var stdOutStream = new RunnerStream();
var stdErrStream = new RunnerStream();
stdOutStream.pipe(process.stdout);
stdErrStream.pipe(process.stderr);
var WorkerInstance = class extends EventEmitter {
cid;
config;
configFile;
// requestedCapabilities
caps;
// actual capabilities returned by driver
capabilities;
specs;
execArgv;
retries;
stdout;
stderr;
childProcess;
sessionId;
server;
logsAggregator = [];
instances;
isMultiremote;
isBusy = false;
isKilled = false;
isReady;
isSetup;
isReadyResolver = () => {
};
isSetupResolver = () => {
};
/**
* assigns paramters to scope of instance
* @param {object} config parsed configuration object
* @param {string} cid capability id (e.g. 0-1)
* @param {string} configFile path to config file (for sub process to parse)
* @param {object} caps capability object
* @param {string[]} specs list of paths to test files to run in this worker
* @param {number} retries number of retries remaining
* @param {object} execArgv execution arguments for the test run
*/
constructor(config, { cid, configFile, caps, specs, execArgv, retries }, stdout, stderr) {
super();
this.cid = cid;
this.config = config;
this.configFile = configFile;
this.caps = caps;
this.capabilities = caps;
this.specs = specs;
this.execArgv = execArgv;
this.retries = retries;
this.stdout = stdout;
this.stderr = stderr;
this.isReady = new Promise((resolve) => {
this.isReadyResolver = resolve;
});
this.isSetup = new Promise((resolve) => {
this.isSetupResolver = resolve;
});
}
/**
* spawns process to kick of wdio-runner
*/
startProcess() {
const { cid, execArgv } = this;
const argv = process.argv.slice(2);
const runnerEnv = Object.assign({
NODE_OPTIONS: "--enable-source-maps"
}, process.env, this.config.runnerEnv, {
WDIO_WORKER_ID: cid,
NODE_ENV: process.env.NODE_ENV || "test"
});
if (this.config.outputDir) {
runnerEnv.WDIO_LOG_PATH = path.join(this.config.outputDir, `wdio-${cid}.log`);
}
if (
/**
* autoCompile feature is enabled
*/
process.env.WDIO_LOAD_TSX === "1" && /**
* the `@wdio/cli` didn't already attached the loader to the environment
*/
!(process.env.NODE_OPTIONS || "").includes("--import tsx")
) {
runnerEnv.NODE_OPTIONS = (runnerEnv.NODE_OPTIONS || "") + " --import tsx";
}
log.info(`Start worker ${cid} with arg: ${argv}`);
const childProcess = this.childProcess = child.fork(path.join(__dirname, "run.js"), argv, {
cwd: process.cwd(),
env: runnerEnv,
execArgv,
stdio: ["inherit", "pipe", "pipe", "ipc"]
});
childProcess.on("message", this._handleMessage.bind(this));
childProcess.on("error", this._handleError.bind(this));
childProcess.on("exit", this._handleExit.bind(this));
if (!process.env.VITEST_WORKER_ID) {
if (childProcess.stdout !== null) {
if (this.config.groupLogsByTestSpec) {
runnerTransformStream(cid, childProcess.stdout, this.logsAggregator);
} else {
runnerTransformStream(cid, childProcess.stdout).pipe(stdOutStream);
}
const worker = new WorkerInstance(this._config, workerOptions, this.stdout, this.stderr);
this.workerPool[workerOptions.cid] = worker;
worker.postMessage(command, args);
return worker;
}
if (childProcess.stderr !== null) {
runnerTransformStream(cid, childProcess.stderr).pipe(stdErrStream);
}
}
/**
* shutdown all worker processes
*
* @return {Promise} resolves when all worker have been shutdown or
* a timeout was reached
*/
shutdown() {
log.info('Shutting down spawned worker');
for (const [cid, worker] of Object.entries(this.workerPool)) {
const { capabilities, server, sessionId, config, isMultiremote, instances } = worker;
let payload = {};
/**
* put connection information to payload if in watch mode
* in order to attach to browser session and kill it
*/
if (config && config.watch && (sessionId || isMultiremote)) {
payload = {
config: { ...server, sessionId, ...config },
capabilities,
watch: true,
isMultiremote,
instances
};
}
else if (!worker.isBusy) {
delete this.workerPool[cid];
continue;
}
worker.postMessage('endSession', payload);
return childProcess;
}
_handleMessage(payload) {
const { cid, childProcess } = this;
if (payload.name === "finishedCommand") {
this.isBusy = false;
}
if (payload.name === "ready") {
this.isReadyResolver(true);
}
if (payload.name === "sessionStarted") {
this.isSetupResolver(true);
if (payload.content.isMultiremote) {
Object.assign(this, payload.content);
} else {
this.sessionId = payload.content.sessionId;
this.capabilities = payload.content.capabilities;
Object.assign(this.config, payload.content);
}
}
if (childProcess && payload.origin === "debugger" && payload.name === "start") {
replQueue.add(
childProcess,
{ prompt: `[${cid}] \u203A `, ...payload.params },
() => this.emit("message", Object.assign(payload, { cid })),
(ev) => this.emit("message", ev)
);
return replQueue.next();
}
if (replQueue.isRunning && payload.origin === "debugger" && payload.name === "result") {
replQueue.runningRepl?.onResult(payload.params);
}
this.emit("message", Object.assign(payload, { cid }));
}
_handleError(payload) {
const { cid } = this;
this.emit("error", Object.assign(payload, { cid }));
}
_handleExit(exitCode) {
const { cid, childProcess, specs, retries } = this;
delete this.childProcess;
this.isBusy = false;
this.isKilled = true;
log.debug(`Runner ${cid} finished with exit code ${exitCode}`);
this.emit("exit", { cid, exitCode, specs, retries });
if (childProcess) {
childProcess.kill("SIGTERM");
}
}
/**
* sends message to sub process to execute functions in wdio-runner
* @param command method to run in wdio-runner
* @param args arguments for functions to call
*/
postMessage(command, args, requiresSetup = false) {
const { cid, configFile, capabilities, specs, retries, isBusy } = this;
if (isBusy && !ACCEPTABLE_BUSY_COMMANDS.includes(command)) {
return log.info(`worker with cid ${cid} already busy and can't take new commands`);
}
if (!this.childProcess) {
this.childProcess = this.startProcess();
}
const cmd = { cid, command, configFile, args, caps: capabilities, specs, retries };
log.debug(`Send command ${command} to worker with cid "${cid}"`);
this.isReady.then(async () => {
if (requiresSetup) {
await this.isSetup;
}
this.childProcess.send(cmd);
});
this.isBusy = true;
}
};
// src/index.ts
var log2 = logger2("@wdio/local-runner");
var LocalRunner = class {
constructor(_options, _config) {
this._options = _options;
this._config = _config;
}
workerPool = {};
stdout = new WritableStreamBuffer(BUFFER_OPTIONS);
stderr = new WritableStreamBuffer(BUFFER_OPTIONS);
/**
* nothing to initialize when running locally
*/
initialize() {
}
getWorkerCount() {
return Object.keys(this.workerPool).length;
}
async run({ command, args, ...workerOptions }) {
const workerCnt = this.getWorkerCount();
if (workerCnt >= process.stdout.getMaxListeners() - 2) {
process.stdout.setMaxListeners(workerCnt + 2);
process.stderr.setMaxListeners(workerCnt + 2);
}
const worker = new WorkerInstance(this._config, workerOptions, this.stdout, this.stderr);
this.workerPool[workerOptions.cid] = worker;
worker.postMessage(command, args);
return worker;
}
/**
* shutdown all worker processes
*
* @return {Promise} resolves when all worker have been shutdown or
* a timeout was reached
*/
shutdown() {
log2.info("Shutting down spawned worker");
for (const [cid, worker] of Object.entries(this.workerPool)) {
const { capabilities, server, sessionId, config, isMultiremote, instances } = worker;
let payload = {};
if (config && config.watch && (sessionId || isMultiremote)) {
payload = {
config: { ...server, sessionId, ...config },
capabilities,
watch: true,
isMultiremote,
instances
};
} else if (!worker.isBusy) {
delete this.workerPool[cid];
continue;
}
worker.postMessage("endSession", payload);
}
return new Promise((resolve) => {
const timeout = setTimeout(resolve, SHUTDOWN_TIMEOUT);
const interval = setInterval(() => {
const busyWorker = Object.entries(this.workerPool).filter(([, worker]) => worker.isBusy).length;
log2.info(`Waiting for ${busyWorker} to shut down gracefully`);
if (busyWorker === 0) {
clearTimeout(timeout);
clearInterval(interval);
log2.info("shutting down");
return resolve(true);
}
return new Promise((resolve) => {
const timeout = setTimeout(resolve, SHUTDOWN_TIMEOUT);
const interval = setInterval(() => {
const busyWorker = Object.entries(this.workerPool)
.filter(([, worker]) => worker.isBusy).length;
log.info(`Waiting for ${busyWorker} to shut down gracefully`);
if (busyWorker === 0) {
clearTimeout(timeout);
clearInterval(interval);
log.info('shutting down');
return resolve(true);
}
}, 250);
});
}
}
}, 250);
});
}
};
export {
LocalRunner as default
};

100

build/run.js

@@ -1,50 +0,62 @@

import exitHook from 'async-exit-hook';
import Runner from '@wdio/runner';
import logger from '@wdio/logger';
import { SHUTDOWN_TIMEOUT } from './constants.js';
const log = logger('@wdio/local-runner');
/**
* send ready event to testrunner to start receive command messages
*/
if (typeof process.send === 'function') {
process.send({
name: 'ready',
origin: 'worker'
});
// src/run.ts
import exitHook from "async-exit-hook";
import Runner from "@wdio/runner";
import logger from "@wdio/logger";
// src/constants.ts
var SHUTDOWN_TIMEOUT = 5e3;
var BUFFER_OPTIONS = {
initialSize: 1e3 * 1024,
// start at 100 kilobytes.
incrementAmount: 100 * 1024
// grow by 10 kilobytes each time buffer overflows.
};
// src/run.ts
var log = logger("@wdio/local-runner");
if (typeof process.send === "function") {
process.send({
name: "ready",
origin: "worker"
});
}
export const runner = new Runner();
runner.on('exit', process.exit.bind(process));
runner.on('error', ({ name, message, stack }) => process.send({
origin: 'worker',
name: 'error',
content: { name, message, stack }
var runner = new Runner();
runner.on("exit", process.exit.bind(process));
runner.on("error", ({ name, message, stack }) => process.send({
origin: "worker",
name: "error",
content: { name, message, stack }
}));
process.on('message', (m) => {
if (!m || !m.command || !runner[m.command]) {
return;
process.on("message", (m) => {
if (!m || !m.command || !runner[m.command]) {
return;
}
log.info(`Run worker command: ${m.command}`);
runner[m.command](m).then(
(result) => process.send({
origin: "worker",
name: "finishedCommand",
content: {
command: m.command,
result
}
}),
(e) => {
log.error(`Failed launching test session: ${e.stack}`);
setTimeout(() => process.exit(1), 10);
}
log.info(`Run worker command: ${m.command}`);
runner[m.command](m).then((result) => process.send({
origin: 'worker',
name: 'finishedCommand',
content: {
command: m.command,
result
}
}), (e) => {
log.error(`Failed launching test session: ${e.stack}`);
setTimeout(() => process.exit(1), 10);
});
);
});
/**
* catch sigint messages as they are handled by main process
*/
export const exitHookFn = (callback) => {
if (!callback) {
return;
}
runner.sigintWasCalled = true;
log.info(`Received SIGINT, giving process ${SHUTDOWN_TIMEOUT}ms to shutdown gracefully`);
setTimeout(callback, SHUTDOWN_TIMEOUT);
var exitHookFn = (callback) => {
if (!callback) {
return;
}
runner.sigintWasCalled = true;
log.info(`Received SIGINT, giving process ${SHUTDOWN_TIMEOUT}ms to shutdown gracefully`);
setTimeout(callback, SHUTDOWN_TIMEOUT);
};
exitHook(exitHookFn);
export {
exitHookFn,
runner
};
{
"name": "@wdio/local-runner",
"version": "9.0.0-alpha.426+d760644c4",
"version": "9.0.0",
"description": "A WebdriverIO runner to run tests locally",

@@ -28,4 +28,10 @@ "author": "Christian Bromann <mail@bromann.dev>",

"exports": {
".": "./build/index.js",
"./package.json": "./package.json"
".": {
"types": "./build/index.d.ts",
"import": "./build/index.js"
},
"./run": {
"source": "./src/run.ts",
"import": "./build/run.js"
}
},

@@ -35,6 +41,6 @@ "typeScriptVersion": "3.8.3",

"@types/node": "^20.1.0",
"@wdio/logger": "9.0.0-alpha.426+d760644c4",
"@wdio/repl": "9.0.0-alpha.426+d760644c4",
"@wdio/runner": "9.0.0-alpha.426+d760644c4",
"@wdio/types": "9.0.0-alpha.426+d760644c4",
"@wdio/logger": "9.0.0",
"@wdio/repl": "9.0.0",
"@wdio/runner": "9.0.0",
"@wdio/types": "9.0.0",
"async-exit-hook": "^2.0.1",

@@ -51,3 +57,3 @@ "split2": "^4.1.0",

},
"gitHead": "d760644c4c6e1ef910c0bee120cb422e25dbbe06"
"gitHead": "957693463371a4cb329395dcdbce8fb0c930ab93"
}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc