Skip to content

Commit d6a4c02

Browse files
committed
fix(workers): Use dynamic imports to work around Node 22.x ES Module loader bug
- Delay importing of event, container, Codecept, and utility modules - Use dynamic imports inside async init function instead of top-level imports - Prevents 'Cannot read properties of undefined (reading getStatus)' error This is a workaround for a known Node.js 22.x bug where worker threads fail to load ES modules that have complex dependency chains. The error occurs in the internal module loader when trying to load ESM from CJS. Note: This partially mitigates the issue but the fundamental bug in Node 22.x's module loader may still cause intermittent failures. The issue has been reported to the Node.js project.
1 parent 112ce88 commit d6a4c02

File tree

1 file changed

+24
-12
lines changed

1 file changed

+24
-12
lines changed

lib/command/workers/runTests.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,15 @@ if (!tty.getWindowSize) {
88
}
99

1010
import { parentPort, workerData } from 'worker_threads'
11-
import event from '../../event.js'
12-
import container from '../../container.js'
13-
import { getConfig } from '../utils.js'
14-
import { tryOrDefault, deepMerge } from '../../utils.js'
11+
12+
// Delay imports to avoid ES Module loader race conditions in Node 22.x worker threads
13+
// These will be imported dynamically when needed
14+
let event, container, Codecept, getConfig, tryOrDefault, deepMerge
1515

1616
let stdout = ''
1717

1818
const stderr = ''
1919

20-
// Importing of Codecept need to be after tty.getWindowSize is available.
21-
import Codecept from '../../codecept.js'
22-
2320
const { options, tests, testRoot, workerIndex, poolMode } = workerData
2421

2522
// hide worker output
@@ -76,19 +73,34 @@ if (poolMode && !options.debug) {
7673
}
7774
}
7875

79-
const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
80-
81-
// important deep merge so dynamic things e.g. functions on config are not overridden
82-
const config = deepMerge(getConfig(options.config || testRoot), overrideConfigs)
83-
8476
// Declare codecept and mocha at module level so they can be accessed by functions
8577
let codecept
8678
let mocha
8779
let initPromise
80+
let config
8881

8982
// Load test and run
9083
initPromise = (async function () {
9184
try {
85+
// Import modules dynamically to avoid ES Module loader race conditions in Node 22.x
86+
const eventModule = await import('../../event.js')
87+
const containerModule = await import('../../container.js')
88+
const utilsModule = await import('../utils.js')
89+
const coreUtilsModule = await import('../../utils.js')
90+
const CodeceptModule = await import('../../codecept.js')
91+
92+
event = eventModule.default
93+
container = containerModule.default
94+
getConfig = utilsModule.getConfig
95+
tryOrDefault = coreUtilsModule.tryOrDefault
96+
deepMerge = coreUtilsModule.deepMerge
97+
Codecept = CodeceptModule.default
98+
99+
const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
100+
101+
// important deep merge so dynamic things e.g. functions on config are not overridden
102+
config = deepMerge(getConfig(options.config || testRoot), overrideConfigs)
103+
92104
codecept = new Codecept(config, options)
93105
await codecept.init(testRoot)
94106
codecept.loadTests()

0 commit comments

Comments
 (0)