From 26e9442994343741d8ef04bd19d9cb870516a8d3 Mon Sep 17 00:00:00 2001 From: Jaromir Obr Date: Sun, 24 May 2026 10:47:19 +0200 Subject: [PATCH] fix(run-multiple): restore plugin loading and per-child reportDir (#5577) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two regressions introduced during the 3.x → 4.x ESM migration: ## Bug 1 – plugins with runInWorker:false silently skipped in child processes lib/container.js used options.child to detect "running inside a worker thread" and skipped plugins whose runInWorker is false (testomatio defaults to false). But run-multiple also sets --child on every forked child process, so those plugins were incorrectly skipped there too. Fix: replace options.child with !isMainThread (worker_threads). A run-multiple child is a freshly-forked OS process whose isMainThread is true, so the gate no longer fires. An actual run-workers worker thread has isMainThread === false, so the gate still fires as intended. | Context | options.child | isMainThread | before (skipped?) | after | |---------------------------|---------------|--------------|-------------------|--------| | run-workers worker thread | truthy (idx) | false | skipped ✓ | skipped ✓ | | run-multiple child proc | truthy (str) | true | skipped ✗ (bug) | loads ✓ | | normal run / parent proc | falsy | true | loads ✓ | loads ✓ | ## Bug 2 – all children write to the same reportDir, last one wins 3.x run-multiple.js replaced three per-child directory keys before forking: output, reportDir, mochaFile The 4.x port dropped the reportDir line, so every child kept the shared reportDir value from the config (e.g. "output/report") and overwrote each other's HTML file. Fix: restore the missing replaceValueDeep('reportDir', ...) call so each child receives its own directory, matching 3.x behaviour. Regression test added: verifies that a plugin with runInWorker:false is initialised once per child and that each child receives a distinct reportDir. Co-Authored-By: Claude Sonnet 4.6 --- lib/command/run-multiple.js | 1 + lib/container.js | 5 ++-- ...decept.multiple.plugin.runInWorkerFalse.js | 28 +++++++++++++++++++ test/data/sandbox/plugin-runInWorkerFalse.js | 6 ++++ test/runner/run_multiple_test.js | 17 +++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 test/data/sandbox/codecept.multiple.plugin.runInWorkerFalse.js create mode 100644 test/data/sandbox/plugin-runInWorkerFalse.js diff --git a/lib/command/run-multiple.js b/lib/command/run-multiple.js index 237256758..d3301214d 100644 --- a/lib/command/run-multiple.js +++ b/lib/command/run-multiple.js @@ -140,6 +140,7 @@ function executeRun(runName, runConfig) { // tweaking default output directories overriddenConfig = replaceValueDeep(overriddenConfig, 'output', path.join(config.output, outputDir)) + overriddenConfig = replaceValueDeep(overriddenConfig, 'reportDir', path.join(config.output, outputDir)) overriddenConfig = replaceValueDeep(overriddenConfig, 'mochaFile', path.join(config.output, outputDir, `${browserName}_report.xml`)) // override tests configuration diff --git a/lib/container.js b/lib/container.js index 581674c28..3a00d42fb 100644 --- a/lib/container.js +++ b/lib/container.js @@ -1,6 +1,7 @@ import { globSync } from 'glob' import path from 'path' import fs from 'fs' +import { isMainThread } from 'worker_threads' import debugModule from 'debug' const debug = debugModule('codeceptjs:container') import { MetaStep } from './step.js' @@ -731,11 +732,11 @@ async function createPlugins(config, options = {}) { const runInWorker = pluginConfig.runInWorker ?? pluginConfig.runInWorkers ?? (pluginName === 'testomatio' ? false : true) const runInParent = pluginConfig.runInParent ?? pluginConfig.runInMain ?? true - if (options.child && !runInWorker) { + if (!isMainThread && !runInWorker) { continue } - if (!options.child && store.workerMode && !runInParent) { + if (isMainThread && store.workerMode && !runInParent) { continue } let module diff --git a/test/data/sandbox/codecept.multiple.plugin.runInWorkerFalse.js b/test/data/sandbox/codecept.multiple.plugin.runInWorkerFalse.js new file mode 100644 index 000000000..d423f9a57 --- /dev/null +++ b/test/data/sandbox/codecept.multiple.plugin.runInWorkerFalse.js @@ -0,0 +1,28 @@ +export const config = { + tests: './*_test.multiple.js', + timeout: 10000, + output: './output', + helpers: { + FakeDriver: { + require: '../fake_driver', + browser: 'dummy', + windowSize: 'maximize', + }, + }, + multiple: { + default: { + browsers: ['chrome', 'firefox'], + }, + }, + plugins: { + runInWorkerFalsePlugin: { + enabled: true, + runInWorker: false, + require: './plugin-runInWorkerFalse.js', + reportDir: 'output/report', + }, + }, + bootstrap: false, + mocha: {}, + name: 'sandbox', +} diff --git a/test/data/sandbox/plugin-runInWorkerFalse.js b/test/data/sandbox/plugin-runInWorkerFalse.js new file mode 100644 index 000000000..261eff1aa --- /dev/null +++ b/test/data/sandbox/plugin-runInWorkerFalse.js @@ -0,0 +1,6 @@ +export default function (config) { + process.stdout.write('plugin-runInWorkerFalse:loaded\n') + if (config.reportDir) { + process.stdout.write(`plugin-runInWorkerFalse:reportDir=${config.reportDir}\n`) + } +} diff --git a/test/runner/run_multiple_test.js b/test/runner/run_multiple_test.js index 355cf2c39..f557b42fa 100644 --- a/test/runner/run_multiple_test.js +++ b/test/runner/run_multiple_test.js @@ -199,6 +199,23 @@ describe('CodeceptJS Multiple Runner', function () { }) }) + it('should load plugins with runInWorker:false in each child process and give each its own reportDir', done => { + exec(`${runner} run-multiple --config ${codecept_dir}/codecept.multiple.plugin.runInWorkerFalse.js default`, (err, stdout) => { + // Plugin must be initialised once per forked child (chrome + firefox = 2 times). + // Regression: in 4.x the plugin was silently skipped because options.child was + // truthy in run-multiple children, same as in worker threads. + ;(stdout.match(/plugin-runInWorkerFalse:loaded/g) || []).should.have.lengthOf(2) + // reportDir must be replaced per child so each child writes its own report. + // Regression: run-multiple.js dropped the replaceValueDeep('reportDir', ...) call + // that was present in 3.x, causing all children to overwrite the same file. + const dirs = (stdout.match(/plugin-runInWorkerFalse:reportDir=(.+)/g) || []).map(m => m.split('=')[1]) + dirs.should.have.lengthOf(2) + dirs[0].should.not.equal(dirs[1]) + assert(!err) + done() + }) + }) + describe('bootstrapAll and teardownAll', () => { const _codecept_run = `run-multiple --config ${codecept_dir}` it('should be executed from async function in config', done => {