Skip to content

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(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 fails
  • retryAfter — number of times to retry the After hook if it fails
  • retryBeforeSuite — number of times to retry the BeforeSuite hook if it fails
  • retryAfterSuite — 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(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 name
  • test.tags — extracted tags from test name (e.g., @smoke, @critical)
  • test.steps — array of executed steps
  • test.artifacts — store screenshots, videos, logs, or files
  • test.meta — custom metadata for reporters
  • test.notes — array for adding notes or annotations
  • test.file — path to test file
  • test.state — current state (pending, passed, failed)
  • test.duration — execution time in milliseconds
  • test.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

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.

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:

Terminal window
npx codeceptjs run --grep '@slow'

Use regex for complex filtering:

Terminal window
# both @smoke2 and @smoke3
npx codeceptjs run --grep '(?=.*@smoke2)(?=.*@smoke3)'
# @smoke2 or @smoke3
npx codeceptjs run --grep '@smoke2|@smoke3'
# all except @smoke4
npx codeceptjs run --grep '(?=.*)^(?!.*@smoke4)'
xScenario('skipped test', ...) // skip
Scenario.skip('skipped test', ...) // skip
Scenario.only('focused test', ...) // run only this test
xFeature('Skipped Suite') // skip entire file
Feature.skip('Skipped Suite') // skip entire file
Feature.only('Run Only This Suite') // focus entire file

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
*/
})

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)

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 BeforeSuite when using Playwright or Puppeteer helpers.

Hooks can also be configured at Feature level:

Feature('My Suite', {
retryBefore: 3,
retryBeforeSuite: 2,
retryAfter: 1,
retryAfterSuite: 3,
})