codeceptjs
Version:
Supercharged End 2 End Testing Framework for NodeJS
276 lines (206 loc) • 6.8 kB
Markdown
---
permalink: /test-structure
title: Test Structure
---
A CodeceptJS test file contains one **Feature** (suite) and one or more **Scenarios** (tests).
```js
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.
```js
Feature('User Authentication')
```
An optional config object sets defaults for all scenarios:
```js
Feature('Payment Processing', {
retries: 2,
timeout: 30000
})
```
Available options:
- `retries` — number of times to retry failed scenarios before marking as failed (see [Retry Mechanisms](/retry))
- `timeout` — maximum time in milliseconds for each scenario to complete (see [Timeouts](/advanced#timeout))
- `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
`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:
```js
Scenario('guest checkout', ({ I, checkoutPage }) => {
checkoutPage.open()
I.see('Order Summary')
})
```
Access the `test` object to store metadata and artifacts for custom reporting:
```js
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:
```js
Scenario('slow test', {
timeout: 60000,
retries: 3
}, ({ I }) => {
// ...
})
```
Available options:
- `timeout` — maximum time in milliseconds for scenario to complete (see [Timeouts](/timeouts))
- `retries` — number of times to retry the scenario if it fails (see [Retry Mechanisms](/retry))
- `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
Override helper config for a single scenario using `.config()`:
```js
Scenario('run in firefox', ({ I }) => {
// ...
}).config({ browser: 'firefox' })
```
To target a specific helper, pass its name as the first argument:
```js
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:
```js
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`:
```js
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
Use `Data().Scenario` to run the same scenario with multiple datasets:
```js
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](/data) for more details.
Append a tag to the scenario title:
```js
Scenario('update user profile @slow', ...)
```
Or use the `tag()` method:
```js
Scenario('update user profile', ({ I }) => {
// ...
}).tag('@slow').tag('important')
```
Run tagged tests with `--grep`:
```sh
npx codeceptjs run --grep '@slow'
```
Use regex for complex filtering:
```sh
npx codeceptjs run --grep '(?=.*@smoke2)(?=.*@smoke3)'
npx codeceptjs run --grep '@smoke2|@smoke3'
npx codeceptjs run --grep '(?=.*)^(?!.*@smoke4)'
```
```js
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
```
### Todo Scenarios
Mark scenarios as planned but not yet implemented:
```js
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:
```js
Before(({ I }) => {
I.amOnPage('/')
})
After(({ I }) => {
I.clearCookie()
})
```
These are equivalent to `beforeEach` / `afterEach` in Mocha/Jest.
Hooks can be retried on failure:
```js
Before(({ I }) => {
I.amOnPage('/dashboard')
}).retry(3)
```
Run code once before or after **all scenarios** in the file — equivalent to `beforeAll` / `afterAll`:
```js
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:
```js
Feature('My Suite', {
retryBefore: 3,
retryBeforeSuite: 2,
retryAfter: 1,
retryAfterSuite: 3,
})
```