Test Structure
A CodeceptJS test file contains one Feature (suite) and one or more Scenarios (tests).
Feature('User Authentication')
Scenario('user logs in', ({ I }) => { I.amOnPage('/login') I.fillField('Email', 'user@example.com') I.fillField('Password', secret('123456')) I.click('Sign In') I.see('Welcome')})Feature
Section titled “Feature”Feature(title, config?) declares a suite. Each test file contains exactly one Feature.
Feature('User Authentication')An optional config object sets defaults for all scenarios:
Feature('Payment Processing', { retries: 2, timeout: 30000})Available options:
retries— number of times to retry failed scenarios before marking as failed (see Retry Mechanisms)timeout— maximum time in milliseconds for each scenario to complete (see Timeouts)retryBefore— number of times to retry the Before hook if it failsretryAfter— number of times to retry the After hook if it failsretryBeforeSuite— number of times to retry the BeforeSuite hook if it failsretryAfterSuite— number of times to retry the AfterSuite hook if it fails
Unlike Mocha/Jest, nesting suites is not allowed — each file maps to exactly one feature.
Scenario
Section titled “Scenario”Scenario(title, config?, fn) declares a test. The function receives an object with I (the actor), test object, and any page objects declared in include config:
Scenario('guest checkout', ({ I, checkoutPage }) => { checkoutPage.open() I.see('Order Summary')})Access the test object to store metadata and artifacts for custom reporting:
Scenario('payment processing', ({ I, test }) => { test.meta.transactionId = '12345' test.artifacts.receipt = 'receipts/order-12345.pdf' I.amOnPage('/checkout')})Available properties:
test.title— test nametest.tags— extracted tags from test name (e.g.,@smoke,@critical)test.steps— array of executed stepstest.artifacts— store screenshots, videos, logs, or filestest.meta— custom metadata for reporterstest.notes— array for adding notes or annotationstest.file— path to test filetest.state— current state (pending, passed, failed)test.duration— execution time in millisecondstest.fullTitle()— full title including suite name
An optional config object can customize the scenario:
Scenario('slow test', { timeout: 60000, retries: 3}, ({ I }) => { // ...})Available options:
timeout— maximum time in milliseconds for scenario to complete (see Timeouts)retries— number of times to retry the scenario if it fails (see Retry Mechanisms)meta— metadata object with key-value pairs for reporting or filtering[helperName]— helper-specific configuration (e.g.,Playwright: { headless: false })cookies— pre-loaded cookies for authentication (used by auth plugin)user— user identifier for session management (used by auth plugin)disableRetryFailedStep— disable automatic step retries for this scenario
Dynamic Configuration
Section titled “Dynamic Configuration”Override helper config for a single scenario using .config():
Scenario('run in firefox', ({ I }) => { // ...}).config({ browser: 'firefox' })To target a specific helper, pass its name as the first argument:
Scenario('use v2 API', ({ I }) => { // ...}).config('REST', { endpoint: 'https://api.mysite.com/v2' })Pass a function to derive config from the test object — useful for cloud providers:
Scenario('report to BrowserStack', ({ I }) => { // ...}).config((test) => ({ desiredCapabilities: { project: test.suite.title, name: test.title, }}))Apply config to all scenarios in a suite via Feature:
Feature('Admin Panel').config({ url: 'https://mysite.com/admin' })Config changes are reverted after the test or suite completes. Some options — such as browser when restart: false — cannot be changed after the browser has started.
Data-Driven Scenarios
Section titled “Data-Driven Scenarios”Use Data().Scenario to run the same scenario with multiple datasets:
const users = new DataTable(['role', 'email'])users.add(['admin', 'admin@example.com'])users.add(['user', 'user@example.com'])
Data(users).Scenario('user can log in', ({ I, current }) => { I.fillField('Email', current.email) I.click('Login') I.see(`Logged in as ${current.role}`)})▶ See Data Driven Tests for more details.
Append a tag to the scenario title:
Scenario('update user profile @slow', ...)Or use the tag() method:
Scenario('update user profile', ({ I }) => { // ...}).tag('@slow').tag('important')Run tagged tests with --grep:
npx codeceptjs run --grep '@slow'Use regex for complex filtering:
# both @smoke2 and @smoke3npx codeceptjs run --grep '(?=.*@smoke2)(?=.*@smoke3)'# @smoke2 or @smoke3npx codeceptjs run --grep '@smoke2|@smoke3'# all except @smoke4npx codeceptjs run --grep '(?=.*)^(?!.*@smoke4)'Skipping & Focusing
Section titled “Skipping & Focusing”xScenario('skipped test', ...) // skipScenario.skip('skipped test', ...) // skipScenario.only('focused test', ...) // run only this test
xFeature('Skipped Suite') // skip entire fileFeature.skip('Skipped Suite') // skip entire fileFeature.only('Run Only This Suite') // focus entire fileTodo Scenarios
Section titled “Todo Scenarios”Mark scenarios as planned but not yet implemented:
Scenario.todo('user can reset password')
Scenario.todo('user can change avatar', ({ I }) => { /** * 1. Open profile settings * 2. Upload new avatar * Result: avatar is updated */})Before / After
Section titled “Before / After”Run code before or after each scenario in the file:
Before(({ I }) => { I.amOnPage('/')})
After(({ I }) => { I.clearCookie()})These are equivalent to beforeEach / afterEach in Mocha/Jest.
Hooks can be retried on failure:
Before(({ I }) => { I.amOnPage('/dashboard')}).retry(3)BeforeSuite / AfterSuite
Section titled “BeforeSuite / AfterSuite”Run code once before or after all scenarios in the file — equivalent to beforeAll / afterAll:
BeforeSuite(async ({ I }) => { // seed test data before any scenario runs await I.executeScript(() => window.resetDatabase())})
AfterSuite(async ({ I }) => { await I.executeScript(() => window.cleanupDatabase())})Note: The browser is available in
BeforeSuitewhen using Playwright or Puppeteer helpers.
Hooks can also be configured at Feature level:
Feature('My Suite', { retryBefore: 3, retryBeforeSuite: 2, retryAfter: 1, retryAfterSuite: 3,})