codeceptjs 4.0.0-rc.20 → 4.0.0-rc.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/mcp-server.js CHANGED
@@ -60,9 +60,7 @@ function aiTraceHint() {
60
60
  }
61
61
 
62
62
  function applyMochaGrep(grep) {
63
- if (!grep) return
64
- const mocha = typeof container.mocha === 'function' ? container.mocha() : container.mocha
65
- if (mocha && typeof mocha.grep === 'function') mocha.grep(grep)
63
+ if (grep) container.mocha().grep(grep)
66
64
  }
67
65
 
68
66
  function pauseAtMatcher(pauseAt) {
@@ -401,14 +399,16 @@ async function cancelRun() {
401
399
  abortRun = true
402
400
  if (typeof pendingRunCleanup === 'function') { try { pendingRunCleanup() } catch {} }
403
401
  if (pausedController) { try { pausedController.resolveContinue() } catch {} ; pausedController = null }
402
+
403
+ try { container.mocha().runner?.abort() } catch {}
404
+
404
405
  if (pendingRunPromise) {
405
- try { await Promise.race([pendingRunPromise.catch(() => {}), new Promise(r => setTimeout(r, 5000))]) } catch {}
406
+ try { await pendingRunPromise.catch(() => {}) } catch {}
406
407
  }
407
408
  pendingRunPromise = null
408
409
  pendingRunResults = null
409
410
  pendingTestFile = null
410
411
  pendingStepInfo = null
411
- abortRun = false
412
412
  return true
413
413
  }
414
414
 
@@ -1032,6 +1032,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1032
1032
  pendingRunCleanup = null
1033
1033
  }
1034
1034
 
1035
+ abortRun = false
1035
1036
  let runError = null
1036
1037
  const runPromise = (async () => {
1037
1038
  try {
@@ -1126,6 +1127,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1126
1127
  pendingRunCleanup = null
1127
1128
  }
1128
1129
 
1130
+ abortRun = false
1129
1131
  let runError = null
1130
1132
  const runPromise = (async () => {
1131
1133
  try {
package/docs/advanced.md CHANGED
@@ -134,7 +134,7 @@ Feature('My feature', {key: val});
134
134
  Scenario('My scenario', {key: val},({ I }) => {});
135
135
  ```
136
136
 
137
- You can use this options for build your own [plugins](https://codecept.io/hooks/#plugins) with [event listners](https://codecept.io/hooks/#api). Example:
137
+ You can use these options to build your own [plugins](https://codecept.io/hooks#plugins) with [event listeners](https://codecept.io/architecture#events). Example:
138
138
 
139
139
  ```js
140
140
  // for test
package/docs/agents.md CHANGED
@@ -5,6 +5,38 @@ title: Agentic Testing
5
5
 
6
6
  # Agentic Testing
7
7
 
8
+ ## What Makes CodeceptJS Agent-friendly
9
+
10
+ CodeceptJS 4 is designed for agent testing. It ships with its own MCP and official skills that teach agents best practices in test automation—how to write tests, choose locators, test them live in the browser, and refactor to page objects.
11
+
12
+ Agents get full control over test and browser execution:
13
+
14
+ - **Full HTML access and ARIA snapshots.** Agents see buttons with icons, empty labels, and other elements Playwright MCP's accessibility tree omits.
15
+
16
+ - **Query HTML directly with CSS, ARIA, Semantic Locators or XPath** Agents can freely run XPath or CSS to efficiently search over HTML.
17
+
18
+ - **Browser logs and page state as files.** Agents read via native filesystem tools instead of extra requests, eliminating redundant context dumps.
19
+
20
+ - **Same locators for tests and agents.** Tests and agent scripts share identical selectors. No elements by reference indexes, no clicks by coordinates. Agents inherit battle-tested patterns without guessing.
21
+
22
+ - **Testing-first framework, not browser control.** CodeceptJS is built for testing, so agents stay focused on test scenarios instead of raw browser commands.
23
+
24
+ CodeceptJS is token-efficient: it stores HTML, ARIA, logs, and HTTP request data as files instead of streaming them through MCP. Agents read these files with their native shell tools—no extra API calls, no redundant context.
25
+
26
+ ## The loop
27
+
28
+ Whether the agent is writing a new test or fixing an old one, it follows the same cycle.
29
+
30
+ 1. **Open the page.** Run a stub test (new work) or set a breakpoint at the failing step (fix). The browser lands at the right starting point and yields control to the agent.
31
+ 2. **Read the page.** MCP saves HTML, ARIA, and screenshot of the page to files (and the agent can call the `snapshot` tool to refresh them). The agent reads those files before deciding what to try next, controlling its token usage.
32
+ 3. **Run a CodeceptJS command.** The agent tries `I.*` commands like `I.click('Add to cart')`, `I.fillField('Email', secret(process.env.EMAIL))`, `I.see('Confirmed')`. On success, that line goes into the test — same syntax.
33
+ 4. **Check the result.** The response after each command shows the new page state. If the URL changed and the modal opened, the line goes into the verified sequence. If not, the agent reads the page again and tries a different locator or a wait.
34
+ 5. **Move forward.** The agent looks at the new state and chooses the next command. Steps 2–4 repeat until the scenario is whole.
35
+ 6. **Commit to the file.** The agent edits the test — replaces `pause()` (new tests) or the broken line (fixes) with the verified sequence — then reruns end-to-end and reads the trace to confirm.
36
+
37
+
38
+ ## How It Works
39
+
8
40
  CodeceptJS ships an **MCP server and a skillset** that lets an AI agent (Claude Code, Cursor, Codex, others) write and fix tests by driving the real browser. The agent runs the same `I.*` commands the test does, reads how the page responds, and only commits the lines that succeeded.
9
41
 
10
42
  ## Why MCP
@@ -30,16 +62,6 @@ This lets the agent get a test working in one iteration. The agent can live-writ
30
62
 
31
63
  The MCP server is the agent-facing equivalent of the `pause()` REPL — same access, driven by tool calls instead of keystrokes. Full tool reference at [/mcp](/mcp).
32
64
 
33
- ## The loop
34
-
35
- Whether the agent is writing a new test or fixing an old one, it follows the same cycle.
36
-
37
- 1. **Open the page.** Run a stub test (new work) or set a breakpoint at the failing step (fix). The browser lands at the right starting point and yields control to the agent.
38
- 2. **Read the page.** MCP saves HTML, ARIA, and screenshot of the page to files (and the agent can call the `snapshot` tool to refresh them). The agent reads those files before deciding what to try next, controlling its token usage.
39
- 3. **Run a CodeceptJS command.** The agent tries `I.*` commands like `I.click('Add to cart')`, `I.fillField('Email', secret(process.env.EMAIL))`, `I.see('Confirmed')`. On success, that line goes into the test — same syntax.
40
- 4. **Check the result.** The response after each command shows the new page state. If the URL changed and the modal opened, the line goes into the verified sequence. If not, the agent reads the page again and tries a different locator or a wait.
41
- 5. **Move forward.** The agent looks at the new state and chooses the next command. Steps 2–4 repeat until the scenario is whole.
42
- 6. **Commit to the file.** The agent edits the test — replaces `pause()` (new tests) or the broken line (fixes) with the verified sequence — then reruns end-to-end and reads the trace to confirm.
43
65
 
44
66
  ## How the agent reads the page
45
67
 
@@ -0,0 +1,219 @@
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)