|
| 1 | +--- |
| 2 | +permalink: /architecture |
| 3 | +title: Architecture |
| 4 | +--- |
| 5 | + |
| 6 | +# CodeceptJS Architecture |
| 7 | + |
| 8 | +How CodeceptJS runs a test, and the internal modules you build [plugins, listeners, and helpers](/hooks) against. |
| 9 | + |
| 10 | +## How a Test Runs |
| 11 | + |
| 12 | +CodeceptJS is built on top of [Mocha](https://mochajs.org). A run goes through these stages: |
| 13 | + |
| 14 | +1. **Load.** CodeceptJS reads the config, builds the [container](#container) (helpers, support objects, plugins), and runs the `bootstrap` hook. `event.all.before` fires. |
| 15 | +2. **Suite.** For each suite, `event.suite.before` fires. Helper `_beforeSuite` hooks run. |
| 16 | +3. **Test.** For each test: `event.test.started` fires; `Before` hooks from helpers (`_before`) and from the suite run, then `event.test.before` fires; the scenario function runs; `event.test.passed` or `event.test.failed` fires; `After` hooks run; `event.test.after` and then `event.test.finished` fire. |
| 17 | +4. **Step.** Each `I.*` call inside a scenario becomes a step. It is *scheduled* onto the [recorder](#the-recorder) — `event.step.before` fires — then executed: `event.step.started`, `event.step.passed` or `event.step.failed`, `event.step.after`, `event.step.finished`. |
| 18 | +5. **Finish.** `event.suite.after` fires after each suite, `event.all.after` after the last one, and `event.all.result` when results are printed. The `teardown` hook runs. |
| 19 | + |
| 20 | +The key idea is step 4: **a scenario doesn't execute its steps as it runs** — it queues them. `I.click()` returns immediately; the [recorder](#the-recorder) runs the queued action later. This is why scenarios rarely need `await`, and why anything that injects async work has to go through the recorder. |
| 21 | + |
| 22 | +## The Internal API |
| 23 | + |
| 24 | +CodeceptJS exposes its internals as named exports of the `codeceptjs` package. Import only what you need: |
| 25 | + |
| 26 | +```js |
| 27 | +import { recorder, event, output, container, config } from 'codeceptjs' |
| 28 | +``` |
| 29 | + |
| 30 | +| Export | What it is | |
| 31 | +| --- | --- | |
| 32 | +| [`codecept`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/codecept.js) | the test runner class | |
| 33 | +| [`config`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/config.js) | the loaded configuration | |
| 34 | +| [`container`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/container.js) | dependency-injection container: helpers, support objects, plugins, the Mocha instance | |
| 35 | +| [`recorder`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/recorder.js) | the global promise chain that orders every step | |
| 36 | +| [`event`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/event.js) | the event dispatcher and the names of all lifecycle events | |
| 37 | +| [`output`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/output.js) | the printer used for all console output | |
| 38 | +| [`helper`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/helper.js) | the base class every helper extends | |
| 39 | +| [`actor`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/actor.js) | the base class behind the `I` object | |
| 40 | + |
| 41 | +> Older code relied on a global `codeceptjs` object (`const { recorder } = codeceptjs`). That global only exists under `noGlobals: false`, the deprecated 3.x default — prefer named imports. |
| 42 | +
|
| 43 | +The [API reference](https://github.com/codeceptjs/CodeceptJS/tree/master/docs/api) on GitHub documents these modules; the source is the final word. |
| 44 | + |
| 45 | +## The Recorder |
| 46 | + |
| 47 | +The recorder is a single global promise chain. Every step a scenario "calls" is appended to it, and the chain runs the steps one after another. To run your own async code at the right point in a test, append it to the recorder too: |
| 48 | + |
| 49 | +```js |
| 50 | +import { event, recorder } from 'codeceptjs' |
| 51 | + |
| 52 | +event.dispatcher.on(event.test.before, () => { |
| 53 | + recorder.add('seed fixture data', async () => { |
| 54 | + await api.post('/users', { name: 'john', email: 'john@example.com' }) |
| 55 | + }) |
| 56 | +}) |
| 57 | +``` |
| 58 | + |
| 59 | +- `recorder.add(name, fn)` — append `fn` (async, or returning a promise) to the chain. The name shows up in `--verbose` output. |
| 60 | +- `recorder.startUnlessRunning()` — start a chain if none is running. Call it before `add()` from a listener that may fire outside a running chain, such as `event.all.before`. |
| 61 | +- `recorder.retry({ retries, when })` — retry failing steps that match `when`. See [conditional retries](/helpers#conditional-retries). |
| 62 | + |
| 63 | +Run tests with `--verbose` to watch the recorder schedule and execute each entry. |
| 64 | + |
| 65 | +## Container |
| 66 | + |
| 67 | +The container resolves helpers and support objects by name: |
| 68 | + |
| 69 | +```js |
| 70 | +import { container } from 'codeceptjs' |
| 71 | + |
| 72 | +const helpers = container.helpers() // every helper, keyed by name |
| 73 | +const { Playwright } = container.helpers() // one helper |
| 74 | +const support = container.support() // every support object |
| 75 | +const { UserPage } = container.support() // one page object |
| 76 | +const plugins = container.plugins() // enabled plugins |
| 77 | +const mocha = container.mocha() // the current Mocha instance |
| 78 | +``` |
| 79 | + |
| 80 | +Add objects at runtime — useful from a `bootstrap` script: |
| 81 | + |
| 82 | +```js |
| 83 | +import { container } from 'codeceptjs' |
| 84 | +import UserPage from './pages/user.js' |
| 85 | + |
| 86 | +container.append({ |
| 87 | + helpers: { MyHelper: new MyHelper({ host: 'http://example.com' }) }, |
| 88 | + support: { UserPage }, |
| 89 | +}) |
| 90 | +``` |
| 91 | + |
| 92 | +## Events |
| 93 | + |
| 94 | +`event.dispatcher` is a Node `EventEmitter`. Attach listeners to it from a [plugin](/hooks#plugins) or `bootstrap` script. |
| 95 | + |
| 96 | +Events are **sync** or **async**: |
| 97 | + |
| 98 | +- **sync** — fires the moment the action happens. Do synchronous work only. |
| 99 | +- **async** — fires when the action is *scheduled*. To do async work in the right order, queue it with `recorder.add()`. |
| 100 | + |
| 101 | +| Event | Kind | When | |
| 102 | +| --- | --- | --- | |
| 103 | +| `event.all.before` | — | before any test runs | |
| 104 | +| `event.suite.before(suite)` | async | before a suite | |
| 105 | +| `event.test.started(test)` | sync | at the very start of a test | |
| 106 | +| `event.test.before(test)` | async | after `Before` hooks from helpers and the test are run | |
| 107 | +| `event.test.passed(test)` | sync | test passed | |
| 108 | +| `event.test.failed(test, err)` | sync | test failed | |
| 109 | +| `event.test.skipped(test)` | sync | test skipped | |
| 110 | +| `event.test.after(test)` | async | after each test | |
| 111 | +| `event.test.finished(test)` | sync | test finished | |
| 112 | +| `event.suite.after(suite)` | async | after a suite | |
| 113 | +| `event.step.before(step)` | async | step scheduled for execution | |
| 114 | +| `event.step.started(step)` | sync | step starts executing | |
| 115 | +| `event.step.passed(step)` | sync | step passed | |
| 116 | +| `event.step.failed(step, err)` | sync | step failed | |
| 117 | +| `event.step.after(step)` | async | after a step | |
| 118 | +| `event.step.finished(step)` | sync | step finished | |
| 119 | +| `event.step.comment(step)` | sync | a comment such as `I.say(...)` | |
| 120 | +| `event.bddStep.before(step)` / `event.bddStep.after(step)` | async | around a Gherkin step | |
| 121 | +| `event.hook.started(hook)` / `event.hook.passed` / `event.hook.failed` / `event.hook.finished` | sync | around `Before` / `After` / `BeforeSuite` / `AfterSuite` hooks | |
| 122 | +| `event.all.after` | — | after all tests | |
| 123 | +| `event.all.result(result)` | — | when results are printed | |
| 124 | +| `event.all.failures(failures)` | — | when a run reports failures | |
| 125 | +| `event.workers.before` / `event.workers.after` / `event.workers.result(result)` | — | around a [parallel run](/parallel) (parent process only) | |
| 126 | + |
| 127 | +The [built-in listeners](https://github.com/codeceptjs/CodeceptJS/tree/master/lib/listener) are working examples — every reporter and several plugins are listeners. |
| 128 | + |
| 129 | +### Test object |
| 130 | + |
| 131 | +Test events pass a test object with these fields: |
| 132 | + |
| 133 | +- `title` — the test title |
| 134 | +- `body` — the test function as a string |
| 135 | +- `opts` — test options such as `retries` (see [test options](/advanced#test-options)) |
| 136 | +- `pending` — `true` while scheduled, `false` once finished |
| 137 | +- `tags` — array of [tags](/test-structure#tags) for this test |
| 138 | +- `artifacts` — files attached to this test (screenshots, videos, …), shared across reporters |
| 139 | +- `file` — path to the test file |
| 140 | +- `steps` — executed steps (only on `test.passed`, `test.failed`, `test.finished`) |
| 141 | +- `skipInfo` — present when the test was skipped: `{ message, description }` |
| 142 | + |
| 143 | +### Step object |
| 144 | + |
| 145 | +Step events pass a step object with these fields: |
| 146 | + |
| 147 | +- `name` — the step name, such as `see` or `click` |
| 148 | +- `actor` — the current actor, usually `I` |
| 149 | +- `helper` — the helper instance that executes this step |
| 150 | +- `helperMethod` — the helper method, usually the same as `name` |
| 151 | +- `status` — `passed` or `failed` |
| 152 | +- `prefix` — for a step inside a `within` block, the within text (e.g. `Within .js-signup-form`) |
| 153 | +- `args` — the arguments passed to the step |
| 154 | + |
| 155 | +## Config |
| 156 | + |
| 157 | +```js |
| 158 | +import { config } from 'codeceptjs' |
| 159 | + |
| 160 | +config.get() // the full config object |
| 161 | +config.get('myKey') // one value |
| 162 | +config.get('myKey', 'fallback') // one value, with a default |
| 163 | +``` |
| 164 | + |
| 165 | +## Output |
| 166 | + |
| 167 | +Output has four verbosity levels, each toggled by a CLI flag: |
| 168 | + |
| 169 | +| Level | Flag | Use | |
| 170 | +| --- | --- | --- | |
| 171 | +| default | — | `output.print` — basic information | |
| 172 | +| steps | `--steps` | step execution | |
| 173 | +| debug | `--debug` | steps plus `output.debug` | |
| 174 | +| verbose | `--verbose` | debug plus `output.log` (internal logs and recorder activity) | |
| 175 | + |
| 176 | +```js |
| 177 | +import { output } from 'codeceptjs' |
| 178 | + |
| 179 | +output.print('basic information') |
| 180 | +output.debug('debug information') |
| 181 | +output.log('verbose logging information') |
| 182 | +``` |
| 183 | + |
| 184 | +Use these instead of `console.log` so messages respect the chosen verbosity. |
| 185 | + |
| 186 | +## Helpers and the Actor |
| 187 | + |
| 188 | +The `I` object is an **actor** assembled from the enabled helpers. Each `I.method()` call delegates to the matching helper method and is wrapped as a step. Methods whose names start with `_` are private to the helper and not exposed on `I`. To add your own actions, write a [custom helper](/helpers). |
| 189 | + |
| 190 | +## Running CodeceptJS from Code |
| 191 | + |
| 192 | +CodeceptJS can be driven from your own script. Create the runner with a config and options, initialize it, then bootstrap, load tests, and run: |
| 193 | + |
| 194 | +```js |
| 195 | +import { codecept as Codecept } from 'codeceptjs' |
| 196 | + |
| 197 | +const config = { helpers: { Playwright: { browser: 'chromium', url: 'http://localhost' } } } |
| 198 | +const opts = { steps: true } |
| 199 | + |
| 200 | +const codecept = new Codecept(config, opts) |
| 201 | +codecept.init(import.meta.dirname) // the test root directory |
| 202 | + |
| 203 | +try { |
| 204 | + await codecept.bootstrap() |
| 205 | + codecept.loadTests('**/*_test.js') |
| 206 | + await codecept.run() // pass a test file path to run only that file |
| 207 | +} catch (err) { |
| 208 | + console.error(err) |
| 209 | + process.exitCode = 1 |
| 210 | +} finally { |
| 211 | + await codecept.teardown() |
| 212 | +} |
| 213 | +``` |
| 214 | +
|
| 215 | +> To run tests inside workers from a script, see [parallel execution](/parallel). |
| 216 | +
|
| 217 | +--- |
| 218 | +
|
| 219 | +**See also:** [Extending CodeceptJS](/hooks) · [Custom Helpers](/helpers) · [Plugins](/plugins) · [Bootstrap & Teardown](/bootstrap) |
0 commit comments