From c3bf125ef095b2cc050eef3cd9e4a3f01de97065 Mon Sep 17 00:00:00 2001 From: Daniil Krapiunitski Date: Tue, 29 Sep 2026 10:43:39 +0200 Subject: [PATCH 1/2] fix(workers): skip loadTests in run() when suite is already populated Worker threads call loadTests() in filterTests() before codecept.run(). A second loadTests() in run() duplicated Gherkin suites and executed each assigned test twice (regression since ESM loadTests in #5640). Add regression tests for JS and Gherkin worker assignments. Co-authored-by: Cursor --- lib/codecept.js | 7 ++- .../sandbox/codecept.workers-gherkin.conf.js | 21 ++++++++ .../sandbox/workers-gherkin/_placeholder.js | 1 + .../sandbox/workers-gherkin/single.feature | 4 ++ test/data/sandbox/workers-gherkin/steps.js | 3 ++ test/unit/worker_test.js | 52 +++++++++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/data/sandbox/codecept.workers-gherkin.conf.js create mode 100644 test/data/sandbox/workers-gherkin/_placeholder.js create mode 100644 test/data/sandbox/workers-gherkin/single.feature create mode 100644 test/data/sandbox/workers-gherkin/steps.js diff --git a/lib/codecept.js b/lib/codecept.js index c630abfab..6c10bca08 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -314,7 +314,12 @@ class Codecept { }) } - await loadTests(mocha) + // Worker threads pre-load and filter tests before run(); reloading duplicates Gherkin suites. + if (!mocha.suite.total()) { + await loadTests(mocha) + } else { + mocha.lazyLoadFiles(true) + } return new Promise((resolve, reject) => { const done = async (failures) => { diff --git a/test/data/sandbox/codecept.workers-gherkin.conf.js b/test/data/sandbox/codecept.workers-gherkin.conf.js new file mode 100644 index 000000000..200b089f5 --- /dev/null +++ b/test/data/sandbox/codecept.workers-gherkin.conf.js @@ -0,0 +1,21 @@ +export const config = { + tests: './workers-gherkin/_placeholder.js', + timeout: 10000, + output: './output', + helpers: { + BDD: { + require: './support/bdd_helper.js', + }, + Workers: { + require: './workers_helper.js', + }, + }, + gherkin: { + features: './workers-gherkin/*.feature', + steps: ['./workers-gherkin/steps.js'], + }, + include: {}, + bootstrap: false, + mocha: {}, + name: 'sandbox-workers-gherkin', +} diff --git a/test/data/sandbox/workers-gherkin/_placeholder.js b/test/data/sandbox/workers-gherkin/_placeholder.js new file mode 100644 index 000000000..bc639d68e --- /dev/null +++ b/test/data/sandbox/workers-gherkin/_placeholder.js @@ -0,0 +1 @@ +// Gherkin-only worker sandbox; JS tests path must exist for config. diff --git a/test/data/sandbox/workers-gherkin/single.feature b/test/data/sandbox/workers-gherkin/single.feature new file mode 100644 index 000000000..3666607a8 --- /dev/null +++ b/test/data/sandbox/workers-gherkin/single.feature @@ -0,0 +1,4 @@ +Feature: Worker gherkin single scenario + + Scenario: runs once @worker_gherkin_once + Given I open a browser on a site diff --git a/test/data/sandbox/workers-gherkin/steps.js b/test/data/sandbox/workers-gherkin/steps.js new file mode 100644 index 000000000..dc9401528 --- /dev/null +++ b/test/data/sandbox/workers-gherkin/steps.js @@ -0,0 +1,3 @@ +import { Given } from '../../../../lib/mocha/bdd.js' + +Given('I open a browser on a site', () => {}) diff --git a/test/unit/worker_test.js b/test/unit/worker_test.js index 44d73f340..94be96030 100644 --- a/test/unit/worker_test.js +++ b/test/unit/worker_test.js @@ -417,4 +417,56 @@ describe('Workers', function () { const actualFiles = testFiles.map(f => path.resolve(f)) expect(actualFiles).to.deep.equal(expectedFiles, 'loadTests() should preserve original glob order without sorting') }) + + it('should run each assigned gherkin worker test once after pre-load', done => { + const workerConfig = { + by: 'test', + testConfig: './test/data/sandbox/codecept.workers-gherkin.conf.js', + options: { + grep: '@worker_gherkin_once', + }, + } + + let passedCount = 0 + const workers = new Workers(1, workerConfig) + + workers.on(event.test.passed, () => { + passedCount += 1 + }) + + workers.run() + + workers.on(event.all.result, result => { + expect(passedCount).to.equal(1) + expect(result.stats.tests).to.equal(1) + expect(result.stats.passes).to.equal(1) + done() + }) + }) + + it('should run each assigned worker test once after pre-load (no duplicate loadTests in run)', done => { + const workerConfig = { + by: 'test', + testConfig: './test/data/sandbox/codecept.workers.conf.js', + options: { + grep: 'From worker @1_grep print message 1', + }, + } + + let passedCount = 0 + const workers = new Workers(1, workerConfig) + + workers.on(event.test.passed, () => { + passedCount += 1 + }) + + workers.run() + + workers.on(event.all.result, result => { + expect(passedCount).to.equal(1) + expect(result.stats.tests).to.equal(1) + expect(result.stats.passes).to.equal(1) + done() + }) + }) }) From dea8427abbecd38193ccff1e5cadd67bf50b6f75 Mon Sep 17 00:00:00 2001 From: Daniil Krapiunitski Date: Tue, 29 Sep 2026 10:57:21 +0200 Subject: [PATCH 2/2] test(workers): drop flaky JS grep case; gherkin regression is enough Co-authored-by: Cursor --- test/unit/worker_test.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/test/unit/worker_test.js b/test/unit/worker_test.js index 94be96030..c2c422ae4 100644 --- a/test/unit/worker_test.js +++ b/test/unit/worker_test.js @@ -444,29 +444,4 @@ describe('Workers', function () { }) }) - it('should run each assigned worker test once after pre-load (no duplicate loadTests in run)', done => { - const workerConfig = { - by: 'test', - testConfig: './test/data/sandbox/codecept.workers.conf.js', - options: { - grep: 'From worker @1_grep print message 1', - }, - } - - let passedCount = 0 - const workers = new Workers(1, workerConfig) - - workers.on(event.test.passed, () => { - passedCount += 1 - }) - - workers.run() - - workers.on(event.all.result, result => { - expect(passedCount).to.equal(1) - expect(result.stats.tests).to.equal(1) - expect(result.stats.passes).to.equal(1) - done() - }) - }) })