Migrate CodeceptJS 3.x → 4.x
CodeceptJS 4 is ESM-only and TypeScript-first. There is no compatibility shim for CommonJS — every helper, page object, custom step, and config file must be ESM. This skill drives a project through the upgrade end-to-end.
The authoritative reference is node_modules/codeceptjs/docs/migration-4.md (after npm install codeceptjs@4). Read it once; this skill orchestrates the work and surfaces the parts agents miss.
When to trigger
Detect before acting. Look for any of:
package.json lists "codeceptjs": "^3" / "3.x", or has no "type": "module".
- Tests, page objects, helpers, or config use
require() / module.exports against CodeceptJS.
- Config still references removed helpers (
Nightmare, Protractor, TestCafe, AI, SoftExpectHelper, Mochawesome) or removed plugins (autoLogin, tryTo, retryTo, eachElement, commentStep, fakerTransform, enhancedRetryFailedStep, allure, htmlReporter, wdio, selenoid, screenshotOnFail, pauseOnFail, stepByStepReport).
- 3.x APIs:
ai.request, Joi schemas in seeResponseMatchesJsonSchema, restart: 'browser', I.retry(), I.limitTime(), Playwright customLocators, I.softExpect* / I.flushSoftAssertions.
--reporter mochawesome in scripts/CI, or mochawesome in package.json / mocha.reporterOptions.
If none of these hint at 3.x, the project is likely on 4.x already — hand back to codeceptjs-fundamentals.
Workflow
Phases run in order. Commit at each boundary so any regression is bisectable.
1. Bump Node and CodeceptJS
- Confirm Node ≥ 16 (recommended 20+); update
engines.node and CI runners.
npm install codeceptjs@4.
- TypeScript projects:
npm install --save-dev tsx. Replaces ts-node/esm, which now warns.
2. Convert the project to ESM
- Add
"type": "module" to package.json. Without this, every .js file parses as CommonJS and ESM imports throw at load time.
- Convert config, page objects, custom helpers, custom steps, programmatic usage:
const Helper = require('@codeceptjs/helper') → import Helper from '@codeceptjs/helper'
module.exports = X → export default X (or named exports)
const { codecept, container, event } = require('codeceptjs') → import codeceptjs, { container, event } from 'codeceptjs'
Container.create() and Config.load() are now async — await them.
- Files that genuinely need CJS (rare; usually third-party shims): rename to
.cjs.
3. Remove or replace deleted helpers and plugins
| Old |
Replacement |
Helper Nightmare / Protractor / TestCafe |
Playwright, Puppeteer, or WebDriver. |
Helper AI |
Top-level ai: config + aiTrace plugin. |
Helper SoftExpectHelper |
import { hopeThat } from 'codeceptjs/effects'; call hopeThat.noErrors() at end of scenario. |
Helper Mochawesome |
Stop and ask the user — see "Mochawesome decision" below. Do not silently delete it. |
Plugin autoLogin |
auth plugin (see the codeceptjs-auth skill). |
Plugin tryTo / retryTo |
import { tryTo, retryTo } from 'codeceptjs/effects' |
Plugin eachElement |
import { eachElement } from 'codeceptjs/els' |
Plugin commentStep |
import step from 'codeceptjs/steps' → step.section('name') / step.endSection() |
Plugin fakerTransform |
import { faker } from '@faker-js/faker' directly in tests. |
Plugin enhancedRetryFailedStep |
Renamed to retryFailedStep. |
Plugin allure / htmlReporter |
@testomatio/reporter (HTML pipe). For JUnit XML, the junitReporter plugin. |
Plugin wdio / selenoid |
Configure via helpers.WebDriver, or run externally. |
Plugin screenshotOnFail / pauseOnFail / stepByStepReport |
Renamed screenshot / pause / screenshot with slides: true. Old names still resolve but emit a deprecation warning. |
Mochawesome decision
The bundled Mochawesome helper is removed in 4.x (the helper class, the mochawesome dependency, and the worker report-dir wiring). The mochawesome reporter itself still works — it is a stock Mocha reporter and never needed CodeceptJS to bundle it.
This is a user choice — do not pick for them. When you detect Mochawesome (the helper in helpers:, --reporter mochawesome, or mochawesome in package.json/mocha.reporterOptions), stop and ask which path they want:
- Switch to
@testomatio/reporter (recommended). Richer HTML report (steps, screenshots, videos, traces), worker-safe, no helper or internal coupling. npm install --save-dev @testomatio/reporter, enable the testomatio plugin, run with TESTOMATIO_DISABLE_UPLOAD=1. For JUnit XML add the junitReporter plugin. This is the path to recommend.
- Keep Mochawesome — report only. Remove the
Mochawesome helper from helpers:, keep npm i -D mochawesome and --reporter mochawesome --reporter-options reportDir=output. Loses the auto-embedded failure screenshots the helper provided.
- Keep Mochawesome — with embedded screenshots. Remove the helper from
helpers: and re-create it as a project-local custom helper that wraps mochawesome/addContext (a faithful port is in the migration guide, section "Keeping Mochawesome"). Flag the two caveats: it imports non-semver-stable codeceptjs/lib/* internals (pin the version), and the screenshot-plugin filename sync is no longer automatic (set uniqueScreenshotNames identically on the helper and the screenshot plugin).
In all cases also: remove mochawesome report-dir handling from any custom worker scripts, and replace any reliance on I.addMochawesomeContext() (only option 3 keeps it).
Present options 1–3 explicitly and let the user decide before changing anything. Default the recommendation to option 1. The full reference (before/after configs + the custom-helper port) is in node_modules/codeceptjs/docs/migration-4.md → "Mochawesome → Testomat.io Reporter".
4. Update changed APIs
- AI config — delete the
request function. Install Vercel AI SDK + provider (npm install ai @ai-sdk/openai) and set ai: { model: openai('gpt-5') }. Same shape for @ai-sdk/anthropic, @ai-sdk/google, etc.
- JSON schemas —
I.seeResponseMatchesJsonSchema() now uses Zod, not Joi. Rewrite (Joi.object().keys({...}) → z.object({...}), Joi.string().required() → z.string(), etc.) and npm uninstall joi.
- Playwright — replace
restart: 'browser' with 'session' (default), 'context', or 'keep'. Drop customLocators config; use the customLocator plugin or ARIA locators ({ role: 'button', name: 'Submit' }).
I.retry() / I.limitTime() — removed (not just deprecated). Replace with the step options API: import step from 'codeceptjs/steps', then pass the config as the last argument of the step. I.retry(3).click('Submit') → I.click('Submit', step.retry(3)); I.limitTime(10).fillField('Email', x) → I.fillField('Email', x, step.timeout(10)). Also step.opts({ elementIndex: 2 }). Behavior is unchanged except the option no longer leaks onto the following step. recorder.retry() is unaffected (custom helpers only).
within and session — no longer global. import { within, session } from 'codeceptjs' (or within from codeceptjs/effects). Both return Promises — await when you need the return value.
- Subpath imports —
codeceptjs/effects, codeceptjs/els, codeceptjs/steps, codeceptjs/store, codeceptjs/assertions. See the migration guide for the full list.
tryTo / hopeThat — return Promise<boolean> (the 3.x Promise<T | false> shape is gone).
5. Adopt noGlobals: true
3.x leaked Helper, actor, inject, share, secret, locate, dataTable, within, session, codecept_dir, output_dir as globals. 4.x defaults noGlobals: true in new configs and warns for projects without it. Add it to config and replace globals with explicit imports from codeceptjs / @codeceptjs/helper. Test-runner globals (Feature, Scenario, Before, After, BeforeSuite, AfterSuite, pause, inject, share) and BDD step-definition globals (Given, When, Then, And) stay — they're scope-injected by the runner.
6. Bump dependent packages
If your project depends directly on these, verify nothing breaks: chai (4 → 6, ESM-only), chai-as-promised (7 → 8), commander (11 → 14), @faker-js/faker (9 → 10), chokidar (4 → 5), @cucumber/gherkin (35 → 38), webdriverio (9.12 → 9.23). joi and testcafe are removed. See migration guide §7 for the full table.
7. Verify
Run, in order:
npx codeceptjs check -c <config> — surfaces config / helper / plugin / page object issues without spinning up a browser.
npx codeceptjs run --debug on a small smoke scenario. Confirm steps execute and the report is sane.
npx codeceptjs run-workers 2 — confirms the worker event dispatcher fires per-test events the same way it does in single-process mode (4.x change).
- TypeScript users: confirm error stack traces point at
.ts source lines (proves tsx is loaded, not ts-node/esm).
- Grep the repo for
tryTo(, retryTo(, eachElement(, commentStep(, softExpect, I.softExpect, Joi., restart: 'browser', I.retry(, I.limitTime(, and (unless the user chose to keep it) Mochawesome / --reporter mochawesome — none should remain. step.retry( / step.timeout( passed as a step argument is the expected replacement, not a leftover.
- If the project used
autoLogin: confirm the auth plugin restores sessions and roles.
Related skills
codeceptjs-auth — replacement for the removed autoLogin plugin
codeceptjs-fundamentals — run after migration to confirm wiring
- Full reference:
node_modules/codeceptjs/docs/migration-4.md
1---2name: migrate-codeceptjs-43description: Migrate a CodeceptJS 3.x project to 4.x. Trigger when `package.json` pins `codeceptjs` at 3.x or is missing `"type": "module"`, when test files still use CommonJS (`require()` / `module.exports`) against CodeceptJS APIs, when config references removed helpers (`Nightmare`, `Protractor`, `TestCafe`, `AI`, `SoftExpectHelper`, `Mochawesome`) or removed plugins (`autoLogin`, `tryTo`, `retryTo`, `eachElement`, `commentStep`, `fakerTransform`, `enhancedRetryFailedStep`, `allure`, `htmlReporter`, `wdio`, `selenoid`, `screenshotOnFail`, `pauseOnFail`, `stepByStepReport`), or when 3.x APIs are in use (`ai.request` function, Joi schemas in `seeResponseMatchesJsonSchema`, `restart: 'browser'`, `I.retry()`, `I.limitTime()`, Playwright `customLocators`).4---56# Migrate CodeceptJS 3.x → 4.x78CodeceptJS 4 is **ESM-only and TypeScript-first**. There is no compatibility shim for CommonJS — every helper, page object, custom step, and config file must be ESM. This skill drives a project through the upgrade end-to-end.910The authoritative reference is `node_modules/codeceptjs/docs/migration-4.md` (after `npm install codeceptjs@4`). Read it once; this skill orchestrates the work and surfaces the parts agents miss.1112## When to trigger1314Detect *before* acting. Look for any of:1516- `package.json` lists `"codeceptjs": "^3"` / `"3.x"`, or has no `"type": "module"`.17- Tests, page objects, helpers, or config use `require()` / `module.exports` against CodeceptJS.18- Config still references removed helpers (`Nightmare`, `Protractor`, `TestCafe`, `AI`, `SoftExpectHelper`, `Mochawesome`) or removed plugins (`autoLogin`, `tryTo`, `retryTo`, `eachElement`, `commentStep`, `fakerTransform`, `enhancedRetryFailedStep`, `allure`, `htmlReporter`, `wdio`, `selenoid`, `screenshotOnFail`, `pauseOnFail`, `stepByStepReport`).19- 3.x APIs: `ai.request`, Joi schemas in `seeResponseMatchesJsonSchema`, `restart: 'browser'`, `I.retry()`, `I.limitTime()`, Playwright `customLocators`, `I.softExpect*` / `I.flushSoftAssertions`.20- `--reporter mochawesome` in scripts/CI, or `mochawesome` in `package.json` / `mocha.reporterOptions`.2122If none of these hint at 3.x, the project is likely on 4.x already — hand back to `codeceptjs-fundamentals`.2324## Workflow2526Phases run in order. Commit at each boundary so any regression is bisectable.2728### 1. Bump Node and CodeceptJS2930- Confirm Node ≥ 16 (recommended 20+); update `engines.node` and CI runners.31- `npm install codeceptjs@4`.32- TypeScript projects: `npm install --save-dev tsx`. Replaces `ts-node/esm`, which now warns.3334### 2. Convert the project to ESM3536- Add `"type": "module"` to `package.json`. **Without this, every `.js` file parses as CommonJS and ESM imports throw at load time.**37- Convert config, page objects, custom helpers, custom steps, programmatic usage:38 - `const Helper = require('@codeceptjs/helper')` → `import Helper from '@codeceptjs/helper'`39 - `module.exports = X` → `export default X` (or named exports)40 - `const { codecept, container, event } = require('codeceptjs')` → `import codeceptjs, { container, event } from 'codeceptjs'`41- `Container.create()` and `Config.load()` are now async — `await` them.42- Files that genuinely need CJS (rare; usually third-party shims): rename to `.cjs`.4344### 3. Remove or replace deleted helpers and plugins4546| Old | Replacement |47|---|---|48| Helper `Nightmare` / `Protractor` / `TestCafe` | `Playwright`, `Puppeteer`, or `WebDriver`. |49| Helper `AI` | Top-level `ai:` config + `aiTrace` plugin. |50| Helper `SoftExpectHelper` | `import { hopeThat } from 'codeceptjs/effects'`; call `hopeThat.noErrors()` at end of scenario. |51| Helper `Mochawesome` | **Stop and ask the user** — see "Mochawesome decision" below. Do not silently delete it. |52| Plugin `autoLogin` | `auth` plugin (see the `codeceptjs-auth` skill). |53| Plugin `tryTo` / `retryTo` | `import { tryTo, retryTo } from 'codeceptjs/effects'` |54| Plugin `eachElement` | `import { eachElement } from 'codeceptjs/els'` |55| Plugin `commentStep` | `import step from 'codeceptjs/steps'` → `step.section('name')` / `step.endSection()` |56| Plugin `fakerTransform` | `import { faker } from '@faker-js/faker'` directly in tests. |57| Plugin `enhancedRetryFailedStep` | Renamed to `retryFailedStep`. |58| Plugin `allure` / `htmlReporter` | `@testomatio/reporter` (HTML pipe). For JUnit XML, the `junitReporter` plugin. |59| Plugin `wdio` / `selenoid` | Configure via `helpers.WebDriver`, or run externally. |60| Plugin `screenshotOnFail` / `pauseOnFail` / `stepByStepReport` | Renamed `screenshot` / `pause` / `screenshot` with `slides: true`. Old names still resolve but emit a deprecation warning. |6162#### Mochawesome decision6364The bundled `Mochawesome` **helper** is removed in 4.x (the helper class, the `mochawesome` dependency, and the worker report-dir wiring). The `mochawesome` **reporter** itself still works — it is a stock Mocha reporter and never needed CodeceptJS to bundle it.6566This is a **user choice — do not pick for them**. When you detect Mochawesome (the helper in `helpers:`, `--reporter mochawesome`, or `mochawesome` in `package.json`/`mocha.reporterOptions`), stop and ask which path they want:67681. **Switch to `@testomatio/reporter` (recommended).** Richer HTML report (steps, screenshots, videos, traces), worker-safe, no helper or internal coupling. `npm install --save-dev @testomatio/reporter`, enable the `testomatio` plugin, run with `TESTOMATIO_DISABLE_UPLOAD=1`. For JUnit XML add the `junitReporter` plugin. This is the path to recommend.692. **Keep Mochawesome — report only.** Remove the `Mochawesome` helper from `helpers:`, keep `npm i -D mochawesome` and `--reporter mochawesome --reporter-options reportDir=output`. Loses the auto-embedded failure screenshots the helper provided.703. **Keep Mochawesome — with embedded screenshots.** Remove the helper from `helpers:` and re-create it as a **project-local custom helper** that wraps `mochawesome/addContext` (a faithful port is in the migration guide, section "Keeping Mochawesome"). Flag the two caveats: it imports non-semver-stable `codeceptjs/lib/*` internals (pin the version), and the screenshot-plugin filename sync is no longer automatic (set `uniqueScreenshotNames` identically on the helper and the `screenshot` plugin).7172In all cases also: remove `mochawesome` report-dir handling from any custom worker scripts, and replace any reliance on `I.addMochawesomeContext()` (only option 3 keeps it).7374Present options 1–3 explicitly and let the user decide before changing anything. Default the recommendation to option 1. The full reference (before/after configs + the custom-helper port) is in `node_modules/codeceptjs/docs/migration-4.md` → "Mochawesome → Testomat.io Reporter".7576### 4. Update changed APIs7778- **AI config** — delete the `request` function. Install Vercel AI SDK + provider (`npm install ai @ai-sdk/openai`) and set `ai: { model: openai('gpt-5') }`. Same shape for `@ai-sdk/anthropic`, `@ai-sdk/google`, etc.79- **JSON schemas** — `I.seeResponseMatchesJsonSchema()` now uses Zod, not Joi. Rewrite (`Joi.object().keys({...})` → `z.object({...})`, `Joi.string().required()` → `z.string()`, etc.) and `npm uninstall joi`.80- **Playwright** — replace `restart: 'browser'` with `'session'` (default), `'context'`, or `'keep'`. Drop `customLocators` config; use the `customLocator` plugin or ARIA locators (`{ role: 'button', name: 'Submit' }`).81- **`I.retry()` / `I.limitTime()`** — **removed** (not just deprecated). Replace with the step options API: `import step from 'codeceptjs/steps'`, then pass the config as the **last argument** of the step. `I.retry(3).click('Submit')` → `I.click('Submit', step.retry(3))`; `I.limitTime(10).fillField('Email', x)` → `I.fillField('Email', x, step.timeout(10))`. Also `step.opts({ elementIndex: 2 })`. Behavior is unchanged except the option no longer leaks onto the following step. `recorder.retry()` is unaffected (custom helpers only).82- **`within` and `session`** — no longer global. `import { within, session } from 'codeceptjs'` (or `within` from `codeceptjs/effects`). Both return Promises — `await` when you need the return value.83- **Subpath imports** — `codeceptjs/effects`, `codeceptjs/els`, `codeceptjs/steps`, `codeceptjs/store`, `codeceptjs/assertions`. See the migration guide for the full list.84- **`tryTo` / `hopeThat`** — return `Promise<boolean>` (the 3.x `Promise<T | false>` shape is gone).8586### 5. Adopt `noGlobals: true`87883.x leaked `Helper`, `actor`, `inject`, `share`, `secret`, `locate`, `dataTable`, `within`, `session`, `codecept_dir`, `output_dir` as globals. 4.x defaults `noGlobals: true` in new configs and warns for projects without it. Add it to config and replace globals with explicit imports from `codeceptjs` / `@codeceptjs/helper`. Test-runner globals (`Feature`, `Scenario`, `Before`, `After`, `BeforeSuite`, `AfterSuite`, `pause`, `inject`, `share`) and BDD step-definition globals (`Given`, `When`, `Then`, `And`) **stay** — they're scope-injected by the runner.8990### 6. Bump dependent packages9192If your project depends directly on these, verify nothing breaks: `chai` (4 → 6, ESM-only), `chai-as-promised` (7 → 8), `commander` (11 → 14), `@faker-js/faker` (9 → 10), `chokidar` (4 → 5), `@cucumber/gherkin` (35 → 38), `webdriverio` (9.12 → 9.23). `joi` and `testcafe` are removed. See migration guide §7 for the full table.9394### 7. Verify9596Run, in order:97981. `npx codeceptjs check -c <config>` — surfaces config / helper / plugin / page object issues without spinning up a browser.992. `npx codeceptjs run --debug` on a small smoke scenario. Confirm steps execute and the report is sane.1003. `npx codeceptjs run-workers 2` — confirms the worker event dispatcher fires per-test events the same way it does in single-process mode (4.x change).1014. TypeScript users: confirm error stack traces point at `.ts` source lines (proves `tsx` is loaded, not `ts-node/esm`).1025. Grep the repo for `tryTo(`, `retryTo(`, `eachElement(`, `commentStep(`, `softExpect`, `I.softExpect`, `Joi.`, `restart: 'browser'`, `I.retry(`, `I.limitTime(`, and (unless the user chose to keep it) `Mochawesome` / `--reporter mochawesome` — none should remain. `step.retry(` / `step.timeout(` passed as a step argument is the expected replacement, not a leftover.1036. If the project used `autoLogin`: confirm the `auth` plugin restores sessions and roles.104105## Related skills106107- `codeceptjs-auth` — replacement for the removed `autoLogin` plugin108- `codeceptjs-fundamentals` — run **after** migration to confirm wiring109- Full reference: `node_modules/codeceptjs/docs/migration-4.md`