Skip to content

fix(core): load Mocha test files as ES Modules (fixes internal-API imports from tests, #5636)#5640

Open
DavertMik wants to merge 1 commit into
4.xfrom
fix/esm-test-loading
Open

fix(core): load Mocha test files as ES Modules (fixes internal-API imports from tests, #5636)#5640
DavertMik wants to merge 1 commit into
4.xfrom
fix/esm-test-loading

Conversation

@DavertMik

Copy link
Copy Markdown
Contributor

Problem

A test that imports the internal API — import { config } from "codeceptjs" — gets a value that looks empty/disconnected from the running session (#5636).

Root cause: Mocha loads test files with synchronous require(). Under tsx/cjs that creates a second CommonJS copy of every lib/* module, each with its own module-level singletons (config, container, recorder, event, store). The test imports the second copy, whose singletons were never populated by the runner. Helpers don't have this problem because the container loads them with await import() (ESM).

Fix

Load test files the same way helpers are loaded — through await import() — so the whole run shares one ES module graph and the plain import resolves to the live instance. No bridge, no globalThis indirection.

New lib/mocha/loadTests.js mirrors Mocha's own loadFiles (lazyLoadFiles(true) so mocha.run() won't sync-require, then per file emit pre-requireawait import()require/post-require). That preserves:

  • teardown hooks (attached in mocha/ui.js on post-require),
  • the gherkin .feature path,
  • the duplicate / missing-Feature validation.

All synchronous mocha.loadFiles() sites converted: codecept.run(), rerun, workers (parent grouping + worker threads), dry-run, check. The factory's loadFiles override is removed.

TypeScript loader migration

tsx/cjs is a require hook and can't transpile files loaded via import(). So:

  • requireModules() auto-maps tsx/cjstsx/esm with a deprecation notice (existing configs keep working),
  • tsx/esm requires "type": "module" in package.jsoninit now writes it, and an ERR_REQUIRE_CYCLE_MODULE guard tells users to add it,
  • docs (typescript, configuration, installation, parallel) updated.

⚠️ Breaking changes

  • Programmatic Workers grouping API is now async: createGroupsOfTests, createGroupsOfSuites, addTestFiles, splitTestsByGroups return Promises (you cannot load ES modules synchronously). Callers must await. Docs + unit tests updated.
  • TypeScript projects must set "type": "module" in package.json. Projects on tsx/cjs without it will hit ERR_REQUIRE_CYCLE_MODULE until they add it (the error message says so). The require value itself is auto-migrated.

Verification

  • New regression test: a test does import { config } from "codeceptjs" and asserts config.get('name') equals the running config — proves the second-copy bug is gone.
  • TS tsx/esm + "type":"module" fixture loads end-to-end with all four hooks firing.
  • ~190 existing tests green across run / gherkin / within / dry-run / rerun / workers (incl. pool mode) / container / config / pageobject / interface / retry / etc. — no regressions.

Alternative to #5636 that keeps the abstraction (no realm.js / globalThis singletons).

🤖 Generated with Claude Code

…s resolve to the live instance

Test files were loaded with synchronous require(), which under tsx/cjs
created a second CommonJS copy of lib/* modules. A test's
`import { config } from "codeceptjs"` then resolved to a disconnected copy
whose module-level singletons were never populated (#5636).

Load test files through `await import()` instead — the same way the
container already loads helpers — so the whole run shares one ES module
graph and plain imports are honest. New lib/mocha/loadTests.js mirrors
Mocha's loadFiles (lazyLoadFiles + per-file pre-require/require/post-require)
to preserve teardown hooks, the gherkin .feature path, and the
dup/missing-Feature validation.

All synchronous mocha.loadFiles() sites converted: codecept.run(), rerun,
workers (parent grouping + worker threads), dry-run, and check.

TypeScript: tsx/cjs is a require hook and can no longer transpile test
files loaded via import(). requireModules() auto-maps tsx/cjs -> tsx/esm
with a deprecation notice; tsx/esm needs "type": "module" in package.json
(init now writes it, and an ERR_REQUIRE_CYCLE_MODULE guard points users to it).

BREAKING CHANGE: the programmatic Workers grouping API
(createGroupsOfTests, createGroupsOfSuites, addTestFiles, splitTestsByGroups)
is now async. TypeScript projects must set "type": "module" in package.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@DavertMik

Copy link
Copy Markdown
Contributor Author

@mirao can you check this implementation?
can it be taken instead?

this solves the root cause of the issue (with some breaking changes)

@mirao

mirao commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

I see. You replaced tsx/cjs by tsx/esm.

In my production CodeceptJS 4.x project now I have to set both tsx/cjs (because of CodeceptJS 4.x) and tsx/esm (because of imports, e.g. "dayjs/plugin/utc" etc. to be working in CodeceptJS 4.x)
Not sure how things such as codeceptjs-dbhelper (thiagodp/codeceptjs-dbhelper#16) will work with your changes, but it might be independent.

I can test it with a beta version if you release it. With the beta I could remove the patch I did because of the internal API issues locally and test the beta without them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants