About codeceptjs-fundamentals
A testing skill for CodeceptJS 4 projects, covering configuration discovery, scenario structure, locators, and plugins. It is intended for ESM/TypeScript test maintenance and favors readable scenarios from the user's perspective.
Key features
- 01Inspect CodeceptJS 4 configuration
- 02Structure tests as user scenarios
- 03Use semantic locators
- 04Organize actors, page objects, and helpers
- 05Configure plugins, assertions, and parallel runs
Use cases
- 01Write browser tests for an existing project
- 02Organize reusable page actions
- 03Handle optional UI steps and retries
- 04Diagnose failed tests and collect run details
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ codeceptjs/ skills --skill codeceptjs-fundamentalsAfter installing, type/codeceptjs-fundamentalsin Claude Code to confirm it loaded.
Details
From the project READMECodeceptJS Fundamentals
Two jobs, in order: learn the rules below, then discover what this project has configured.
Gate
- CodeceptJS 4 is ESM/TypeScript only; tests, configs, page objects, helpers use
import/export. - No
"type": "module"in package.json → add it before anything else. - TypeScript: config
codecept.conf.ts, TS loader entry inrequire: [...]. - Project on 3.x or CommonJS (
require(), removed plugins likeautoLogin, helperNightmare) → stop, recommendmigrate-codeceptjs-4. Don't patch files piecemeal — migration is whole-project.
Main rule
- Tests are written from the user's perspective: a linear scenario of actions, readable as prose.
- Good:
I.click('Login'),I.fillField('Email', ...),I.see('Welcome')
- Good:
- Tests are declarative, helpers imperative — recommended layering:
- Scenario shows what the user does via
I.*; implementation details live below - Low-level access (
this.helpers['Playwright'].page, fetch, filesystem) works fine inside a Scenario, but it's recommended to push it into a helper and expose oneI.*action instead
- Scenario shows what the user does via
- Keep tests short: repeated sequences → actor method, page object, or step object.
- Prefer semantic locators over selectors so tests survive markup churn.
Where things go (recommended placement)
- Site-wide actions (
login, dropdowns, rich text editors) → actor file (custom steps) - Page/screen actions + locators → page object; SPA screen = one page object
- Site-wide widgets (nav, modals, datepickers) → page fragments / component objects
- Low-level driver access (DB connections, email, filesystem, complex mouse) → helper
- Data creation/cleanup via API → data objects (REST/GraphQL helper +
_after()cleanup), orApiDataFactory(I.have(...)) - Don't overengineer: no page object until an abstraction is reused across tests.
Shortcuts
- Login needed →
autoLoginplugin or actor method, not inline steps per test - Test data → create via API before the test, not through the UI
- Long test → break into several; long tests are fragile and hard to follow
- Optional UI element / conditional flow →
await tryTo(...)instead ofif (await I.grab...)— keeps scenarios linear
Architecture
- Config:
codecept.conf.{js,ts,mjs,cjs}at repo root; multiple files selected via--config <file>. - Helpers execute;
Idelegates. EveryI.<method>is routed to whichever active helper implements it (Playwright, WebDriver, Puppeteer, Appium share one API surface). Active helpers = keys underhelpers. Tests call the actor, never the engine — backends stay swappable. - DI container — why it exists:
- Everything shared (actor, helpers, page objects, support objects) registers under one container;
includemaps names → modules - Classes are auto-instantiated by the container — no
new, no manual wiring inject()returns lazy proxies: destructuring at module top resolves at call time, so circular page-object references work where plainimportwould giveundefined- Access: destructure in Scenario signature (
Scenario('...', ({ I, loginPage }) => ...)) orconst { I } = inject()once per file
- Everything shared (actor, helpers, page objects, support objects) registers under one container;
- Custom helpers extend
Helper, register underhelpers, add newI.*methods.Idoes not exist inside a helper. Compose viathis.helpers['<HelperName>'](e.g.this.helpers['Playwright'].page,this.helpers['REST'].sendGetRequest(...)).
- Plugins are event listeners on lifecycle events (
suite.*,test.*,step.*,hook.*,multiple.*). Full list:node_modules/codeceptjs/lib/event.js. Register underpluginswithenabled: true.
Config mutation trap
@codeceptjs/configuremutates resolved config at load time (setHeadlessWhen,setBrowser, ...). Static values can lie — grep for its import before trustingshow:/browser:fields.setCommonPlugins(): enablesretryFailedStep+screenshot; registers (off until-p)pause,browser,aiTrace,heal.
Plugins worth knowing
retryFailedStep— retries transient step failuresscreenshot— screenshots on failure;slides: true→output/records.htmlslideshowpageInfo— dumps URL/HTML/console on failureauth— session reuse for login (seecodeceptjs-authskill)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$nameprefix 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.
Plugins from CLI
Any plugin can be enabled/reconfigured per-run with -p <plugin>, args chained with ::
npx codeceptjs run -p aiTrace # enable for this run
npx codeceptjs run -p screenshot:on=step # reconfigure inline
npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43
screenshot,pause,aiTrace,healshare anon=trigger:fail(default except aiTrace) |step|test|file:path=...;line=N|url:pattern=<glob>browserplugin 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- Requires
@codeceptjs/configure
Effects (codeceptjs/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; returnsboolean.- Prefer
tryTooverif: 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:const banner = await tryTo(() => { I.see('Cookie banner'); I.click('Accept cookies') }) if (!banner) I.say('No cookie banner') - Auto-retries are disabled inside
tryToblocks.
- Prefer
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 withhopeThat.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 — reservewithinfor genuinely scoped blocks.
All effects return Promises — await them.
Element-based API (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; chainel.$(locator)into children without re-queryingeachElement(locator, async (el, index) => ...)— iterate collectionsexpectElement/expectAnyElement/expectAllElements(locator, fn)— custom conditions- Elements are
WebElementwrappers — same API on all helpers:getText(),getAttribute(),isVisible(),isEnabled(),getBoundingBox(),exists(),$$() - Optional purpose string improves debug logs:
element('verify discount applied', '.price', ...) - Use when built-ins don't cover it: collections, layout checks (
getBoundingBox), per-element loops, chaining ops on one element. PreferI.*for readability otherwise.
Writing tests
- Structure: one
Feature(...)per file, one or moreScenario(...)inside. No nested suites, no multiple Features per file. - Hooks:
Before,After,BeforeSuite,AfterSuite,Fail(...). - Page object lifecycle hooks:
_before()(lazy, once per test, on first use),_after()(skipped if unused),_beforeSuite(),_afterSuite(). awaitrequired for:grab*methods, imported functions, page-object methods containing async ops (elsewhere: unhandled rejections). Never for plain action steps — the recorder chains them.- Secrets:
I.fillField('Password', secret(process.env.PASSWORD))— masks logs, traces, AI prompts. Import fromcodeceptjs. - Sessions:
session(name, fn)— parallel browser context for multi-user Scenarios (chat, multi-tenant).
Locators
- ARIA locators are strongest — resilient to CSS refactors, describe what the user sees:
I.click({ role: 'button', name: 'Save' })
- Actions accept plain strings (visible text, label, placeholder,
name,aria-label) or objects ({ css },{ xpath },{ id }). - Plain string already matches
aria-label— no'aria-label=...'prefix needed. - Pass context as last argument — scoped semantic locator beats long unscoped one:
I.click('Save', '.toolbar')notI.click('#toolbar .btn-save')
- Avoid style-based class names (
.bg-green); prefer semantic ones (.btn-save). data-testid/data-qaapps → enablecustomLocator, write$name.- No semantic name fits →
locate(...)builder (.withClass,.withText,.inside,.and):locate('.button').withText('Click me').
Waiting
- Action steps auto-wait for existence + interactability. Add explicit
waitFor*only when the condition isn't tied to an interaction (modal after network call, spinner hiding). - Avoid
I.wait(N)— last resort.
Assertions
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:
- Reusable custom assertion in a helper —
I.seeTableIsOrdered('Price', 'desc'); name positivessee*, negativesdontSee*; usecodeceptjs/assertionsinside, never rawthrow new Error() - ExpectHelper (
@codeceptjs/expect-helper) — chai matchers onI:I.expectEqual,I.expectDeepEqualExcluding,I.expectMatchesPattern,I.expectJsonSchema; appears in step log like other steps codeceptjs/assertionsdirectly — dependency-free factories:equals(subject).assert(actual, expected)/.negate(...); failure messages matchI.seeformatting- Any library on grabbed data (
grab*always needsawait) — chai/jest/node:assert; fails the test but won't show as a step
Soft assertions: hopeThat(() => I.see(...)) from codeceptjs/effects — logs each failure and continues; end with hopeThat.noErrors() to fail if any were recorded.
Parallel runs
run-workers <N>— splits Scenarios across worker threadsrun-multiple <profile>— profiles viamultipleblock in config (browsers, viewports)
Config organization (recommended)
- Multiple config files per environment (
codecept.conf.js,codecept.ci.conf.js, ...); share parts via modules in aconfig/dir .envfiles +dotenvfor secrets/env-specific values- Bulk-register page objects/components by spreading exported maps into
include - Pass data from config/bootstrap into tests via
codeceptjs.container.append({ testUser })— injectable by name
Discover this project
In order; skipping steps produces wrong guesses:
- Verify setup loads:
npx codeceptjs check -c <config>— validates everything; output doubles as inventory. Fix failures before continuing. - Read the active config: helpers (+ browser/baseURL/viewport/env-driven values), plugins (incl. anything
setCommonPlugins()injects), AI provider + required env var, env selection mechanism, page objects frominclude, custom helpers. - List actions:
npx codeceptjs list -c <config>(--docsadds JSDoc;--action <name>for one). The actualI.*surface differs from built-ins when custom helpers exist — always check before suggesting a method. - List tests:
npx codeceptjs dry-run -c <config>—--stepsshows queued actions,--grepfilters,--numbersgives per-test step indices matching MCPpauseAt.- ⚠
dry-run --grepandrun --grepdo not select the same set (4.1.0):run --grepmatchesFeature+Scenario,dry-run --grepmatches the Scenario title only.dry-run --grep 'Dialogs'lists 0 tests whererun --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.
Report
Short prose summary. Must include:
- Env-driven values flagged as env-driven (
process.env.BROWSER || 'chromium', not just'chromium') - Conflicts flagged (static
show: truevssetHeadlessWhen(CI);authconfigured but credential env vars missing) - No config at root and no
--configreferenced → recommendnpx codeceptjs init ., stop - 3.x/CommonJS detected → recommend
migrate-codeceptjs-4, stop