@wdio/local-runner
Advanced tools
Comparing version 9.0.0-alpha.426 to 9.0.0
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
29016
23
632
+ Added@babel/code-frame@7.26.2(transitive)
+ Added@babel/helper-validator-identifier@7.25.9(transitive)
+ Added@isaacs/cliui@8.0.2(transitive)
+ Added@jest/expect-utils@29.7.0(transitive)
+ Added@jest/schemas@29.6.3(transitive)
+ Added@jest/types@29.6.3(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@pkgjs/parseargs@0.11.0(transitive)
+ Added@promptbook/utils@0.69.5(transitive)
+ Added@puppeteer/browsers@2.6.1(transitive)
+ Added@sinclair/typebox@0.27.8(transitive)
+ Added@tootallnate/quickjs-emscripten@0.23.0(transitive)
+ Added@types/istanbul-lib-coverage@2.0.6(transitive)
+ Added@types/istanbul-lib-report@3.0.3(transitive)
+ Added@types/istanbul-reports@3.0.4(transitive)
+ Added@types/sinonjs__fake-timers@8.1.5(transitive)
+ Added@types/stack-utils@2.0.3(transitive)
+ Added@types/which@2.0.2(transitive)
+ Added@types/ws@8.5.13(transitive)
+ Added@types/yargs@17.0.33(transitive)
+ Added@types/yargs-parser@21.0.3(transitive)
+ Added@types/yauzl@2.10.3(transitive)
+ Added@vitest/snapshot@1.6.0(transitive)
+ Added@wdio/config@9.0.0(transitive)
+ Added@wdio/globals@9.0.0(transitive)
+ Added@wdio/logger@8.38.09.0.0(transitive)
+ Added@wdio/protocols@9.0.0(transitive)
+ Added@wdio/repl@9.0.0(transitive)
+ Added@wdio/runner@9.0.0(transitive)
+ Added@wdio/types@9.0.0(transitive)
+ Added@wdio/utils@9.0.0(transitive)
+ Added@zip.js/zip.js@2.7.54(transitive)
+ Addedabort-controller@3.0.0(transitive)
+ Addedagent-base@7.1.3(transitive)
+ Addedansi-regex@5.0.16.1.0(transitive)
+ Addedansi-styles@4.3.05.2.06.2.1(transitive)
+ Addedarchiver@7.0.1(transitive)
+ Addedarchiver-utils@5.0.2(transitive)
+ Addedaria-query@5.3.2(transitive)
+ Addedast-types@0.13.4(transitive)
+ Addedasync@3.2.6(transitive)
+ Addedb4a@1.6.7(transitive)
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbare-events@2.5.0(transitive)
+ Addedbare-fs@2.3.5(transitive)
+ Addedbare-os@2.4.4(transitive)
+ Addedbare-path@2.1.3(transitive)
+ Addedbare-stream@2.6.1(transitive)
+ Addedbase64-js@1.5.1(transitive)
+ Addedbasic-ftp@5.0.5(transitive)
+ Addedboolbase@1.0.0(transitive)
+ Addedbrace-expansion@1.1.112.0.1(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedbuffer@5.7.16.0.3(transitive)
+ Addedbuffer-crc32@0.2.131.0.0(transitive)
+ Addedchalk@4.1.25.4.1(transitive)
+ Addedcheerio@1.0.0(transitive)
+ Addedcheerio-select@2.1.0(transitive)
+ Addedci-info@3.9.0(transitive)
+ Addedcliui@8.0.1(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addedcommander@9.5.0(transitive)
+ Addedcompress-commons@6.0.2(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedcrc-32@1.2.2(transitive)
+ Addedcrc32-stream@6.0.0(transitive)
+ Addedcross-spawn@7.0.6(transitive)
+ Addedcss-select@5.1.0(transitive)
+ Addedcss-shorthand-properties@1.1.2(transitive)
+ Addedcss-value@0.0.1(transitive)
+ Addedcss-what@6.1.0(transitive)
+ Addeddata-uri-to-buffer@4.0.16.0.2(transitive)
+ Addeddebug@4.4.0(transitive)
+ Addeddecamelize@6.0.0(transitive)
+ Addeddeepmerge-ts@7.1.3(transitive)
+ Addeddegenerator@5.0.1(transitive)
+ Addeddiff-sequences@29.6.3(transitive)
+ Addeddom-serializer@2.0.0(transitive)
+ Addeddomelementtype@2.3.0(transitive)
+ Addeddomhandler@5.0.3(transitive)
+ Addeddomutils@3.2.1(transitive)
+ Addedeastasianwidth@0.2.0(transitive)
+ Addededge-paths@3.0.5(transitive)
+ Addededgedriver@5.6.1(transitive)
+ Addedemoji-regex@8.0.09.2.2(transitive)
+ Addedencoding-sniffer@0.2.0(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addedentities@4.5.0(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedescape-string-regexp@2.0.0(transitive)
+ Addedescodegen@2.1.0(transitive)
+ Addedesprima@4.0.1(transitive)
+ Addedestraverse@5.3.0(transitive)
+ Addedesutils@2.0.3(transitive)
+ Addedevent-target-shim@5.0.1(transitive)
+ Addedevents@3.3.0(transitive)
+ Addedexpect@29.7.0(transitive)
+ Addedexpect-webdriverio@5.0.0-alpha.2(transitive)
+ Addedextract-zip@2.0.1(transitive)
+ Addedfast-deep-equal@2.0.1(transitive)
+ Addedfast-fifo@1.3.2(transitive)
+ Addedfast-xml-parser@4.5.1(transitive)
+ Addedfd-slicer@1.1.0(transitive)
+ Addedfetch-blob@3.2.0(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedforeground-child@3.3.0(transitive)
+ Addedformdata-polyfill@4.0.10(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedgaze@1.1.3(transitive)
+ Addedgeckodriver@4.5.1(transitive)
+ Addedget-caller-file@2.0.5(transitive)
+ Addedget-port@7.1.0(transitive)
+ Addedget-stream@5.2.0(transitive)
+ Addedget-uri@6.0.4(transitive)
+ Addedglob@10.4.57.1.7(transitive)
+ Addedglobule@1.3.4(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedgrapheme-splitter@1.0.4(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedhtmlfy@0.2.1(transitive)
+ Addedhtmlparser2@9.1.0(transitive)
+ Addedhttp-proxy-agent@7.0.2(transitive)
+ Addedhttps-proxy-agent@7.0.6(transitive)
+ Addediconv-lite@0.6.3(transitive)
+ Addedieee754@1.2.1(transitive)
+ Addedimmediate@3.0.6(transitive)
+ Addedimport-meta-resolve@4.1.0(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedip-address@9.0.5(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addedis-plain-obj@4.1.0(transitive)
+ Addedis-stream@2.0.1(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisexe@2.0.03.1.1(transitive)
+ Addedjackspeak@3.4.3(transitive)
+ Addedjest-diff@29.7.0(transitive)
+ Addedjest-get-type@29.6.3(transitive)
+ Addedjest-matcher-utils@29.7.0(transitive)
+ Addedjest-message-util@29.7.0(transitive)
+ Addedjest-util@29.7.0(transitive)
+ Addedjs-tokens@4.0.0(transitive)
+ Addedjsbn@1.1.0(transitive)
+ Addedjszip@3.10.1(transitive)
+ Addedlazystream@1.0.1(transitive)
+ Addedlie@3.3.0(transitive)
+ Addedlocate-app@2.5.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlodash.clonedeep@4.5.0(transitive)
+ Addedlodash.isequal@4.5.0(transitive)
+ Addedlodash.zip@4.2.0(transitive)
+ Addedloglevel@1.9.2(transitive)
+ Addedloglevel-plugin-prefix@0.8.4(transitive)
+ Addedlru-cache@10.4.37.18.3(transitive)
+ Addedmagic-string@0.30.17(transitive)
+ Addedmicromatch@4.0.8(transitive)
+ Addedminimatch@3.0.85.1.69.0.5(transitive)
+ Addedminipass@7.1.2(transitive)
+ Addedms@2.1.3(transitive)
+ Addednetmask@2.0.2(transitive)
+ Addednode-domexception@1.0.0(transitive)
+ Addednode-fetch@3.3.2(transitive)
+ Addednormalize-path@3.0.0(transitive)
+ Addednth-check@2.1.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpac-proxy-agent@7.1.0(transitive)
+ Addedpac-resolver@7.0.1(transitive)
+ Addedpackage-json-from-dist@1.0.1(transitive)
+ Addedpako@1.0.11(transitive)
+ Addedparse5@7.2.1(transitive)
+ Addedparse5-htmlparser2-tree-adapter@7.1.0(transitive)
+ Addedparse5-parser-stream@7.1.2(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-key@3.1.1(transitive)
+ Addedpath-scurry@1.11.1(transitive)
+ Addedpathe@1.1.2(transitive)
+ Addedpend@1.2.0(transitive)
+ Addedpicocolors@1.1.1(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedpretty-format@29.7.0(transitive)
+ Addedprocess@0.11.10(transitive)
+ Addedprocess-nextick-args@2.0.1(transitive)
+ Addedprogress@2.0.3(transitive)
+ Addedproxy-agent@6.5.0(transitive)
+ Addedproxy-from-env@1.1.0(transitive)
+ Addedpump@3.0.2(transitive)
+ Addedquery-selector-shadow-dom@1.0.1(transitive)
+ Addedqueue-tick@1.0.1(transitive)
+ Addedreact-is@18.3.1(transitive)
+ Addedreadable-stream@2.3.84.6.0(transitive)
+ Addedreaddir-glob@1.1.3(transitive)
+ Addedrequire-directory@2.1.1(transitive)
+ Addedresq@1.11.0(transitive)
+ Addedrgb2hex@0.2.5(transitive)
+ Addedsafaridriver@0.1.2(transitive)
+ Addedsafe-buffer@5.1.25.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsemver@7.6.3(transitive)
+ Addedserialize-error@11.0.3(transitive)
+ Addedsetimmediate@1.0.5(transitive)
+ Addedshebang-command@2.0.0(transitive)
+ Addedshebang-regex@3.0.0(transitive)
+ Addedsignal-exit@4.1.0(transitive)
+ Addedslash@3.0.0(transitive)
+ Addedsmart-buffer@4.2.0(transitive)
+ Addedsocks@2.8.3(transitive)
+ Addedsocks-proxy-agent@8.0.5(transitive)
+ Addedsource-map@0.6.1(transitive)
+ Addedspacetrim@0.11.59(transitive)
+ Addedsprintf-js@1.1.3(transitive)
+ Addedstack-utils@2.0.6(transitive)
+ Addedstreamx@2.21.1(transitive)
+ Addedstring-width@4.2.35.1.2(transitive)
+ Addedstring_decoder@1.1.11.3.0(transitive)
+ Addedstrip-ansi@6.0.17.1.0(transitive)
+ Addedstrnum@1.0.5(transitive)
+ Addedsupports-color@7.2.0(transitive)
+ Addedtar-fs@3.0.6(transitive)
+ Addedtar-stream@3.1.7(transitive)
+ Addedtext-decoder@1.2.3(transitive)
+ Addedthrough@2.3.8(transitive)
+ Addedto-regex-range@5.0.1(transitive)
+ Addedtslib@2.8.1(transitive)
+ Addedtype-fest@2.19.04.26.0(transitive)
+ Addedunbzip2-stream@1.4.3(transitive)
+ Addedundici@6.21.0(transitive)
+ Addedurlpattern-polyfill@10.0.0(transitive)
+ Addeduserhome@1.0.1(transitive)
+ Addedutil-deprecate@1.0.2(transitive)
+ Addedwait-port@1.1.0(transitive)
+ Addedweb-streams-polyfill@3.3.3(transitive)
+ Addedwebdriver@9.0.0(transitive)
+ Addedwebdriverio@9.0.0(transitive)
+ Addedwhatwg-encoding@3.1.1(transitive)
+ Addedwhatwg-mimetype@4.0.0(transitive)
+ Addedwhich@2.0.24.0.0(transitive)
+ Addedwrap-ansi@7.0.08.1.0(transitive)
+ Addedwrappy@1.0.2(transitive)
+ Addedws@8.18.0(transitive)
+ Addedy18n@5.0.8(transitive)
+ Addedyargs@17.7.2(transitive)
+ Addedyargs-parser@21.1.1(transitive)
+ Addedyauzl@2.10.0(transitive)
+ Addedzip-stream@6.0.1(transitive)
Updated@wdio/logger@9.0.0
Updated@wdio/repl@9.0.0
Updated@wdio/runner@9.0.0
Updated@wdio/types@9.0.0