codeceptjs 4.0.8 → 4.1.0-beta.1-esm-mocha

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.
@@ -71,16 +71,18 @@ For TypeScript test files in CodeceptJS 4.x, use the [`tsx`](https://tsx.is) loa
71
71
  // codecept.conf.ts
72
72
  export const config = {
73
73
  tests: './**/*_test.ts',
74
- require: ['tsx/cjs'],
74
+ require: ['tsx/esm'],
75
75
  helpers: {},
76
76
  include: {},
77
77
  }
78
78
  ```
79
79
 
80
+ This requires `"type": "module"` in `package.json` so `.ts` test files are compiled as ES Modules.
81
+
80
82
  Combine several modules:
81
83
 
82
84
  ```ts
83
- require: ['tsx/cjs', 'should', './lib/testSetup']
85
+ require: ['tsx/esm', 'should', './lib/testSetup']
84
86
  ```
85
87
 
86
88
  The config file itself (`codecept.conf.ts`) and helpers are transpiled automatically — only test files need the loader. See [TypeScript](/typescript) for the full setup.
@@ -111,7 +111,7 @@ npm i tsx --save-dev
111
111
  // codecept.conf.ts
112
112
  export const config = {
113
113
  tests: './**/*_test.ts',
114
- require: ['tsx/cjs'], // loads the *_test.ts files
114
+ require: ['tsx/esm'], // loads the *_test.ts files as ES Modules (needs "type": "module")
115
115
  helpers: {
116
116
  Playwright: { url: 'http://localhost', browser: 'chromium' },
117
117
  },
package/docs/parallel.md CHANGED
@@ -116,7 +116,7 @@ import { Workers, event } from 'codeceptjs'
116
116
  const workers = new Workers(null, { testConfig: './codecept.conf.js' })
117
117
 
118
118
  // split the suite into 2 groups, run each group on two browsers
119
- const groups = workers.createGroupsOfSuites(2)
119
+ const groups = await workers.createGroupsOfSuites(2)
120
120
  for (const browser of ['chromium', 'firefox']) {
121
121
  for (const group of groups) {
122
122
  const worker = workers.spawn()
@@ -139,7 +139,7 @@ try {
139
139
  Building blocks:
140
140
 
141
141
  - `new Workers(N, { testConfig, options })` — `N` workers; pass `null` to spawn them yourself with `spawn()`.
142
- - `createGroupsOfTests(n)` / `createGroupsOfSuites(n)` — split the suite into `n` groups.
142
+ - `await createGroupsOfTests(n)` / `await createGroupsOfSuites(n)` — split the suite into `n` groups (async: test files are loaded as ES Modules).
143
143
  - `worker.addTests(group)` / `worker.addConfig(partialConfig)` — assign tests and config overrides to a spawned worker.
144
144
  - `bootstrapAll()` → `run()` → `teardownAll()` — lifecycle (wrap `run()` in `try/finally` so teardown always runs).
145
145
  - Events on the `workers` object: `event.test.passed`, `event.test.failed`, `event.all.result`, plus `'message'` for anything a child worker sends. `printResults()` prints the standard summary; `result.hasFailed()` and `result.stats` give the totals.
@@ -15,7 +15,7 @@ CodeceptJS ships [type declarations](https://github.com/codeceptjs/CodeceptJS/tr
15
15
  ? Do you plan to write tests in TypeScript? Yes
16
16
  ```
17
17
 
18
- It writes `codecept.conf.ts` and `*_test.ts` files. The **config file** and helpers are transpiled automatically. **Test files** need a loader — CodeceptJS 4.x is ESM, and Mocha loads test files through CommonJS hooks, so use [`tsx`](https://tsx.is) (fast, esbuild-based, no `tsconfig.json` required):
18
+ It writes `codecept.conf.ts` and `*_test.ts` files. The **config file** and helpers are transpiled automatically. **Test files** need a loader — CodeceptJS 4.x is ESM and loads test files as ES Modules, so use [`tsx`](https://tsx.is) (fast, esbuild-based, no `tsconfig.json` required):
19
19
 
20
20
  ```sh
21
21
  npm i tsx --save-dev
@@ -25,16 +25,16 @@ npm i tsx --save-dev
25
25
  // codecept.conf.ts
26
26
  export const config = {
27
27
  tests: './**/*_test.ts',
28
- require: ['tsx/cjs'], // loads the *_test.ts files
28
+ require: ['tsx/esm'], // loads the *_test.ts files as ES Modules
29
29
  helpers: {
30
30
  Playwright: { url: 'http://localhost', browser: 'chromium' },
31
31
  },
32
32
  }
33
33
  ```
34
34
 
35
- Run the tests with `npx codeceptjs run`.
35
+ Set `"type": "module"` in `package.json` so `tsx` compiles your `.ts` test files as ES Modules. Then run the tests with `npx codeceptjs run`.
36
36
 
37
- > Adding TypeScript to an existing project: set `"type": "module"` in `package.json`, rename the config to `codecept.conf.ts` with `export const config = {}`, install `tsx`, and add `require: ['tsx/cjs']`.
37
+ > Adding TypeScript to an existing project: set `"type": "module"` in `package.json`, rename the config to `codecept.conf.ts` with `export const config = {}`, install `tsx`, and add `require: ['tsx/esm']`.
38
38
 
39
39
  ## Writing tests
40
40
 
@@ -59,7 +59,9 @@ Scenario('admin signs in', ({ I }) => {
59
59
  })
60
60
  ```
61
61
 
62
- > **Cannot find module** or **Unexpected token** while running tests means the loader isn't wired up — check that `tsx` is installed and `require: ['tsx/cjs']` is in the config.
62
+ > **Cannot find module** or **Unexpected token** while running tests means the loader isn't wired up — check that `tsx` is installed and `require: ['tsx/esm']` is in the config.
63
+ >
64
+ > **`ERR_REQUIRE_CYCLE_MODULE`** means `tsx` is compiling your `.ts` tests as CommonJS — add `"type": "module"` to the nearest `package.json`.
63
65
 
64
66
  ## Promise-based typings
65
67
 
package/lib/codecept.js CHANGED
@@ -19,6 +19,7 @@ import ActorFactory from './actor.js'
19
19
  import output from './output.js'
20
20
  import { emptyFolder, resolveImportModulePath } from './utils.js'
21
21
  import { initCodeceptGlobals } from './globals.js'
22
+ import loadTests from './mocha/loadTests.js'
22
23
  import { validateTypeScriptSetup, getTSNodeESMWarning } from './utils/loaderCheck.js'
23
24
  import recorder from './recorder.js'
24
25
  import store from './store.js'
@@ -58,7 +59,11 @@ class Codecept {
58
59
  */
59
60
  async requireModules(requiringModules) {
60
61
  if (requiringModules) {
61
- for (const requiredModule of requiringModules) {
62
+ for (let requiredModule of requiringModules) {
63
+ if (requiredModule === 'tsx/cjs') {
64
+ output.print(output.styles.debug('`tsx/cjs` is deprecated for test files. Using `tsx/esm` instead. Update your config `require` to `tsx/esm` and add `"type": "module"` to package.json.'))
65
+ requiredModule = 'tsx/esm'
66
+ }
62
67
  let modulePath = requiredModule
63
68
  const isLocalFile = existsSync(modulePath) || existsSync(`${modulePath}.js`)
64
69
  if (isLocalFile) {
@@ -295,21 +300,23 @@ class Codecept {
295
300
  this.testFiles.sort()
296
301
  }
297
302
 
298
- return new Promise((resolve, reject) => {
299
- const mocha = container.mocha()
300
- mocha.files = this.testFiles
303
+ const mocha = container.mocha()
304
+ mocha.files = this.testFiles
301
305
 
302
- if (test) {
303
- if (!fsPath.isAbsolute(test)) {
304
- test = fsPath.join(store.codeceptDir, test)
305
- }
306
- const testBasename = fsPath.basename(test, '.js')
307
- const testFeatureBasename = fsPath.basename(test, '.feature')
308
- mocha.files = mocha.files.filter(t => {
309
- return fsPath.basename(t, '.js') === testBasename || fsPath.basename(t, '.feature') === testFeatureBasename || t === test
310
- })
306
+ if (test) {
307
+ if (!fsPath.isAbsolute(test)) {
308
+ test = fsPath.join(store.codeceptDir, test)
311
309
  }
310
+ const testBasename = fsPath.basename(test, '.js')
311
+ const testFeatureBasename = fsPath.basename(test, '.feature')
312
+ mocha.files = mocha.files.filter(t => {
313
+ return fsPath.basename(t, '.js') === testBasename || fsPath.basename(t, '.feature') === testFeatureBasename || t === test
314
+ })
315
+ }
316
+
317
+ await loadTests(mocha)
312
318
 
319
+ return new Promise((resolve, reject) => {
313
320
  const done = async (failures) => {
314
321
  event.emit(event.all.result, container.result())
315
322
  event.emit(event.all.after, this)
@@ -6,6 +6,7 @@ import Container from '../container.js'
6
6
  import figures from 'figures'
7
7
  import chalk from 'chalk'
8
8
  import { createTest } from '../mocha/test.js'
9
+ import loadTests from '../mocha/loadTests.js'
9
10
  import { getMachineInfo } from './info.js'
10
11
  import definitions from './definitions.js'
11
12
 
@@ -73,7 +74,7 @@ export default async function (options) {
73
74
  const files = codecept.testFiles
74
75
  const mocha = Container.mocha()
75
76
  mocha.files = files
76
- mocha.loadFiles()
77
+ await loadTests(mocha)
77
78
 
78
79
  for (const suite of mocha.suite.suites) {
79
80
  if (suite && suite.tests) {
@@ -6,6 +6,7 @@ import output from '../output.js'
6
6
  import event from '../event.js'
7
7
  import store from '../store.js'
8
8
  import Container from '../container.js'
9
+ import loadTests from '../mocha/loadTests.js'
9
10
 
10
11
  export default async function (test, options) {
11
12
  if (options.grep) process.env.grep = options.grep
@@ -74,7 +75,7 @@ async function printTests(files) {
74
75
 
75
76
  const mocha = Container.mocha()
76
77
  mocha.files = files
77
- mocha.loadFiles()
78
+ await loadTests(mocha)
78
79
 
79
80
  let numOfTests = 0
80
81
  let numOfSuites = 0
@@ -165,7 +165,7 @@ export default async function (initPath, options = {}) {
165
165
  config.tests = result.tests
166
166
  if (isTypeScript) {
167
167
  config.tests = `${config.tests.replace(/\.js$/, `.${extension}`)}`
168
- config.require = ['tsx/cjs']
168
+ config.require = ['tsx/esm']
169
169
  }
170
170
 
171
171
  const matchResults = config.tests.match(/[^*.]+/)
@@ -260,6 +260,18 @@ export default async function (initPath, options = {}) {
260
260
  }
261
261
 
262
262
  if (isTypeScript) {
263
+ try {
264
+ const pkgPath = path.join(process.cwd(), 'package.json')
265
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
266
+ if (pkg.type !== 'module') {
267
+ pkg.type = 'module'
268
+ fs.writeFileSync(pkgPath, beautify(JSON.stringify(pkg)))
269
+ print('Set "type": "module" in package.json so TypeScript tests load as ES Modules')
270
+ }
271
+ } catch (err) {
272
+ print(colors.bold.yellow('Could not set "type": "module" in package.json. Add it manually so TypeScript tests load as ES Modules.'))
273
+ }
274
+
263
275
  const tsconfigJson = beautify(JSON.stringify(tsconfig))
264
276
  const tsconfigFile = path.join(testsPath, 'tsconfig.json')
265
277
  if (fileExists(tsconfigFile)) {
@@ -11,7 +11,7 @@ import { parentPort, workerData } from 'worker_threads'
11
11
 
12
12
  // Delay imports to avoid ES Module loader race conditions in Node 22.x worker threads
13
13
  // These will be imported dynamically when needed
14
- let event, container, Codecept, getConfig, tryOrDefault, deepMerge, fixErrorStack
14
+ let event, container, Codecept, getConfig, tryOrDefault, deepMerge, fixErrorStack, loadTests
15
15
 
16
16
  let stdout = ''
17
17
 
@@ -143,6 +143,7 @@ initPromise = (async function () {
143
143
  const coreUtilsModule = await import('../../utils.js')
144
144
  const CodeceptModule = await import('../../codecept.js')
145
145
  const typescriptModule = await import('../../utils/typescript.js')
146
+ const loadTestsModule = await import('../../mocha/loadTests.js')
146
147
 
147
148
  event = eventModule.default
148
149
  container = containerModule.default
@@ -151,6 +152,7 @@ initPromise = (async function () {
151
152
  deepMerge = coreUtilsModule.deepMerge
152
153
  Codecept = CodeceptModule.default
153
154
  fixErrorStack = typescriptModule.fixErrorStack
155
+ loadTests = loadTestsModule.default
154
156
 
155
157
  const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
156
158
 
@@ -200,7 +202,7 @@ initPromise = (async function () {
200
202
  // We'll reload test files fresh for each test request
201
203
  } else {
202
204
  // Legacy mode - filter tests upfront
203
- filterTests()
205
+ await filterTests()
204
206
  }
205
207
 
206
208
  // run tests
@@ -290,20 +292,13 @@ async function runPoolTests() {
290
292
 
291
293
  // Load only the assigned test file
292
294
  mocha.files = [testIdentifier]
293
- mocha.loadFiles()
294
-
295
- try {
296
- require('fs').appendFileSync('/tmp/config_listener_debug.log', `${new Date().toISOString()} [POOL] Loaded ${testIdentifier}, tests: ${mocha.suite.total()}\n`)
297
- } catch (e) { /* ignore */ }
295
+ await loadTests(mocha)
298
296
 
299
297
  if (mocha.suite.total() > 0) {
300
298
  // Run only the tests in the current mocha suite
301
299
  // Don't use codecept.run() as it overwrites mocha.files with ALL test files
302
300
  await new Promise((resolve, reject) => {
303
301
  mocha.run(() => {
304
- try {
305
- require('fs').appendFileSync('/tmp/config_listener_debug.log', `${new Date().toISOString()} [POOL] Finished ${testIdentifier}\n`)
306
- } catch (e) { /* ignore */ }
307
302
  resolve()
308
303
  })
309
304
  })
@@ -429,10 +424,10 @@ function filterTestById(testUid) {
429
424
  }
430
425
  }
431
426
 
432
- function filterTests() {
427
+ async function filterTests() {
433
428
  const files = codecept.testFiles
434
429
  mocha.files = files
435
- mocha.loadFiles()
430
+ await loadTests(mocha)
436
431
 
437
432
  // Recursively filter tests in all suites (including nested ones)
438
433
  const filterSuiteTests = (suite) => {
@@ -1,9 +1,7 @@
1
1
  import Mocha from 'mocha'
2
2
  import fsPath from 'path'
3
- import fs from 'fs'
4
3
  import { fileURLToPath } from 'url'
5
4
  import reporter from './cli.js'
6
- import gherkinParser, { loadTranslations } from './gherkin.js'
7
5
  import output from '../output.js'
8
6
  import scenarioUiFunction from './ui.js'
9
7
  import { initMochaGlobals } from '../globals.js'
@@ -52,64 +50,6 @@ class MochaFactory {
52
50
  process.exit(1)
53
51
  }
54
52
 
55
- // Override loadFiles to handle feature files
56
- const originalLoadFiles = Mocha.prototype.loadFiles
57
- mocha.loadFiles = function (fn) {
58
- // load features
59
- const featureFiles = this.files.filter(file => file.match(/\.feature$/))
60
- if (featureFiles.length > 0) {
61
- // Load translations for Gherkin features
62
- loadTranslations().catch(() => {
63
- // Ignore if translations can't be loaded
64
- })
65
-
66
- for (const file of featureFiles) {
67
- const suite = gherkinParser(fs.readFileSync(file, 'utf8'), file)
68
- this.suite.addSuite(suite)
69
- }
70
-
71
- // remove feature files
72
- const jsFiles = this.files.filter(file => !file.match(/\.feature$/))
73
- this.files = this.files.filter(file => !file.match(/\.feature$/))
74
-
75
- // Load JavaScript test files using original loadFiles
76
- if (jsFiles.length > 0) {
77
- originalLoadFiles.call(this, fn)
78
- }
79
-
80
- // add ids for each test and check uniqueness
81
- const dupes = []
82
- let missingFeatureInFile = []
83
- const seenTests = []
84
- this.suite.eachTest(test => {
85
- if (!test) {
86
- return // Skip undefined tests
87
- }
88
- const name = test.fullTitle()
89
- if (seenTests.includes(test.uid)) {
90
- dupes.push(name)
91
- }
92
- seenTests.push(test.uid)
93
-
94
- if (name.slice(0, name.indexOf(':')) === '') {
95
- missingFeatureInFile.push(test.file)
96
- }
97
- })
98
- if (dupes.length) {
99
- // ideally this should be no-op and throw (breaking change)...
100
- output.error(`Duplicate test names detected - Feature + Scenario name should be unique:\n${dupes.join('\n')}`)
101
- }
102
-
103
- if (missingFeatureInFile.length) {
104
- missingFeatureInFile = [...new Set(missingFeatureInFile)]
105
- output.error(`Missing Feature section in:\n${missingFeatureInFile.join('\n')}`)
106
- }
107
- } else {
108
- // Use original for non-feature files
109
- originalLoadFiles.call(this, fn)
110
- }
111
- }
112
-
113
53
  const presetReporter = opts.reporter || config.reporter
114
54
  // use standard reporter
115
55
  if (!presetReporter) {
@@ -0,0 +1,69 @@
1
+ import fs from 'fs'
2
+ import fsPath from 'path'
3
+ import gherkinParser, { loadTranslations } from './gherkin.js'
4
+ import output from '../output.js'
5
+ import { resolveImportModulePath } from '../utils.js'
6
+
7
+ export default async function loadTests(mocha) {
8
+ mocha.lazyLoadFiles(true)
9
+
10
+ const featureFiles = mocha.files.filter(file => file.match(/\.feature$/))
11
+ const testFiles = mocha.files.filter(file => !file.match(/\.feature$/))
12
+
13
+ if (featureFiles.length > 0) {
14
+ await loadTranslations()
15
+ for (const file of featureFiles) {
16
+ mocha.suite.addSuite(gherkinParser(fs.readFileSync(file, 'utf8'), file))
17
+ }
18
+ }
19
+
20
+ for (const file of testFiles) {
21
+ const resolvedPath = resolveImportModulePath(fsPath.resolve(file))
22
+ mocha.suite.emit('pre-require', global, file, mocha)
23
+ try {
24
+ const module = await import(resolvedPath)
25
+ mocha.suite.emit('require', module, file, mocha)
26
+ } catch (err) {
27
+ throw enrichLoaderError(err, file)
28
+ }
29
+ mocha.suite.emit('post-require', global, file, mocha)
30
+ }
31
+
32
+ validateLoadedTests(mocha)
33
+ }
34
+
35
+ function validateLoadedTests(mocha) {
36
+ const dupes = []
37
+ let missingFeatureInFile = []
38
+ const seenTests = []
39
+ mocha.suite.eachTest(test => {
40
+ if (!test) {
41
+ return
42
+ }
43
+ const name = test.fullTitle()
44
+ if (seenTests.includes(test.uid)) {
45
+ dupes.push(name)
46
+ }
47
+ seenTests.push(test.uid)
48
+
49
+ if (name.slice(0, name.indexOf(':')) === '') {
50
+ missingFeatureInFile.push(test.file)
51
+ }
52
+ })
53
+
54
+ if (dupes.length) {
55
+ output.error(`Duplicate test names detected - Feature + Scenario name should be unique:\n${dupes.join('\n')}`)
56
+ }
57
+
58
+ if (missingFeatureInFile.length) {
59
+ missingFeatureInFile = [...new Set(missingFeatureInFile)]
60
+ output.error(`Missing Feature section in:\n${missingFeatureInFile.join('\n')}`)
61
+ }
62
+ }
63
+
64
+ function enrichLoaderError(err, file) {
65
+ if (err && err.code === 'ERR_REQUIRE_CYCLE_MODULE') {
66
+ err.message = `${err.message}\n\nFailed to load test file as ES Module: ${file}\nAdd "type": "module" to the nearest package.json so TypeScript files are compiled as ES Modules.\nSee https://codecept.io/typescript`
67
+ }
68
+ return err
69
+ }
package/lib/rerun.js CHANGED
@@ -4,23 +4,12 @@ import container from './container.js'
4
4
  import event from './event.js'
5
5
  import BaseCodecept from './codecept.js'
6
6
  import output from './output.js'
7
- import { createRequire } from 'module'
8
- import { resolveImportModulePath } from './utils.js'
9
-
10
- const require = createRequire(import.meta.url)
7
+ import loadTests from './mocha/loadTests.js'
11
8
 
12
9
  class CodeceptRerunner extends BaseCodecept {
13
10
  async runOnce(test) {
14
11
  await container.started()
15
12
 
16
- // Ensure translations are loaded for Gherkin features
17
- try {
18
- const { loadTranslations } = await import('./mocha/gherkin.js')
19
- await loadTranslations()
20
- } catch (e) {
21
- // Ignore if gherkin module not available
22
- }
23
-
24
13
  return new Promise(async (resolve, reject) => {
25
14
  try {
26
15
  // Create a fresh Mocha instance for each run
@@ -40,24 +29,8 @@ class CodeceptRerunner extends BaseCodecept {
40
29
  mocha.suite.suites = []
41
30
  mocha.suite.tests = []
42
31
 
43
- // Manually load each test file by importing it
44
- for (const file of filesToRun) {
45
- try {
46
- // Clear CommonJS cache if available (for mixed environments)
47
- try {
48
- delete require.cache[file]
49
- } catch (e) {
50
- // ESM modules don't have require.cache, ignore
51
- }
52
-
53
- // Force reload the module by using a cache-busting query parameter
54
- const fileUrl = `${fsPath.resolve(file)}`
55
- const resolvedPath = resolveImportModulePath(fileUrl)
56
- await import(resolvedPath)
57
- } catch (e) {
58
- console.error(`Error loading test file ${file}:`, e)
59
- }
60
- }
32
+ mocha.files = filesToRun
33
+ await loadTests(mocha)
61
34
 
62
35
  const done = () => {
63
36
  event.emit(event.all.result, container.result())
@@ -50,18 +50,23 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
50
50
  npm install --save-dev tsx
51
51
 
52
52
  Configuration:
53
- Add to your codecept.conf.ts or codecept.conf.js:
53
+ 1. Add to your codecept.conf.ts or codecept.conf.js:
54
54
 
55
- export const config = {
56
- tests: './**/*_test.ts',
57
- require: ['tsx/cjs'], // ← Add this line
58
- helpers: { /* ... */ }
59
- }
55
+ export const config = {
56
+ tests: './**/*_test.ts',
57
+ require: ['tsx/esm'], // ← Add this line
58
+ helpers: { /* ... */ }
59
+ }
60
+
61
+ 2. Add "type": "module" to your package.json so TypeScript test files
62
+ are compiled as ES Modules:
63
+
64
+ { "type": "module" }
60
65
 
61
66
  Why tsx?
62
67
  ⚡ Fast: Built on esbuild
63
68
  🎯 Zero config: No tsconfig.json required
64
- ✅ Works with Mocha: Uses CommonJS hooks
69
+ ✅ Works with Mocha: Test files are loaded as ES Modules
65
70
  ✅ Complete: Handles all TypeScript features
66
71
 
67
72
  ┌─────────────────────────────────────────────────────────────────────────────┐
@@ -69,12 +74,11 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
69
74
  └─────────────────────────────────────────────────────────────────────────────┘
70
75
 
71
76
  ⚠️ ts-node/esm has significant limitations and is not recommended:
72
- - Doesn't work with "type": "module" in package.json
73
77
  - Module resolution doesn't work like standard TypeScript ESM
74
78
  - Import statements must use explicit file paths
75
-
76
- We strongly recommend using tsx/cjs instead.
77
-
79
+
80
+ We strongly recommend using tsx/esm instead.
81
+
78
82
  If you still want to use ts-node/esm:
79
83
 
80
84
  Installation:
@@ -119,7 +123,7 @@ export function getTSNodeESMWarning(requiredModules = []) {
119
123
  return `
120
124
  ⚠️ Warning: ts-node/esm with "module": "esnext" requires explicit file extensions in all imports.
121
125
 
122
- This is a known limitation. Use tsx/cjs instead to write imports without extensions.
126
+ This is a known limitation. Use tsx/esm instead to write imports without extensions.
123
127
 
124
128
  Examples:
125
129
 
@@ -385,9 +385,7 @@ const __dirname = __dirname_fn(__filename);
385
385
  )
386
386
 
387
387
  // Write the transpiled file with updated imports
388
- // Include process.pid + a random suffix so concurrent run-multiple workers
389
- // don't write to and delete each other's temp files (see issue #5642).
390
- const tempFile = filePath.replace(/\.ts$/, `.${process.pid}.${Math.random().toString(36).slice(2, 10)}.temp.mjs`)
388
+ const tempFile = filePath.replace(/\.ts$/, '.temp.mjs')
391
389
  fs.writeFileSync(tempFile, jsContent)
392
390
  transpiledFiles.set(filePath, tempFile)
393
391
  }
package/lib/workers.js CHANGED
@@ -11,6 +11,7 @@ const __filename = fileURLToPath(import.meta.url)
11
11
  const __dirname = dirname(__filename)
12
12
  import Codecept from './codecept.js'
13
13
  import MochaFactory from './mocha/factory.js'
14
+ import loadTests from './mocha/loadTests.js'
14
15
  import Container from './container.js'
15
16
  import { getTestRoot } from './command/utils.js'
16
17
  import { isFunction, fileExists, replaceValueDeep, deepClone } from './utils.js'
@@ -169,12 +170,12 @@ const indexOfSmallestElement = groups => {
169
170
  return i
170
171
  }
171
172
 
172
- const convertToMochaTests = testGroup => {
173
+ const convertToMochaTests = async testGroup => {
173
174
  const group = []
174
175
  if (testGroup instanceof Array) {
175
176
  const mocha = MochaFactory.create({}, {})
176
177
  mocha.files = testGroup
177
- mocha.loadFiles()
178
+ await loadTests(mocha)
178
179
  mocha.suite.eachTest(test => {
179
180
  group.push(test.uid)
180
181
  })
@@ -247,8 +248,8 @@ class WorkerObject {
247
248
  this.options.override = JSON.stringify(newConfig)
248
249
  }
249
250
 
250
- addTestFiles(testGroup) {
251
- this.addTests(convertToMochaTests(testGroup))
251
+ async addTestFiles(testGroup) {
252
+ this.addTests(await convertToMochaTests(testGroup))
252
253
  }
253
254
 
254
255
  addTests(tests) {
@@ -304,13 +305,13 @@ class Workers extends EventEmitter {
304
305
  const shouldAutoInit = this.workers.length === 0 && ((Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) || (this.numberOfWorkersRequested < 0 && isFunction(this.config.by)))
305
306
 
306
307
  if (shouldAutoInit) {
307
- this._initWorkers(this.numberOfWorkersRequested, this.config)
308
+ await this._initWorkers(this.numberOfWorkersRequested, this.config)
308
309
  }
309
310
  }
310
311
  }
311
312
 
312
- _initWorkers(numberOfWorkers, config) {
313
- this.splitTestsByGroups(numberOfWorkers, config)
313
+ async _initWorkers(numberOfWorkers, config) {
314
+ await this.splitTestsByGroups(numberOfWorkers, config)
314
315
  // For function-based grouping, use the actual number of test groups created
315
316
  const actualNumberOfWorkers = isFunction(config.by) ? this.testGroups.length : numberOfWorkers
316
317
  this.workers = createWorkerObjects(this.testGroups, this.codecept.config, getTestRoot(config.testConfig), config.options, config.selectedRuns)
@@ -330,7 +331,7 @@ class Workers extends EventEmitter {
330
331
  *
331
332
  * This method can be overridden for a better split.
332
333
  */
333
- splitTestsByGroups(numberOfWorkers, config) {
334
+ async splitTestsByGroups(numberOfWorkers, config) {
334
335
  if (isFunction(config.by)) {
335
336
  const createTests = config.by
336
337
  const testGroups = createTests(numberOfWorkers)
@@ -338,13 +339,13 @@ class Workers extends EventEmitter {
338
339
  throw new Error('Test group should be an array')
339
340
  }
340
341
  for (const testGroup of testGroups) {
341
- this.testGroups.push(convertToMochaTests(testGroup))
342
+ this.testGroups.push(await convertToMochaTests(testGroup))
342
343
  }
343
344
  } else if (typeof numberOfWorkers === 'number' && numberOfWorkers > 0) {
344
345
  if (config.by === 'pool') {
345
346
  this.createTestPool(numberOfWorkers)
346
347
  } else {
347
- this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
348
+ this.testGroups = config.by === 'suite' ? await this.createGroupsOfSuites(numberOfWorkers) : await this.createGroupsOfTests(numberOfWorkers)
348
349
  }
349
350
  }
350
351
  }
@@ -364,16 +365,16 @@ class Workers extends EventEmitter {
364
365
  /**
365
366
  * @param {Number} numberOfWorkers
366
367
  */
367
- createGroupsOfTests(numberOfWorkers) {
368
+ async createGroupsOfTests(numberOfWorkers) {
368
369
  // If Codecept isn't initialized yet, return empty groups as a safe fallback
369
370
  if (!this.codecept) return populateGroups(numberOfWorkers)
370
371
  const files = this.codecept.testFiles
371
-
372
+
372
373
  // Create a fresh mocha instance to avoid state pollution
373
374
  Container.createMocha(this.codecept.config.mocha || {}, this.options)
374
375
  const mocha = Container.mocha()
375
376
  mocha.files = files
376
- mocha.loadFiles()
377
+ await loadTests(mocha)
377
378
 
378
379
  const groups = populateGroups(numberOfWorkers)
379
380
  let groupCounter = 0
@@ -451,7 +452,7 @@ class Workers extends EventEmitter {
451
452
  /**
452
453
  * @param {Number} numberOfWorkers
453
454
  */
454
- createGroupsOfSuites(numberOfWorkers) {
455
+ async createGroupsOfSuites(numberOfWorkers) {
455
456
  // If Codecept isn't initialized yet, return empty groups as a safe fallback
456
457
  if (!this.codecept) return populateGroups(numberOfWorkers)
457
458
  const files = this.codecept.testFiles
@@ -461,7 +462,7 @@ class Workers extends EventEmitter {
461
462
  Container.createMocha(this.codecept.config.mocha || {}, this.options)
462
463
  const mocha = Container.mocha()
463
464
  mocha.files = files
464
- mocha.loadFiles()
465
+ await loadTests(mocha)
465
466
 
466
467
  mocha.suite.suites.forEach(suite => {
467
468
  const i = indexOfSmallestElement(groups)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "4.0.8",
3
+ "version": "4.1.0-beta.1-esm-mocha",
4
4
  "type": "module",
5
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
6
6
  "keywords": [