@wdio/local-runner 9.32.0-alpha.6 → 10.0.0-alpha.149
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +31 -0
- package/build/index.d.ts +7 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +182 -63
- package/build/worker.d.ts +14 -7
- package/build/worker.d.ts.map +1 -1
- package/package.json +12 -11
package/AGENTS.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @wdio/local-runner
|
|
2
|
+
|
|
3
|
+
Forks one worker process per capability and owns its lifetime: ready/session
|
|
4
|
+
IPC, shutdown, the display daemon, and the in-worker REPL queue.
|
|
5
|
+
|
|
6
|
+
## Commands
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm run dev @wdio/local-runner
|
|
10
|
+
pnpm run test:package wdio-local-runner
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Display-daemon changes also need `pnpm run test:e2e:display-server` on a Linux
|
|
14
|
+
host with no display. Elsewhere, leave that lane to CI.
|
|
15
|
+
|
|
16
|
+
## Test ownership
|
|
17
|
+
|
|
18
|
+
- `LocalRunner` fork, shutdown, watch-mode attach, and display lifetime:
|
|
19
|
+
`tests/localRunner.test.ts`. Force-kill after the real `SHUTDOWN_TIMEOUT`:
|
|
20
|
+
`tests/workerCleanupIssue.test.ts`.
|
|
21
|
+
- Worker IPC, exit codes, and `kill`: `tests/worker.test.ts`. Forked log-file
|
|
22
|
+
names: `tests/worker.env.test.ts`.
|
|
23
|
+
- Child-process entry (`src/run.ts`): `tests/run.test.ts`.
|
|
24
|
+
- REPL eval and result callbacks: `tests/repl.test.ts`. One-at-a-time debug
|
|
25
|
+
sessions: `tests/replQueue.test.ts` through `ReplQueue.next`.
|
|
26
|
+
- Log prefix and debugger-line filtering: `tests/transformStream.test.ts`.
|
|
27
|
+
Write the Node debugger prefixes in the test. Do not feed it
|
|
28
|
+
`DEBUGGER_MESSAGES`.
|
|
29
|
+
|
|
30
|
+
Do not add a second assertion of the same exit payload. Assert REPL errors
|
|
31
|
+
through `onResult`, not private helpers. A skipped queue test is not coverage.
|
package/build/index.d.ts
CHANGED
|
@@ -11,21 +11,16 @@ export default class LocalRunner {
|
|
|
11
11
|
private _options;
|
|
12
12
|
protected config: WebdriverIO.Config;
|
|
13
13
|
workerPool: Record<string, WorkerInstance>;
|
|
14
|
-
private
|
|
15
|
-
private xvfbManager;
|
|
14
|
+
private daemon;
|
|
16
15
|
stdout: WritableStreamBuffer;
|
|
17
16
|
stderr: WritableStreamBuffer;
|
|
18
17
|
constructor(_options: never, config: WebdriverIO.Config);
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
19
|
+
* Runs before any service `onPrepare`, so drivers those services start inherit the display.
|
|
21
20
|
*/
|
|
22
21
|
initialize(): Promise<void>;
|
|
23
22
|
getWorkerCount(): number;
|
|
24
23
|
run({ command, args, ...workerOptions }: RunArgs): Promise<WorkerInstance>;
|
|
25
|
-
/**
|
|
26
|
-
* Initialize XVFB with capability-aware detection
|
|
27
|
-
*/
|
|
28
|
-
private initializeXvfb;
|
|
29
24
|
/**
|
|
30
25
|
* shutdown all worker processes
|
|
31
26
|
*
|
|
@@ -33,5 +28,10 @@ export default class LocalRunner {
|
|
|
33
28
|
* a timeout was reached
|
|
34
29
|
*/
|
|
35
30
|
shutdown(): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Stop the display daemon. Runs after `onComplete`, so a driver or app a
|
|
33
|
+
* service started in `onPrepare` still has its display while it is torn down.
|
|
34
|
+
*/
|
|
35
|
+
dispose(): Promise<void>;
|
|
36
36
|
}
|
|
37
37
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,cAAc,MAAM,aAAa,CAAA;AAKxC,YAAY,EAAE,cAAc,EAAE,CAAA;AAE9B,MAAM,WAAW,OAAQ,SAAQ,OAAO,CAAC,gBAAgB;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,CAAC,iBAAiB,CAAA;IAC/B,GAAG,EAAE,MAAM,CAAA;CACd;AAED,MAAM,CAAC,OAAO,OAAO,WAAW;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,cAAc,MAAM,aAAa,CAAA;AAKxC,YAAY,EAAE,cAAc,EAAE,CAAA;AAE9B,MAAM,WAAW,OAAQ,SAAQ,OAAO,CAAC,gBAAgB;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,CAAC,iBAAiB,CAAA;IAC/B,GAAG,EAAE,MAAM,CAAA;CACd;AAED,MAAM,CAAC,OAAO,OAAO,WAAW;IAQxB,OAAO,CAAC,QAAQ;IAChB,SAAS,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM;IARxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAK;IAC/C,OAAO,CAAC,MAAM,CAA6B;IAE3C,MAAM,uBAA2C;IACjD,MAAM,uBAA2C;gBAGrC,QAAQ,EAAE,KAAK,EACb,MAAM,EAAE,WAAW,CAAC,MAAM;IAGxC;;OAEG;IACG,UAAU;IAQhB,cAAc;IAIR,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,aAAa,EAAE,EAAE,OAAO;IAqBtD;;;;;OAKG;IACG,QAAQ;IAkFd;;;OAGG;IACG,OAAO;CAGhB"}
|
package/build/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import logger2 from "@wdio/logger";
|
|
3
3
|
import { WritableStreamBuffer } from "stream-buffers";
|
|
4
|
-
import {
|
|
4
|
+
import { startDisplayDaemonFromConfig } from "@wdio/display-server";
|
|
5
5
|
|
|
6
6
|
// src/worker.ts
|
|
7
7
|
import url from "node:url";
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import { fork } from "node:child_process";
|
|
10
|
+
import { constants } from "node:os";
|
|
9
11
|
import { EventEmitter } from "node:events";
|
|
10
|
-
import { ProcessFactory } from "@wdio/xvfb";
|
|
11
12
|
import logger from "@wdio/logger";
|
|
12
13
|
|
|
13
14
|
// src/transformStream.ts
|
|
@@ -186,6 +187,112 @@ var stdOutStream = new RunnerStream();
|
|
|
186
187
|
var stdErrStream = new RunnerStream();
|
|
187
188
|
stdOutStream.pipe(process.stdout);
|
|
188
189
|
stdErrStream.pipe(process.stderr);
|
|
190
|
+
var GRACEFUL_SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
|
|
191
|
+
var SIGNAL_EXIT_CODE_OFFSET = 128;
|
|
192
|
+
var UNKNOWN_SIGNAL_EXIT_CODE = 1;
|
|
193
|
+
function getExitCodeForSignal(signal) {
|
|
194
|
+
const signalNumber = signal ? constants.signals[signal] : void 0;
|
|
195
|
+
return signalNumber ? SIGNAL_EXIT_CODE_OFFSET + signalNumber : UNKNOWN_SIGNAL_EXIT_CODE;
|
|
196
|
+
}
|
|
197
|
+
var NODE_OPTIONS_WITH_VALUE = /* @__PURE__ */ new Set([
|
|
198
|
+
"-r",
|
|
199
|
+
"--require",
|
|
200
|
+
"--import",
|
|
201
|
+
"--experimental-loader",
|
|
202
|
+
"--loader",
|
|
203
|
+
"--conditions",
|
|
204
|
+
"-C",
|
|
205
|
+
"--max-old-space-size",
|
|
206
|
+
"--max-semi-space-size"
|
|
207
|
+
]);
|
|
208
|
+
var REPEATABLE_NODE_OPTIONS = /* @__PURE__ */ new Set([
|
|
209
|
+
"-r",
|
|
210
|
+
"--require",
|
|
211
|
+
"--import",
|
|
212
|
+
"--experimental-loader",
|
|
213
|
+
"--loader",
|
|
214
|
+
"--conditions",
|
|
215
|
+
"-C"
|
|
216
|
+
]);
|
|
217
|
+
function nodeOptionName(group) {
|
|
218
|
+
return group.split(" ")[0].split("=")[0];
|
|
219
|
+
}
|
|
220
|
+
function tokenizeNodeOptions(value) {
|
|
221
|
+
const tokens = [];
|
|
222
|
+
let current = "";
|
|
223
|
+
let quote;
|
|
224
|
+
const push = () => {
|
|
225
|
+
if (current) {
|
|
226
|
+
tokens.push(current);
|
|
227
|
+
current = "";
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
for (let i = 0; i < value.length; i++) {
|
|
231
|
+
const char = value[i];
|
|
232
|
+
if (quote) {
|
|
233
|
+
if (char === "\\" && i + 1 < value.length) {
|
|
234
|
+
current += char + value[i + 1];
|
|
235
|
+
i++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
current += char;
|
|
239
|
+
if (char === quote) {
|
|
240
|
+
quote = void 0;
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (char === '"' || char === "'") {
|
|
245
|
+
quote = char;
|
|
246
|
+
current += char;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (/\s/.test(char)) {
|
|
250
|
+
push();
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
current += char;
|
|
254
|
+
}
|
|
255
|
+
push();
|
|
256
|
+
return tokens;
|
|
257
|
+
}
|
|
258
|
+
function parseNodeOptionGroups(value) {
|
|
259
|
+
const parts = tokenizeNodeOptions(value);
|
|
260
|
+
const groups = [];
|
|
261
|
+
for (let i = 0; i < parts.length; i++) {
|
|
262
|
+
const token = parts[i];
|
|
263
|
+
const name = token.split("=")[0];
|
|
264
|
+
const next = parts[i + 1];
|
|
265
|
+
if (!token.includes("=") && NODE_OPTIONS_WITH_VALUE.has(name) && next && !next.startsWith("-")) {
|
|
266
|
+
groups.push(`${token} ${next}`);
|
|
267
|
+
i++;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
groups.push(token);
|
|
271
|
+
}
|
|
272
|
+
return groups;
|
|
273
|
+
}
|
|
274
|
+
function mergeWorkerNodeOptions(parent, runner) {
|
|
275
|
+
const merged = parseNodeOptionGroups(parent);
|
|
276
|
+
if (typeof runner !== "string") {
|
|
277
|
+
return merged.join(" ");
|
|
278
|
+
}
|
|
279
|
+
for (const group of parseNodeOptionGroups(runner)) {
|
|
280
|
+
const name = nodeOptionName(group);
|
|
281
|
+
if (REPEATABLE_NODE_OPTIONS.has(name)) {
|
|
282
|
+
if (!merged.includes(group)) {
|
|
283
|
+
merged.push(group);
|
|
284
|
+
}
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const existing = merged.findIndex((item) => nodeOptionName(item) === name);
|
|
288
|
+
if (existing === -1) {
|
|
289
|
+
merged.push(group);
|
|
290
|
+
} else {
|
|
291
|
+
merged[existing] = group;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return merged.join(" ");
|
|
295
|
+
}
|
|
189
296
|
var WorkerInstance = class extends EventEmitter {
|
|
190
297
|
cid;
|
|
191
298
|
config;
|
|
@@ -203,9 +310,8 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
203
310
|
sessionId;
|
|
204
311
|
server;
|
|
205
312
|
logsAggregator = [];
|
|
206
|
-
#processFactory;
|
|
207
313
|
instances;
|
|
208
|
-
|
|
314
|
+
isMultiRemote;
|
|
209
315
|
isBusy = false;
|
|
210
316
|
isKilled = false;
|
|
211
317
|
isReady;
|
|
@@ -223,9 +329,8 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
223
329
|
* @param {string[]} specs list of paths to test files to run in this worker
|
|
224
330
|
* @param {number} retries number of retries remaining
|
|
225
331
|
* @param {object} execArgv execution arguments for the test run
|
|
226
|
-
* @param {XvfbManager} xvfbManager configured XvfbManager instance
|
|
227
332
|
*/
|
|
228
|
-
constructor(config, { cid, configFile, caps, specs, execArgv, retries }, stdout, stderr
|
|
333
|
+
constructor(config, { cid, configFile, caps, specs, execArgv, retries }, stdout, stderr) {
|
|
229
334
|
super();
|
|
230
335
|
this.cid = cid;
|
|
231
336
|
this.config = config;
|
|
@@ -237,7 +342,6 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
237
342
|
this.retries = retries;
|
|
238
343
|
this.stdout = stdout;
|
|
239
344
|
this.stderr = stderr;
|
|
240
|
-
this.#processFactory = new ProcessFactory(xvfbManager);
|
|
241
345
|
this.isReady = new Promise((resolve) => {
|
|
242
346
|
this.isReadyResolver = resolve;
|
|
243
347
|
});
|
|
@@ -251,9 +355,7 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
251
355
|
async startProcess() {
|
|
252
356
|
const { cid, execArgv } = this;
|
|
253
357
|
const argv = process.argv.slice(2);
|
|
254
|
-
const runnerEnv = Object.assign({
|
|
255
|
-
NODE_OPTIONS: "--enable-source-maps"
|
|
256
|
-
}, process.env, this.config.runnerEnv, {
|
|
358
|
+
const runnerEnv = Object.assign({}, process.env, this.config.runnerEnv, {
|
|
257
359
|
WDIO_WORKER_ID: cid,
|
|
258
360
|
NODE_ENV: process.env.NODE_ENV || "test"
|
|
259
361
|
});
|
|
@@ -265,9 +367,20 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
265
367
|
}
|
|
266
368
|
runnerEnv.WDIO_LOG_PATH = path.join(this.config.outputDir, logFileRunner);
|
|
267
369
|
}
|
|
268
|
-
|
|
370
|
+
const runnerOverride = this.config.runnerEnv?.NODE_OPTIONS;
|
|
371
|
+
const nodeOptions = mergeWorkerNodeOptions(
|
|
372
|
+
process.env.NODE_OPTIONS ?? "",
|
|
373
|
+
typeof runnerOverride === "string" ? runnerOverride : void 0
|
|
374
|
+
);
|
|
375
|
+
const hasSourceMaps = nodeOptions.split(" ").includes("--enable-source-maps");
|
|
376
|
+
const merged = this.shouldEnableSourceMaps() && !hasSourceMaps ? `${nodeOptions} --enable-source-maps`.trim() : nodeOptions;
|
|
377
|
+
if (merged) {
|
|
378
|
+
runnerEnv.NODE_OPTIONS = merged;
|
|
379
|
+
} else {
|
|
380
|
+
delete runnerEnv.NODE_OPTIONS;
|
|
381
|
+
}
|
|
269
382
|
log.info(`Start worker ${cid} with arg: ${argv.join(" ")}`);
|
|
270
|
-
const childProcess = this.childProcess =
|
|
383
|
+
const childProcess = this.childProcess = fork(
|
|
271
384
|
path.join(__dirname, "run.js"),
|
|
272
385
|
argv,
|
|
273
386
|
{
|
|
@@ -294,6 +407,17 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
294
407
|
}
|
|
295
408
|
return childProcess;
|
|
296
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Source maps help debug stack traces but cost worker boot time.
|
|
412
|
+
* Enable for verbose log levels, or when WDIO_SOURCE_MAPS=1.
|
|
413
|
+
*/
|
|
414
|
+
shouldEnableSourceMaps() {
|
|
415
|
+
if (process.env.WDIO_SOURCE_MAPS === "1" || process.env.WDIO_SOURCE_MAPS === "true") {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
const level = this.config.logLevel;
|
|
419
|
+
return level === "trace" || level === "debug";
|
|
420
|
+
}
|
|
297
421
|
_handleMessage(payload) {
|
|
298
422
|
const { cid, childProcess } = this;
|
|
299
423
|
if (payload.name === "finishedCommand") {
|
|
@@ -310,12 +434,13 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
310
434
|
if (this.retries === -1 && payload.specFileRetries) {
|
|
311
435
|
this.retries = payload.specFileRetries - 1;
|
|
312
436
|
}
|
|
313
|
-
if (payload.content.
|
|
437
|
+
if (payload.content.isMultiRemote) {
|
|
314
438
|
Object.assign(this, payload.content);
|
|
315
439
|
} else {
|
|
440
|
+
const { capabilities, ...connection } = payload.content;
|
|
316
441
|
this.sessionId = payload.content.sessionId;
|
|
317
|
-
this.capabilities =
|
|
318
|
-
|
|
442
|
+
this.capabilities = capabilities;
|
|
443
|
+
this.config = { ...this.config, ...connection };
|
|
319
444
|
}
|
|
320
445
|
}
|
|
321
446
|
if (childProcess && payload.origin === "debugger" && payload.name === "start") {
|
|
@@ -336,13 +461,27 @@ var WorkerInstance = class extends EventEmitter {
|
|
|
336
461
|
const { cid } = this;
|
|
337
462
|
this.emit("error", Object.assign(payload, { cid }));
|
|
338
463
|
}
|
|
339
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Node calls this with `(code, signal)` where `code` is `null` whenever the
|
|
466
|
+
* worker was terminated by a signal rather than exiting on its own, e.g. on
|
|
467
|
+
* a segmentation fault. Only the second argument identifies that case.
|
|
468
|
+
*/
|
|
469
|
+
_handleExit(exitCode, signal = null) {
|
|
340
470
|
const { cid, childProcess, specs, retries } = this;
|
|
471
|
+
const wasKilledIntentionally = this.isKilled;
|
|
341
472
|
delete this.childProcess;
|
|
342
473
|
this.isBusy = false;
|
|
343
474
|
this.isKilled = true;
|
|
344
|
-
|
|
345
|
-
|
|
475
|
+
const resolvedExitCode = exitCode ?? getExitCodeForSignal(signal);
|
|
476
|
+
const crashed = signal !== null && !wasKilledIntentionally && !GRACEFUL_SHUTDOWN_SIGNALS.includes(signal);
|
|
477
|
+
if (crashed) {
|
|
478
|
+
log.error(
|
|
479
|
+
`Runner ${cid} was terminated by ${signal} and did not report an exit code. The spec file(s) ${specs.join(", ")} are reported as failed with exit code ${resolvedExitCode}. This usually means the process ran out of memory or a native module crashed.`
|
|
480
|
+
);
|
|
481
|
+
} else {
|
|
482
|
+
log.debug(`Runner ${cid} finished with exit code ${resolvedExitCode}`);
|
|
483
|
+
}
|
|
484
|
+
this.emit("exit", { cid, exitCode: resolvedExitCode, specs, retries, signal });
|
|
346
485
|
if (childProcess) {
|
|
347
486
|
childProcess.kill("SIGTERM");
|
|
348
487
|
}
|
|
@@ -402,35 +541,27 @@ var LocalRunner = class {
|
|
|
402
541
|
constructor(_options, config) {
|
|
403
542
|
this._options = _options;
|
|
404
543
|
this.config = config;
|
|
405
|
-
this.xvfbManager = new XvfbManager({
|
|
406
|
-
enabled: this.config.autoXvfb !== false,
|
|
407
|
-
autoInstall: this.config.xvfbAutoInstall,
|
|
408
|
-
autoInstallMode: this.config.xvfbAutoInstallMode,
|
|
409
|
-
autoInstallCommand: this.config.xvfbAutoInstallCommand,
|
|
410
|
-
xvfbMaxRetries: this.config.xvfbMaxRetries,
|
|
411
|
-
xvfbRetryDelay: this.config.xvfbRetryDelay
|
|
412
|
-
});
|
|
413
544
|
}
|
|
414
545
|
_options;
|
|
415
546
|
config;
|
|
416
547
|
workerPool = {};
|
|
417
|
-
|
|
418
|
-
xvfbManager;
|
|
548
|
+
daemon = null;
|
|
419
549
|
stdout = new WritableStreamBuffer(BUFFER_OPTIONS);
|
|
420
550
|
stderr = new WritableStreamBuffer(BUFFER_OPTIONS);
|
|
421
551
|
/**
|
|
422
|
-
*
|
|
552
|
+
* Runs before any service `onPrepare`, so drivers those services start inherit the display.
|
|
423
553
|
*/
|
|
424
554
|
async initialize() {
|
|
555
|
+
try {
|
|
556
|
+
this.daemon = await startDisplayDaemonFromConfig(this.config);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
log2.warn("Failed to start a display server; continuing without a virtual display:", error);
|
|
559
|
+
}
|
|
425
560
|
}
|
|
426
561
|
getWorkerCount() {
|
|
427
562
|
return Object.keys(this.workerPool).length;
|
|
428
563
|
}
|
|
429
564
|
async run({ command, args, ...workerOptions }) {
|
|
430
|
-
if (!this.xvfbInitialized) {
|
|
431
|
-
await this.initializeXvfb(workerOptions);
|
|
432
|
-
this.xvfbInitialized = true;
|
|
433
|
-
}
|
|
434
565
|
const workerCnt = this.getWorkerCount();
|
|
435
566
|
if (workerCnt >= process.stdout.getMaxListeners() - 2) {
|
|
436
567
|
process.stdout.setMaxListeners(workerCnt + 2);
|
|
@@ -440,34 +571,12 @@ var LocalRunner = class {
|
|
|
440
571
|
this.config,
|
|
441
572
|
workerOptions,
|
|
442
573
|
this.stdout,
|
|
443
|
-
this.stderr
|
|
444
|
-
this.xvfbManager
|
|
574
|
+
this.stderr
|
|
445
575
|
);
|
|
446
576
|
this.workerPool[workerOptions.cid] = worker;
|
|
447
577
|
await worker.postMessage(command, args);
|
|
448
578
|
return worker;
|
|
449
579
|
}
|
|
450
|
-
/**
|
|
451
|
-
* Initialize XVFB with capability-aware detection
|
|
452
|
-
*/
|
|
453
|
-
async initializeXvfb(workerOptions) {
|
|
454
|
-
if (this.config.autoXvfb === false) {
|
|
455
|
-
log2.info("Skipping automatic Xvfb initialization (disabled by config)");
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
try {
|
|
459
|
-
const capabilities = workerOptions.caps;
|
|
460
|
-
const xvfbInitialized = await this.xvfbManager.init(capabilities);
|
|
461
|
-
if (xvfbInitialized) {
|
|
462
|
-
log2.info("Xvfb is ready for use");
|
|
463
|
-
}
|
|
464
|
-
} catch (error) {
|
|
465
|
-
log2.warn(
|
|
466
|
-
"Failed to initialize Xvfb, continuing without virtual display:",
|
|
467
|
-
error
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
580
|
/**
|
|
472
581
|
* shutdown all worker processes
|
|
473
582
|
*
|
|
@@ -483,16 +592,22 @@ var LocalRunner = class {
|
|
|
483
592
|
server,
|
|
484
593
|
sessionId,
|
|
485
594
|
config,
|
|
486
|
-
|
|
595
|
+
isMultiRemote,
|
|
487
596
|
instances
|
|
488
597
|
} = worker;
|
|
489
598
|
let payload = {};
|
|
490
|
-
if (config && config.watch && (sessionId ||
|
|
599
|
+
if (config && config.watch && (sessionId || isMultiRemote)) {
|
|
491
600
|
payload = {
|
|
492
|
-
|
|
601
|
+
/**
|
|
602
|
+
* The worker's own session id wins over any id stored on
|
|
603
|
+
* config. Workers used to share one config object, so the
|
|
604
|
+
* last session started would otherwise be deleted twice
|
|
605
|
+
* and the others left open.
|
|
606
|
+
*/
|
|
607
|
+
config: { ...server, ...config, ...sessionId ? { sessionId } : {} },
|
|
493
608
|
capabilities,
|
|
494
609
|
watch: true,
|
|
495
|
-
|
|
610
|
+
isMultiRemote,
|
|
496
611
|
instances
|
|
497
612
|
};
|
|
498
613
|
} else if (!worker.isBusy) {
|
|
@@ -527,11 +642,15 @@ var LocalRunner = class {
|
|
|
527
642
|
}
|
|
528
643
|
}, 250);
|
|
529
644
|
});
|
|
530
|
-
if (this.xvfbManager.shouldRun()) {
|
|
531
|
-
log2.info("Xvfb cleanup handled automatically by xvfb-run");
|
|
532
|
-
}
|
|
533
645
|
return shutdownResult;
|
|
534
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Stop the display daemon. Runs after `onComplete`, so a driver or app a
|
|
649
|
+
* service started in `onPrepare` still has its display while it is torn down.
|
|
650
|
+
*/
|
|
651
|
+
async dispose() {
|
|
652
|
+
await this.daemon?.stop();
|
|
653
|
+
}
|
|
535
654
|
};
|
|
536
655
|
export {
|
|
537
656
|
LocalRunner as default
|
package/build/worker.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
1
2
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { ChildProcess } from 'node:child_process';
|
|
3
3
|
import type { WritableStreamBuffer } from 'stream-buffers';
|
|
4
|
-
import { type XvfbManager } from '@wdio/xvfb';
|
|
5
4
|
import type { Workers } from '@wdio/types';
|
|
6
5
|
/**
|
|
7
6
|
* WorkerInstance
|
|
@@ -9,7 +8,6 @@ import type { Workers } from '@wdio/types';
|
|
|
9
8
|
* session lifetime.
|
|
10
9
|
*/
|
|
11
10
|
export default class WorkerInstance extends EventEmitter implements Workers.Worker {
|
|
12
|
-
#private;
|
|
13
11
|
cid: string;
|
|
14
12
|
config: WebdriverIO.Config;
|
|
15
13
|
configFile: string;
|
|
@@ -27,7 +25,7 @@ export default class WorkerInstance extends EventEmitter implements Workers.Work
|
|
|
27
25
|
instances?: Record<string, {
|
|
28
26
|
sessionId: string;
|
|
29
27
|
}>;
|
|
30
|
-
|
|
28
|
+
isMultiRemote?: boolean;
|
|
31
29
|
isBusy: boolean;
|
|
32
30
|
isKilled: boolean;
|
|
33
31
|
isReady: Promise<boolean>;
|
|
@@ -43,15 +41,24 @@ export default class WorkerInstance extends EventEmitter implements Workers.Work
|
|
|
43
41
|
* @param {string[]} specs list of paths to test files to run in this worker
|
|
44
42
|
* @param {number} retries number of retries remaining
|
|
45
43
|
* @param {object} execArgv execution arguments for the test run
|
|
46
|
-
* @param {XvfbManager} xvfbManager configured XvfbManager instance
|
|
47
44
|
*/
|
|
48
|
-
constructor(config: WebdriverIO.Config, { cid, configFile, caps, specs, execArgv, retries }: Workers.WorkerRunPayload, stdout: WritableStreamBuffer, stderr: WritableStreamBuffer
|
|
45
|
+
constructor(config: WebdriverIO.Config, { cid, configFile, caps, specs, execArgv, retries }: Workers.WorkerRunPayload, stdout: WritableStreamBuffer, stderr: WritableStreamBuffer);
|
|
49
46
|
/**
|
|
50
47
|
* spawns process to kick of wdio-runner
|
|
51
48
|
*/
|
|
52
49
|
startProcess(): Promise<ChildProcess>;
|
|
50
|
+
/**
|
|
51
|
+
* Source maps help debug stack traces but cost worker boot time.
|
|
52
|
+
* Enable for verbose log levels, or when WDIO_SOURCE_MAPS=1.
|
|
53
|
+
*/
|
|
54
|
+
private shouldEnableSourceMaps;
|
|
53
55
|
private _handleMessage;
|
|
54
56
|
private _handleError;
|
|
57
|
+
/**
|
|
58
|
+
* Node calls this with `(code, signal)` where `code` is `null` whenever the
|
|
59
|
+
* worker was terminated by a signal rather than exiting on its own, e.g. on
|
|
60
|
+
* a segmentation fault. Only the second argument identifies that case.
|
|
61
|
+
*/
|
|
55
62
|
private _handleExit;
|
|
56
63
|
/**
|
|
57
64
|
* Forcefully kill the worker process.
|
|
@@ -66,6 +73,6 @@ export default class WorkerInstance extends EventEmitter implements Workers.Work
|
|
|
66
73
|
* @param command method to run in wdio-runner
|
|
67
74
|
* @param args arguments for functions to call
|
|
68
75
|
*/
|
|
69
|
-
postMessage(command: string, args: Workers.WorkerMessageArgs, requiresSetup?: boolean): Promise<void>;
|
|
76
|
+
postMessage(command: string, args: Workers.WorkerMessageArgs | Workers.WorkerRequest['args'], requiresSetup?: boolean): Promise<void>;
|
|
70
77
|
}
|
|
71
78
|
//# sourceMappingURL=worker.d.ts.map
|
package/build/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAwK1C;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,YAAa,YAAW,OAAO,CAAC,MAAM;IAC9E,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,WAAW,CAAC,MAAM,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;IAElB,IAAI,EAAE,WAAW,CAAC,YAAY,CAAA;IAE9B,YAAY,EAAE,WAAW,CAAC,YAAY,CAAA;IACtC,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,oBAAoB,CAAA;IAC5B,MAAM,EAAE,oBAAoB,CAAA;IAC5B,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,cAAc,EAAE,MAAM,EAAE,CAAK;IAE7B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjD,aAAa,CAAC,EAAE,OAAO,CAAA;IAEvB,MAAM,UAAQ;IACd,QAAQ,UAAQ;IAChB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IACzB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IACzB,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,CAAW;IAC3E,eAAe,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,IAAI,CAAW;IAE3E;;;;;;;;;OASG;gBAEC,MAAM,EAAE,WAAW,CAAC,MAAM,EAC1B,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,gBAAgB,EAC7E,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,oBAAoB;IAkBhC;;OAEG;IACG,YAAY;IAgFlB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,cAAc;IA+EtB,OAAO,CAAC,YAAY;IAKpB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAmCnB;;;;;;OAMG;IACH,IAAI,CAAC,MAAM,GAAE,MAAM,CAAC,OAAmB,GAAG,IAAI;IAiB9C;;;;OAIG;IACG,WAAW,CAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,aAAa,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;CA4B7I"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdio/local-runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0-alpha.149+ce13bea83",
|
|
4
4
|
"description": "A WebdriverIO runner to run tests locally",
|
|
5
5
|
"author": "Christian Bromann <mail@bromann.dev>",
|
|
6
6
|
"homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-local-runner",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
9
|
+
"node": ">=22.19.0"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
@@ -34,16 +34,17 @@
|
|
|
34
34
|
"import": "./build/run.js"
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
|
-
"typeScriptVersion": "
|
|
37
|
+
"typeScriptVersion": "6.0.3",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@types/node": "^20.
|
|
40
|
-
"@
|
|
41
|
-
"@wdio/
|
|
42
|
-
"@wdio/
|
|
43
|
-
"@wdio/
|
|
44
|
-
"@wdio/
|
|
39
|
+
"@types/node": "^22.20.4",
|
|
40
|
+
"@types/split2": "^4.2.3",
|
|
41
|
+
"@wdio/display-server": "10.0.0-alpha.149+ce13bea83",
|
|
42
|
+
"@wdio/logger": "10.0.0-alpha.149+ce13bea83",
|
|
43
|
+
"@wdio/repl": "10.0.0-alpha.149+ce13bea83",
|
|
44
|
+
"@wdio/runner": "10.0.0-alpha.149+ce13bea83",
|
|
45
|
+
"@wdio/types": "10.0.0-alpha.149+ce13bea83",
|
|
45
46
|
"exit-hook": "^4.0.0",
|
|
46
|
-
"expect-webdriverio": "^
|
|
47
|
+
"expect-webdriverio": "^8.0.0-alpha.2",
|
|
47
48
|
"split2": "^4.1.0",
|
|
48
49
|
"stream-buffers": "^3.0.2"
|
|
49
50
|
},
|
|
@@ -53,5 +54,5 @@
|
|
|
53
54
|
"publishConfig": {
|
|
54
55
|
"access": "public"
|
|
55
56
|
},
|
|
56
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "ce13bea839fb037eceda429dbf94cd387b7c3584"
|
|
57
58
|
}
|