npx skills add ...
npx skills add codeceptjs/skills --skill codeceptjs-fundamentals
Run first when working with any CodeceptJS 4 project — before writing, debugging, refactoring, or migrating tests. Teaches the framework's non-obvious rules and runs four-step discovery (`check` → read config → `list` → `dry-run`) reporting which helpers, plugins, page objects, custom actions, and tests are active. Other CodeceptJS skills depend on this output.
npx skills add codeceptjs/skills --skill codeceptjs-fundamentals
Two jobs, in order: learn the rules below, then discover what this project has configured.
import/export."type": "module" in package.json → add it before anything else.codecept.conf.ts, TS loader entry in require: [...].require(), removed plugins like autoLogin, helper Nightmare) → stop, recommend migrate-codeceptjs-4. Don't patch files piecemeal — migration is whole-project.I.click('Login'), I.fillField('Email', ...), I.see('Welcome')I.*; implementation details live belowthis.helpers['Playwright'].page, fetch, filesystem) works fine inside a Scenario, but it's recommended to push it into a helper and expose one I.* action insteadlogin, dropdowns, rich text editors) → actor file (custom steps)_after() cleanup), or ApiDataFactory (I.have(...))autoLogin plugin or actor method, not inline steps per testawait tryTo(...) instead of if (await I.grab...) — keeps scenarios linearcodecept.conf.{js,ts,mjs,cjs} at repo root; multiple files selected via --config <file>.I delegates. Every I.<method> is routed to whichever active helper implements it (Playwright, WebDriver, Puppeteer, Appium share one API surface). Active helpers = keys under helpers. Tests call the actor, never the engine — backends stay swappable.include maps names → modulesnew, no manual wiringinject() returns lazy proxies: destructuring at module top resolves at call time, so circular page-object references work where plain import would give undefinedScenario('...', ({ I, loginPage }) => ...)) or const { I } = inject() once per fileHelper, register under helpers, add new I.* methods.
I does not exist inside a helper. Compose via this.helpers['<HelperName>'] (e.g. this.helpers['Playwright'].page, this.helpers['REST'].sendGetRequest(...)).suite.*, test.*, step.*, hook.*, multiple.*). Full list: node_modules/codeceptjs/lib/event.js. Register under plugins with enabled: true.@codeceptjs/configure mutates resolved config at load time (setHeadlessWhen, setBrowser, ...). Static values can lie — grep for its import before trusting show: / browser: fields.setCommonPlugins(): enables retryFailedStep + screenshot; registers (off until -p) pause, browser, aiTrace, heal.retryFailedStep — retries transient step failuresscreenshot — screenshots on failure; slides: true → output/records.html slideshowpageInfo — dumps URL/HTML/console on failureauth — session reuse for login (see codeceptjs-auth skill)aiTrace — per-step screenshots/HTML/ARIA/console for AI debuggingpause — interactive pauseheal — AI-suggested fixes for broken steps (off in --debug)screencast — video of the runcustomLocator — maps $name prefix to team's test attribute (data-testid, data-qa)browser — CLI-only override of browser helper config (see below)Note: tryTo, retryTo, eachElement are not plugins in 4.x — import them from codeceptjs/effects.
Any plugin can be enabled/reconfigured per-run with -p <plugin>, args chained with ::
screenshot, pause, aiTrace, heal share an on= trigger: fail (default except aiTrace) | step | test | file:path=...;line=N | url:pattern=<glob>browser plugin overrides without touching config — CI matrix legs, one-off env variants:
-p browser:hide / -p browser:show / -p browser:browser=firefox / -p browser:windowSize=1280x800@codeceptjs/configurecodeceptjs/effects)Flow-control functions imported from codeceptjs/effects. In 4.x these are no longer plugins/globals.
tryTo(() => ...) — runs steps that may fail without stopping the test; returns boolean.
tryTo over if: scenarios should stay linear — instead of branching on a grabbed value to decide whether a UI state exists, attempt the optional steps and branch on the boolean result:
tryTo blocks.retryTo(() => ..., maxTries, pollInterval = 200) — retries a step block until it succeeds (flaky elements, animations); callback receives the current attempt count.hopeThat(() => ...) — soft assertions (see Assertions); end with hopeThat.noErrors().within(locator | { frame }, fn) — scopes resolution to subtree or iframe; can return values (await). Prefer the context parameter of individual actions (I.click('Save', '.toolbar')) when possible — reserve within for genuinely scoped blocks.All effects return Promises — await them.
codeceptjs/els)Hybrid style: mix I.* with direct element access. Import { element, eachElement, expectElement, expectAnyElement, expectAllElements } from 'codeceptjs/els'.
element(locator, async el => { ... }) — scoped access to one element; chain el.$(locator) into children without re-queryingeachElement(locator, async (el, index) => ...) — iterate collectionsexpectElement / expectAnyElement / expectAllElements(locator, fn) — custom conditionsWebElement wrappers — same API on all helpers: getText(), getAttribute(), isVisible(), isEnabled(), getBoundingBox(), exists(), $$()element('verify discount applied', '.price', ...)getBoundingBox), per-element loops, chaining ops on one element. Prefer I.* for readability otherwise.Feature(...) per file, one or more Scenario(...) inside. No nested suites, no multiple Features per file.Before, After, BeforeSuite, AfterSuite, Fail(...)._before() (lazy, once per test, on first use), _after() (skipped if unused), _beforeSuite(), _afterSuite().await required for: grab* methods, imported functions, page-object methods containing async ops (elsewhere: unhandled rejections). Never for plain action steps — the recorder chains them.I.fillField('Password', secret(process.env.PASSWORD)) — masks logs, traces, AI prompts. Import from codeceptjs.session(name, fn) — parallel browser context for multi-user Scenarios (chat, multi-tenant).I.click({ role: 'button', name: 'Save' })name, aria-label) or objects ({ css }, { xpath }, { id }).aria-label — no 'aria-label=...' prefix needed.I.click('Save', '.toolbar') not I.click('#toolbar .btn-save').bg-green); prefer semantic ones (.btn-save).data-testid/data-qa apps → enable customLocator, write $name.locate(...) builder (.withClass, .withText, .inside, .and): locate('.button').withText('Click me').waitFor* only when the condition isn't tied to an interaction (modal after network call, spinner hiding).I.wait(N) — last resort.Built-in browser assertions come first: I.see, I.seeTextEquals, I.seeElement, I.seeInField, I.seeNumberOfElements, I.seeInCurrentUrl (+ dontSee* counterparts). Clear failures, recorder-integrated. see matches visible text; hidden DOM content needs seeInSource / seeElementInDOM.
For what built-ins don't cover, in order of preference:
I.seeTableIsOrdered('Price', 'desc'); name positives see*, negatives dontSee*; use codeceptjs/assertions inside, never raw throw new Error()@codeceptjs/expect-helper) — chai matchers on I: I.expectEqual, I.expectDeepEqualExcluding, I.expectMatchesPattern, I.expectJsonSchema; appears in step log like other stepscodeceptjs/assertions directly — dependency-free factories: equals(subject).assert(actual, expected) / .negate(...); failure messages match I.see formattinggrab* always needs await) — chai/jest/node:assert; fails the test but won't show as a stepSoft assertions: hopeThat(() => I.see(...)) from codeceptjs/effects — logs each failure and continues; end with hopeThat.noErrors() to fail if any were recorded.
run-workers <N> — splits Scenarios across worker threadsrun-multiple <profile> — profiles via multiple block in config (browsers, viewports)codecept.conf.js, codecept.ci.conf.js, ...); share parts via modules in a config/ dir.env files + dotenv for secrets/env-specific valuesincludecodeceptjs.container.append({ testUser }) — injectable by nameIn order; skipping steps produces wrong guesses:
npx codeceptjs check -c <config> — validates everything; output doubles as inventory. Fix failures before continuing.setCommonPlugins() injects), AI provider + required env var, env selection mechanism, page objects from include, custom helpers.npx codeceptjs list -c <config> (--docs adds JSDoc; --action <name> for one). The actual I.* surface differs from built-ins when custom helpers exist — always check before suggesting a method.npx codeceptjs dry-run -c <config> — --steps shows queued actions, --grep filters, --numbers gives per-test step indices matching MCP pauseAt.
dry-run --grep and run --grep do not select the same set (4.1.0): run --grep matches Feature + Scenario, dry-run --grep matches the Scenario title only. dry-run --grep 'Dialogs' lists 0 tests where run --grep 'Dialogs' executes all 11. Never size a run from a dry-run's grep, and target a whole Feature by file path (run tests/foo_test.ts) when the selection must be exact.Gherkin projects: npx codeceptjs gherkin:steps -c <config>.
Reference docs live under node_modules/codeceptjs/docs/ — read them instead of guessing APIs.
Short prose summary. Must include:
process.env.BROWSER || 'chromium', not just 'chromium')show: true vs setHeadlessWhen(CI); auth configured but credential env vars missing)--config referenced → recommend npx codeceptjs init ., stopmigrate-codeceptjs-4, stop