{"owner":"codeceptjs","repo":"CodeceptJS","hasSkills":true,"totalSkillsCount":24,"totalTokensCount":17097,"categories":["claude-rule","root-instruction","project-spec","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","docs/agents.md","docs/api.md","docs/architecture.md","docs/plugins/aiTrace.md","docs/plugins/analyze.md","docs/plugins/auth.md","docs/plugins/autoDelay.md","docs/plugins/browser.md","docs/plugins/coverage.md","docs/plugins/customLocator.md","docs/plugins/customReporter.md","docs/plugins/expose.md","docs/plugins/heal.md","docs/plugins/junitReporter.md","docs/plugins/pageInfo.md","docs/plugins/pause.md","docs/plugins/pauseOnFail.md","docs/plugins/retryFailedStep.md","docs/plugins/screencast.md","docs/plugins/screenshot.md","docs/plugins/screenshotOnFail.md","docs/plugins/sitemap.md","docs/plugins/stepTimeout.md"],"skills":{"CLAUDE.md":"# CodeceptJS ESM Migration Plan\n\nThis project is migrating from 3.x to ESM replacing all require() calls with imports.\n\n## Compare with Reference\n\nEach time unsure what to do refer to the same file implementation in **3.x branch**\n3.x branch contains stable version of all core classes that work\n\n## Running Tests\n\nFocus on acceptance tests:\n\nFor instance this helps to understand if final migration is ok:\n\n```\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --debug --grep within\n```\n\nDo not say: it works befire running specific acceptance test.\nTests may stuck so always run them with timeout call:\n\n```\ntimeout=30000 ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n```\n\nWeb Server for this tests are running on port 8000 and it works but responds 500 for HEAD requests.\n\n## Princinples\n\nAll unit tests for Playwright/Puppeteer/WebdriverIO are passing. But acceptance tests are failing due to promises composition. Complexity of promises composition comes from promise chaining and async/await syntax. And `session` mechanism from `promise.js` which allows spawning different promises chains and sync them up in the end.\n\nThe full test suite extensively uses plugins so refer to plugins and corresponding helpers as well\n\nWe are building test framework. So even passing ests are not indicator of successful usage. Side effects like: timeouts, bad output, errors, memory leaks, etc are affecting performance.\nFor instance, if a tests stuck and won't finish this is very bad.\n\n## Coding Style\n\n- Never add comments unless explicitly required.\n- DO not mention plugins or helpers inside core classes\n","docs/agents.md":"---\npermalink: /agents\ntitle: Agentic Testing\n---\n\n# Agentic Testing\n\n## What Makes CodeceptJS Agent-friendly\n\nCodeceptJS 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.\n\nAgents get full control over test and browser execution: \n\n- **Full HTML access and ARIA snapshots.** Agents see buttons with icons, empty labels, and other elements Playwright MCP's accessibility tree omits.\n\n- **Query HTML directly with CSS, ARIA, Semantic Locators or XPath** Agents can freely run XPath or CSS to efficiently search over HTML.\n\n- **Browser logs and page state as files.** Agents read via native filesystem tools instead of extra requests, eliminating redundant context dumps.\n\n- **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.\n\n- **Testing-first framework, not browser control.** CodeceptJS is built for testing, so agents stay focused on test scenarios instead of raw browser commands.\n\nCodeceptJS 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.\n\n## Essential Setup\n\nTwo things make agent testing work: the **skills** that teach the agent CodeceptJS, and the **MCP server** that lets it drive the browser. Set both up once, from your project directory:\n\n```bash\nnpx codeceptjs init:agent claude\n```\n\nUse `codex`, `cursor`, or `opencode` instead of `claude` for other agents, or omit the name to pick from a list. `npx codeceptjs init` offers the same step at the end.\n\n### Manual setup\n\nInstall the skills:\n\n```bash\nnpx skills add codeceptjs/skills\n```\n\nConnect the MCP server (`npx codeceptjs-mcp`).\n\n**Claude Code:**\n\n```bash\nclaude mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Codex:**\n\n```bash\ncodex mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Cursor** — add to `.cursor/mcp.json`:\n\n```json\n{ \"mcpServers\": { \"codeceptjs\": { \"command\": \"npx\", \"args\": [\"codeceptjs-mcp\"] } } }\n```\n\nSee [/mcp](/mcp) for full client setup. Now the agent is ready to run the loop.\n\n## The loop\n\nWhether the agent is writing a new test or fixing an old one, it follows the same cycle.\n\n1. **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.\n2. **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.\n3. **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.\n4. **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.\n5. **Move forward.** The agent looks at the new state and chooses the next command. Steps 2–4 repeat until the scenario is whole.\n6. **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.\n\n\n## How It Works\n\nCodeceptJS 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.\n\n## Why MCP\n\nThe traditional agent testing loop is test/fix/retry, where the agent executes a test, watches it fail, reads artifacts, performs code fixes, and reruns the test. The agent applies fixes by intelligent guess — looking at the ARIA tree, HTML, and screenshot — then assumes the fix is enough and reruns the test hoping it will pass. If the guess is wrong and the test runs for over a minute, it may take dozens of minutes of iteration and a lot of wasted tokens.\n\nTo improve that flow, the agent can spawn a browser and open the page the way the test does. This lets it interact with the page more freely and perform multi-step actions. But putting that experience back into test code is not efficient either: actions executed in the browser may not be relevant in test context, so the agent ends up in another guess-and-try loop.\n\nThe problem is that **the test runs in a different context than the agent**.\n\nThe agent can launch a test but can't control it while it's running. It can't access the browser. It can't set a breakpoint.\n\nThis is where CodeceptJS MCP steps in. Connected to the agent, it can:\n\n- run a test and pause it on failure\n- interact with the browser in a test context\n- test locators and perform actions live while the test is running\n- write successful actions to the test file\n\nThis lets the agent get a test working in one iteration. The agent can live-write the test before your eyes by exploring the page and performing actions that eventually land in the CodeceptJS test file.\n\n**Live debugging of tests** is what CodeceptJS MCP provides. The agent receives feedback faster — not from a whole test execution but from specific actions on a specific page — so it can adjust and react faster, trying different approaches.\n\nThe 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).\n\n\n## How the agent reads the page\n\nMCP commands are token efficient — they don't stream large HTML pages back to the model. MCP writes artifacts to disk under `output/trace_*/` and returns file paths. The agent reads each artifact with its own bash tools — `cat`, `grep`, `jq`.\n\nA `run_code` response, for example, looks like this:\n\n```json\n{\n  \"status\": \"success\",\n  \"artifacts\": {\n    \"url\": \"http://localhost:8000/\",\n    \"html\": \"file:///output/trace_run_code_.../mcp_page.html\",\n    \"aria\": \"file:///output/trace_run_code_.../mcp_aria.txt\",\n    \"screenshot\": \"file:///output/trace_run_code_.../mcp_screenshot.png\",\n    \"console\": \"file:///output/trace_run_code_.../mcp_console.json\",\n    \"storage\": \"file:///output/trace_run_code_.../mcp_storage.json\"\n  }\n}\n```\n\nOnly `url` is inline. The rest are paths the agent opens with the right tool:\n\n| Artifact | How the agent reads it |\n|----------|------------------------|\n| `*_screenshot.png` | As an image — most agents are multimodal |\n| `*_aria.txt` | Whole — small and structured |\n| `*_page.html` | With `grep` — too large for context, searchable for specific elements/attributes |\n| `*_console.json` | With `jq` — filter for errors, 4xx/5xx, deprecation warnings |\n| `*_storage.json` | Whole — cookies and `localStorage` snapshot |\n| `trace.md` | Whole — markdown index linking every step to its artifacts |\n\nSaved HTML is formatted, with non-semantic elements stripped out: `<style>`, `<script>`, Tailwind-style trash classes, and inline `style=\"\"` attributes. `grep` can then effectively find the correct tree branch in raw page source. ARIA snapshots are smaller and more structured than HTML, which is why the agent prefers them when picking locators.\n\n## Usage Examples\n\nWhen MCP and skills are connected, the agent receives predefined workflows and can act effectively for testing purposes. Common scenarios it handles:\n\n### Writing a new test\n\nYou ask: \"Add a test for the checkout flow.\"\n\nThe agent writes a stub:\n\n```javascript\nScenario('checkout', ({ I }) => {\n  I.amOnPage('/cart')\n  pause()\n})\n```\n\nIt runs the stub. The browser opens at `/cart` and yields control at `pause()`. The agent reads the ARIA tree, runs `I.click('Add to cart')`, sees the cart total update — that line goes into the verified sequence. It runs `I.fillField('Email', '...')`, sees the field accept the value, records it. Through `I.click('Continue to payment')`, `I.see('Payment')`, `I.fillField('Card', secret(process.env.TEST_CARD))`, `I.click('Pay')`, `I.see('Order confirmed')` — each command commits only after the response confirms it worked.\n\nWhen the scenario is whole, the agent edits the test file: replaces `pause()` with the verified sequence, renames the scenario, wraps credentials with `secret()`. It reruns the file end-to-end with `aiTrace` on and hands you the diff.\n\n### Fixing a failing test\n\nA test fails. You point the agent at the scenario.\n\nIt opens `output/trace_<TestName>_*/trace.md` from the last run, reads the steps, and finds the one marked failed. Most of the time the screenshot and ARIA from that step explain the cause — \"Save\" is now \"Save changes,\" or a spinner is gating the next action. The agent patches the line and reruns.\n\nWhen the trace doesn't say enough, the agent passes a step number to `run_test` so the test pauses right before the failing step. From the live page, it tries `I.click({ role: 'button', name: 'Save changes' })`, sees the modal close. Or `I.waitForInvisible('.spinner', 10)` followed by the original click — watches it pass. Whatever holds goes into the test.\n\nThe fix lands with a one-line note explaining what changed.\n\n### Auto-fixing on CI\n\nAfter a failed run, the agent reads every trace under `output/`, clusters failures by signature, and patches what fits a small set of safe fixes (locator drift, missing waits, raw `I.wait(N)` replacement). It reruns only the failing scenarios, compares against the baseline, and writes a markdown report at `output/ci-fix.md`.\n\nIf the fix held, the PR goes green. If it didn't, every edit is rolled back with `git checkout` and the report says which patterns the agent couldn't safely handle. No half-applied fixes left behind, no `retries: 3` masking the problem.\n\n## Assertions in plain language\n\nSome outcomes are hard to pin to a locator: \"checkout form has all required fields\", \"success message is shown\". Instead of building a fragile chain of `see*` checks, the agent can write the expected outcome as a statement with the [Decision helper](/assertions#decision-assertions):\n\n```js\nI.decide([\n  'order summary lists the purchased items',\n  'success message is shown',\n])\n```\n\nThe agent runs the statement on the live page like any other command and keeps it only if it passes. The test then checks it on every run.\n\nTo let the agent use decisions, [configure a decision model](/ai#configure-decision-models) and enable the Decision helper.\n\nDecision models like [Jev](https://openrouter.ai/typesafe/jev-1.13) are built for this. They answer in a fraction of a second, cost a fraction of a cent per request, and return a probability instead of free text. That makes them fast and cheap enough to run on every CI build, and predictable enough to keep in a test.\n\n## Skills bundle\n\nSkills teach the agent best practices for using CodeceptJS. Plug them in when you develop tests with agents, and update them regularly to ensure you use CodeceptJS in the most effective way.\n\n| Skill | Use case |\n|-------|----------|\n| `writing-codeceptjs-tests` | Author or extend a scenario. Runs the loop above with a stub-and-pause flow for greenfield work, incremental `run_code` for known flows. |\n| `debugging-codeceptjs-tests` | A test is failing or flaky. Reads the trace, decides whether to patch from the trace alone or set a breakpoint on the live page. |\n| `ci-fix-tests` | Conservative auto-repair on CI |\n| `refactoring-codeceptjs-tests` | Extract page objects, tame long locators, move raw browser code into helpers. Proposes in batches. |\n| `codeceptjs-fundamentals` | Obtain actual CodeceptJS knowledge. |\n| `codeceptjs-exploration` | Pick a stable locator from messy markup. |\n| `codeceptjs-run-analysis` | Read trace artifacts, cluster CI failures into root causes, verify a fix held across many traces. |\n| `codeceptjs-auth` | Authorize efficiently with `auth` plugin. |\n\n## Pointers\n\n- [/mcp](/mcp) — full MCP tool reference, client setup\n- [/aitrace](/aitrace) — trace plugin configuration and capture options\n- [/debugging](/debugging) — pause modes, IDE setup, the `pause` plugin\n- [skills repo](https://github.com/codeceptjs/skills) — source and install for non-Claude clients\n","docs/api.md":"---\npermalink: /api\ntitle: API Testing\n---\n\n## API Testing\n\nCodeceptJS provides a way to write tests in declarative manner for REST and GraphQL APIs. \n\nTake a look:\n\n```js\nI.sendGetRequest('/users/1');\n// returns { \"user\": { \"name\": \"jon\" }, \"projects\": [] }\nI.seeResponseCodeIsSuccessful();\nI.seeResponseContainsKeys(['user', 'projects']);\nI.seeResponseContainsJson({ user: { name: 'jon' } });\nI.seeResponseMatchesJsonSchema($ => {\n  return $.object(\n    user: $.object({\n      name: $.string(),\n    }),\n    projects: $.array()\n  )\n});\n```\nIn this code we checked API request for:\n\n* status code\n* data inclusion\n* data structure\n\nThese are the things you should generally test your APIs for.\n\n> 🤓 It is recommended to check only invariable parts of responses. Check for required fields and only values you control. For instance, it is not recommended to check id fields, date fields, as they can be frequently changed.\n\n## Installation\n\nInstall CodeceptJS if it is not installed yet.\n\n```\nnpm i codeceptjs --save-dev\n```\n\nInitialize CodeceptJS and select REST or GraphQL helper when asked for a helper:\n\n```\nnpx codeceptjs init\n```\n\n## Configuration\n\nEnsure that inside `codecept.conf.js` in helpers section `REST` or `GraphQL` helpers are enabled.\n\n* If you use `REST` helper add `JSONResponse` helper below with no extra config:\n\n```js\n// inside codecept.conf.js\n// ...\n  helpers: {\n    REST: {\n      endpoint: 'http://localhost:3000/api'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {}\n  }\n```\n* If you use `GraphQL` helper add `JSONResponse` helper, configuring it to use GraphQL for requests:\n\n```js\n  helpers: {\n    GraphQL: {\n      endpoint: 'http://localhost:3000/graphql'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {\n      requestHelper: 'GraphQL',\n    }\n  }\n```\n\nOriginally, REST and GraphQL helpers were not designed for API testing. \nThey were used to perform API requests for browser tests. As so, they lack assertion methods to API responses.\n\n[`JSONResponse`](/helpers/json-response/) helper adds response assertions. \n\n> 💡 In CodeceptJS assertions start with `see` prefix. Learn more about assertions by [opening reference for JSONResponse](/helpers/json-response/) helper.\n\nGenerate TypeScript definitions to get auto-completions for JSONResponse:\n\n```\nnpx codeceptjs def\n```\n\nAfter helpers were configured and typings were generated, you can start writing first API test. By default, CodeceptJS saves tests in `tests` directory and uses `*_test.js` suffix. The `init` command created the first test for you to start.\n\n> Check [API Examples](https://github.com/codeceptjs/api-examples) to see tests implementations.\n\n## Requests\n\n[REST](/helpers/rest/) or [GraphQL](/helpers/graph-ql/) helpers implement methods for making API requests.\nBoth helpers send requests via HTTP protocol from CodeceptJS process. \nFor most cases, you will need to have authentication. It can be passed via headers, which can be added to helper's configuration in `codecept.conf.js`. \n\n```js\nhelpers: {\n  REST: {\n    defaultHeaders: {\n      // use Bearer Authorization\n      'Authorization': 'Bearer 11111',\n      'Content-Type': 'application/json',\n      'Accept': 'application/json',\n    },\n  }\n}\n```\n\nOr you can use the browser cookies if you are running browser session.\nIn this case use `setSharedCookies()` from `@codeceptjs/configure` package:\n\n```js\nimport { setSharedCookies } from '@codeceptjs/configure'\n\n// call before exporting config\nsetSharedCookies()\n\nexport const config = {\n  // ...\n  helpers: {  \n    // also works with Playwright or Puppeteer\n    WebDriver: {\n      //... \n    },\n\n    REST: { \n      // ...\n    }\n  }\n}\n```\n\n### REST\n\nREST helper can send GET/POST/PATCH/etc requests to REST API endpoint:\n\n* [`I.sendGetRequest()`](/helpers/rest#sendgetrequest)\n* [`I.sendPostRequest()`](/helpers/rest#sendpostrequest)\n* [`I.sendPutRequest()`](/helpers/rest#sendputrequest)\n* [`I.sendPatchRequest()`](/helpers/rest#sendpatchrequest)\n* [`I.sendDeleteRequest()`](/helpers/rest#senddeleterequest)\n* [`I.sendDeleteRequestWithPayload()`](/helpers/rest#senddeleterequestwithpayload)\n* ...\n\nAuthentication headers can be set in [helper's config](https://codecept.io/helpers/rest/#configuration) or per test with headers or special methods like `I.amBearerAuthenticated`.\n\nExample:\n\n```js\nFeature('Users endpoint')\n\nScenario('create user', ({ I }) => {\n    // this way we pass Bearer token\n  I.amBearerAuthenticated(secret('token-is-here'));\n  // for custom authorization with headers use\n  // I.haveRequestHeaders method\n\n  // here we send a POST request\n  const response = await I.sendPostRequest('/users', {\n    name: 'joe',\n    email: 'joe@mail.com'\n  });\n  // usually we won't need direct access to response object for API testing \n  // but you can obtain it from request\n\n  // check the last request was successful\n  // this method introduced by JSONResponse helper\n  I.seeResponseCodeIsSuccessful();\n})\n```\n\n### GraphQL\n\nGraphQL have request format different then in REST API, but the response format is the same.\nIt's plain old JSON. This why `JSONResponse` helper works for both API types.\nConfigure authorization headers in `codecept.conf.js` and make your first query:  \n\n```js\nFeature('Users endpoint')\n\nScenario('get user by query', ({ I }) => {\n  // make GraphQL query or mutation\n  const resp = await I.sendQuery('{ user(id: 0) { id name email }}');\n  I.seeResponseCodeIsSuccessful();\n\n  // GraphQL always returns key data as part of response\n  I.seeResponseContainsKeys(['data']);\n\n  // check data for partial inclusion\n  I.seeResponseContainsJson({\n    data: {\n      user: {\n        name: 'john doe',\n        email: 'johnd@mutex.com',\n      },\n    },\n  });\n});\n```\n\nGraphQL helper has two methods available:\n\n* [`I.sendQuery()`](/helpers/graph-ql#sendquery)\n* [`I.sendMutation()`](/helpers/graph-ql#sendmutation)\n\n## Assertions\n\n`JSONResponse` provides set of assertions for responses in JSON format. These assertions were designed to check only invariable parts of responses. So instead of checking that response equals to the one provided, we will check for data inclusion and structure matching.\n\nFor most of cases, you won't need to perform assertions by accessing `response` object directly. All assretions are performed under hood inside `JSONResponse` module. It is recommended to keep it that way, to keep tests readable and make test log to contain all assertions.\n\n```js\nScenario('I make API call', ({ I }) => {\n  // request was made by REST \n  // or by GraphQL helper\n\n  // check that response code is 2xx\n  I.seeResponseCodeIsSuccessful();\n\n  // check that response contains keys\n  I.seeResponseContainsKeys(['data', 'pages', 'meta']);\n});\n```\n\n### Response Status Codes\n\n[Response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) can be checked to be equal to some value or to be in a specific range. \nTo check that response code is `200` call `I.seeResponseCodeIs`:\n\n```js\nI.seeResponseCodeIs(200);\n```\nBut because other response codes in 2xx range are also valid responses, you can use `seeResponseCodeIsSuccessful()` which will match 200 (OK), 201 (Created), 206 (Partial Content) and others. Methods to check 3xx, 4xx, 5xx response statuses also available.\n\n```js\n// matches 200, 201, 202, ... 206\nI.seeResponseCodeIsSuccessful();\n\n// matches 300...308\nI.seeResponseCodeIsRedirection();\n\n// matches 400..451\nI.seeResponseCodeIsClientError();\n\n// matches 500-511\nI.seeResponseCodeIsServerError();\n```\n\n### Structure\n\nThe most basic thing to check in response is existence of keys in JSON object. Use [`I.seeResponseContainsKeys()`](/helpers/json-response#seeresponsecontainskeys) method for it:\n\n```js\n// response is { \"name\": \"joe\", \"email\": \"joe@joe.com\" }\nI.seeResponseContainsKeys(['name', 'email']);\n```\n\n> ℹ️ If response is an array, it will check that every element in array have provided keys\n\nHowever, this is a very naive approach. It won't work for arrays or nested objects.\nTo check complex JSON structures `JSONResponse` helper uses [`Zod`](https://zod.dev) library.\nIt has rich API to validate JSON by the schema defined using JavaScript.\n\n```js\n// import zod library,\n// it is installed with CodeceptJS\nimport { z } from 'zod';\n\n// create schema definition using Zod API\nconst schema = z.object({\n  email: z.string().email(),\n  phone: z.string().regex(/^\\d{3}-\\d{3}-\\d{4}$/),\n  birthday: z.string().datetime().max(new Date('2004-01-01'))\n});\n\n// check that response matches that schema\nI.seeResponseMatchesJsonSchema(schema);\n```\n\n> 📋 **Migration Note**: CodeceptJS has migrated from Joi to Zod v4 for JSON schema validation.\n> If you have existing tests using Joi, please update them:\n> * Replace `const Joi = require('joi')` with `import { z } from 'zod'`\n> * Replace `Joi.object().keys({...})` with `z.object({...})`\n> * Replace `Joi.string().email()` with `z.string().email()`\n> * Replace `Joi.date()` with appropriate `z.string()` or `z.date()` types\n> * See [Zod documentation](https://zod.dev) for complete API reference\n\n### Data Inclusion\n\nTo check that response contains expected data use `I.seeResponseContainsJson` method. \nIt will check the response data for partial match. \n\n```js\nI.seeResponseContainsJson({\n  user: {\n    email: 'user@user.com'\n  }\n})\n```\n\n> ℹ️ If response is an array, it will check that at least one element in array matches JSON\n\nTo perform arbitrary assertions on a response object use `seeResponseValidByCallback`. \nIt allows you to do any kind of assertions by using `expect` from [`chai`](https://www.chaijs.com) library.\n\n```js\nI.seeResponseValidByCallback(({ data, status, expect }) => {\n  // we receive data and expect to combine them for good assertion\n  expect(data.users.length).to.be.gte(10);\n})\n```\n\n## Extending JSONResponse\n\nTo add more assertions it is recommended to create a custom helper.\nInside it you can get access to latest JSON response:\n\n```js\n// inside a custom helper\nmakeSomeCustomAssertion() {\n  const response = this.helpers.JSONResponse.response;\n}\n```","docs/architecture.md":"---\npermalink: /architecture\ntitle: Architecture\n---\n\n# CodeceptJS Architecture\n\nHow CodeceptJS runs a test, and the internal modules you build [plugins, listeners, and helpers](/hooks) against.\n\n## How a Test Runs\n\nCodeceptJS is built on top of [Mocha](https://mochajs.org). A run goes through these stages:\n\n1. **Load.** CodeceptJS reads the config, builds the [container](#container) (helpers, support objects, plugins), and runs the `bootstrap` hook. `event.all.before` fires.\n2. **Suite.** For each suite, `event.suite.before` fires. Helper `_beforeSuite` hooks run.\n3. **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.\n4. **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`.\n5. **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.\n\nThe 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.\n\n## The Internal API\n\nCodeceptJS exposes its internals as named exports of the `codeceptjs` package. Import only what you need:\n\n```js\nimport { recorder, event, output, container, config } from 'codeceptjs'\n```\n\n| Export | What it is |\n| --- | --- |\n| [`codecept`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/codecept.js) | the test runner class |\n| [`config`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/config.js) | the loaded configuration |\n| [`container`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/container.js) | dependency-injection container: helpers, support objects, plugins, the Mocha instance |\n| [`recorder`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/recorder.js) | the global promise chain that orders every step |\n| [`event`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/event.js) | the event dispatcher and the names of all lifecycle events |\n| [`output`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/output.js) | the printer used for all console output |\n| [`store`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/store.js) | global state of the run — current test/step, run modes, directories |\n| [`helper`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/helper.js) | the base class every helper extends |\n| [`actor`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/actor.js) | the base class behind the `I` object |\n\n> 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.\n\nThe [API reference](https://github.com/codeceptjs/CodeceptJS/tree/master/docs/api) on GitHub documents these modules; the source is the final word.\n\n## The Recorder\n\nThe 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:\n\n```js\nimport { event, recorder } from 'codeceptjs'\n\nevent.dispatcher.on(event.test.before, () => {\n  recorder.add('seed fixture data', async () => {\n    await api.post('/users', { name: 'john', email: 'john@example.com' })\n  })\n})\n```\n\n- `recorder.add(name, fn)` — append `fn` (async, or returning a promise) to the chain. The name shows up in `--verbose` output.\n- `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`.\n- `recorder.retry({ retries, when })` — retry failing steps that match `when`. See [conditional retries](/helpers#conditional-retries).\n\nRun tests with `--verbose` to watch the recorder schedule and execute each entry.\n\n## Container\n\nThe container resolves helpers and support objects by name:\n\n```js\nimport { container } from 'codeceptjs'\n\nconst helpers = container.helpers()          // every helper, keyed by name\nconst { Playwright } = container.helpers()   // one helper\nconst support = container.support()          // every support object\nconst { UserPage } = container.support()     // one page object\nconst plugins = container.plugins()          // enabled plugins\nconst mocha = container.mocha()              // the current Mocha instance\n```\n\nAdd objects at runtime — useful from a `bootstrap` script:\n\n```js\nimport { container } from 'codeceptjs'\nimport UserPage from './pages/user.js'\n\ncontainer.append({\n  helpers: { MyHelper: new MyHelper({ host: 'http://example.com' }) },\n  support: { UserPage },\n})\n```\n\n## Events\n\n`event.dispatcher` is a Node `EventEmitter`. Attach listeners to it from a [plugin](/hooks#plugins) or `bootstrap` script.\n\nEvents are **sync** or **async**:\n\n- **sync** — fires the moment the action happens. Do synchronous work only.\n- **async** — fires when the action is *scheduled*. To do async work in the right order, queue it with `recorder.add()`.\n\n| Event | Kind | When |\n| --- | --- | --- |\n| `event.all.before` | — | before any test runs |\n| `event.suite.before(suite)` | async | before a suite |\n| `event.test.started(test)` | sync | at the very start of a test |\n| `event.test.before(test)` | async | after `Before` hooks from helpers and the test are run |\n| `event.test.passed(test)` | sync | test passed |\n| `event.test.failed(test, err)` | sync | test failed |\n| `event.test.skipped(test)` | sync | test skipped |\n| `event.test.after(test)` | async | after each test |\n| `event.test.finished(test)` | sync | test finished |\n| `event.suite.after(suite)` | async | after a suite |\n| `event.step.before(step)` | async | step scheduled for execution |\n| `event.step.started(step)` | sync | step starts executing |\n| `event.step.passed(step)` | sync | step passed |\n| `event.step.failed(step, err)` | sync | step failed |\n| `event.step.after(step)` | async | after a step |\n| `event.step.finished(step)` | sync | step finished |\n| `event.step.comment(step)` | sync | a comment such as `I.say(...)` |\n| `event.bddStep.before(step)` / `event.bddStep.after(step)` | async | around a Gherkin step |\n| `event.hook.started(hook)` / `event.hook.passed` / `event.hook.failed` / `event.hook.finished` | sync | around `Before` / `After` / `BeforeSuite` / `AfterSuite` hooks |\n| `event.all.after` | — | after all tests |\n| `event.all.result(result)` | — | when results are printed |\n| `event.all.failures(failures)` | — | when a run reports failures |\n| `event.workers.before` / `event.workers.after` / `event.workers.result(result)` | — | around a [parallel run](/parallel) (parent process only) |\n\nThe [built-in listeners](https://github.com/codeceptjs/CodeceptJS/tree/master/lib/listener) are working examples — every reporter and several plugins are listeners.\n\n### Test object\n\nTest events pass a test object with these fields:\n\n- `title` — the test title\n- `body` — the test function as a string\n- `opts` — test options such as `retries` (see [test options](/advanced#test-options))\n- `pending` — `true` while scheduled, `false` once finished\n- `tags` — array of [tags](/test-structure#tags) for this test\n- `artifacts` — files attached to this test (screenshots, videos, …), shared across reporters\n- `file` — path to the test file\n- `steps` — executed steps (only on `test.passed`, `test.failed`, `test.finished`)\n- `skipInfo` — present when the test was skipped: `{ message, description }`\n\n### Step object\n\nStep events pass a step object with these fields:\n\n- `name` — the step name, such as `see` or `click`\n- `actor` — the current actor, usually `I`\n- `helper` — the helper instance that executes this step\n- `helperMethod` — the helper method, usually the same as `name`\n- `status` — `passed` or `failed`\n- `prefix` — for a step inside a `within` block, the within text (e.g. `Within .js-signup-form`)\n- `args` — the arguments passed to the step\n\n## Config\n\n```js\nimport { config } from 'codeceptjs'\n\nconfig.get()                       // the full config object\nconfig.get('myKey')                // one value\nconfig.get('myKey', 'fallback')    // one value, with a default\n```\n\n## Output\n\nOutput has four verbosity levels, each toggled by a CLI flag:\n\n| Level | Flag | Use |\n| --- | --- | --- |\n| default | — | `output.print` — basic information |\n| steps | `--steps` | step execution |\n| debug | `--debug` | steps plus `output.debug` |\n| verbose | `--verbose` | debug plus `output.log` (internal logs and recorder activity) |\n\n```js\nimport { output } from 'codeceptjs'\n\noutput.print('basic information')\noutput.debug('debug information')\noutput.log('verbose logging information')\n```\n\nUse these instead of `console.log` so messages respect the chosen verbosity.\n\n## Store\n\n`store` holds the state of the current run — the executing test, suite, and step, the active run modes (`dryRun`, `debugMode`, `workerMode`, …), and the project directories. Listeners, plugins, and helpers read it to know where in the [lifecycle](#events) they are without that information being passed to them:\n\n```js\nimport { store } from 'codeceptjs'\n\nevent.dispatcher.on(event.step.before, () => {\n  if (store.dryRun) return                    // no side effects on a dry run\n  output.debug(`in ${store.currentTest?.title}`)\n})\n```\n\nCodeceptJS keeps the state fields up to date for you. See the [Store reference](/store) for every field and when to write to it.\n\n## Helpers and the Actor\n\nThe `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).\n\n## Running CodeceptJS from Code\n\nCodeceptJS can be driven from your own script. Create the runner with a config and options, initialize it, then bootstrap, load tests, and run:\n\n```js\nimport { codecept as Codecept } from 'codeceptjs'\n\nconst config = { helpers: { Playwright: { browser: 'chromium', url: 'http://localhost' } } }\nconst opts = { steps: true }\n\nconst codecept = new Codecept(config, opts)\ncodecept.init(import.meta.dirname)   // the test root directory\n\ntry {\n  await codecept.bootstrap()\n  codecept.loadTests('**/*_test.js')\n  await codecept.run()               // pass a test file path to run only that file\n} catch (err) {\n  console.error(err)\n  process.exitCode = 1\n} finally {\n  await codecept.teardown()\n}\n```\n\n> To run tests inside workers from a script, see [parallel execution](/parallel).\n\n---\n\n**See also:** [Extending CodeceptJS](/hooks) · [Custom Helpers](/helpers) · [Plugins](/plugins) · [Bootstrap & Teardown](/bootstrap)\n","docs/plugins/aiTrace.md":"---\npermalink: /plugins/ai-trace\neditLink: false\nsidebar: auto\ntitle: aiTrace\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## aiTrace\n\nGenerates AI-friendly trace files for debugging with AI agents.\nThis plugin creates a markdown file with test execution logs and links to all artifacts\n(screenshots, HTML, ARIA snapshots, browser logs, HTTP requests) for each step.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"aiTrace\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `deleteSuccessful`: delete traces for successfully executed tests. Default: false.\n*   `fullPageScreenshots`: should full page screenshots be used. Default: false.\n*   `output`: a directory where traces should be stored. Default: `output`.\n*   `captureHTML`: capture HTML for each step. Default: true.\n*   `captureARIA`: capture ARIA snapshot for each step. Default: true.\n*   `captureBrowserLogs`: capture browser console logs. Default: true.\n*   `captureHTTP`: capture HTTP requests (requires `trace` or `recordHar` enabled in helper config). Default: true.\n*   `captureDebugOutput`: capture CodeceptJS debug output. Default: true.\n*   `ignoreSteps`: steps to ignore in trace. Array of RegExps is expected.\n*   `on`: trigger mode — `step` (default), `fail`, `test`, `file`, `url`.\n\n#### `on=` modes\n\n*   **step** — persist every step (default)\n*   **fail** — persist only the failed step\n*   **test** — persist only the last step of each test\n*   **file** — persist steps from `path=...[;line=...]`\n*   **url** — persist when the current URL matches `pattern=...`\n\n### Parameters\n\n*   `config` **any**  \n","docs/plugins/analyze.md":"---\npermalink: /plugins/analyze\neditLink: false\nsidebar: auto\ntitle: analyze\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## analyze\n\nUses AI to analyze test failures and provide insights\n\nThis plugin analyzes failed tests using AI to provide detailed explanations and group similar failures.\nWhen enabled with --ai flag, it generates reports after test execution.\n\n#### Usage\n\n```js\n// in codecept.conf.js\nexports.config = {\n  plugins: {\n    analyze: {\n      enabled: true,\n      clusterize: 5,\n      analyze: 2,\n      vision: false\n    }\n  }\n}\n```\n\n#### Configuration\n\n*   `clusterize` (number) - minimum number of failures to trigger clustering analysis. Default: 5\n*   `analyze` (number) - maximum number of individual test failures to analyze in detail. Default: 2\n*   `vision` (boolean) - enables visual analysis of test screenshots. Default: false\n*   `categories` (array) - list of failure categories for classification. Defaults to:\n    *   Browser connection error / browser crash\n    *   Network errors (server error, timeout, etc)\n    *   HTML / page elements (not found, not visible, etc)\n    *   Navigation errors (404, etc)\n    *   Code errors (syntax error, JS errors, etc)\n    *   Library & framework errors\n    *   Data errors (password incorrect, invalid format, etc)\n    *   Assertion failures\n    *   Other errors\n*   `prompts` (object) - customize AI prompts for analysis\n    *   `clusterize` - prompt for clustering analysis\n    *   `analyze` - prompt for individual test analysis\n\n#### Features\n\n*   Groups similar failures when number of failures >= clusterize value\n*   Provides detailed analysis of individual failures\n*   Analyzes screenshots if vision=true and screenshots are available\n*   Classifies failures into predefined categories\n*   Suggests possible causes and solutions\n\n### Parameters\n\n*   `config` **[Object][1]** Plugin configuration \n\nReturns **void**&#x20;\n\n[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n","docs/plugins/auth.md":"---\npermalink: /plugins/auth\neditLink: false\nsidebar: auto\ntitle: auth\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## auth\n\nLogs user in for the first test and reuses session for next tests.\nWorks by saving cookies into memory or file.\nIf a session expires automatically logs in again.\n\n> For better development experience cookies can be saved into file, so a session can be reused while writing tests.\n\n#### Usage\n\n1.  Enable this plugin and configure as described below\n2.  Define user session names (example: `user`, `editor`, `admin`, etc).\n3.  Define how users are logged in and how to check that user is logged in\n4.  Use `login` object inside your tests to log in:\n\n```js\n// inside a test file\n// use login to inject auto-login function\nFeature('Login');\n\nBefore(({ login }) => {\n   login('user'); // login using user session\n});\n\n// Alternatively log in for one scenario.\nScenario('log me in', ( { I, login } ) => {\n   login('admin');\n   I.see('I am logged in');\n});\n```\n\n#### Configuration\n\n*   `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.\n*   `inject` (default: `login`) - name of the login function to use\n*   `users` - an array containing different session names and functions to:\n    *   `login` - sign in into the system\n    *   `check` - check that user is logged in\n    *   `fetch` - to get current cookies (by default `I.grabCookie()`)\n    *   `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)\n\n#### How It Works\n\n1.  `restore` method is executed. It should open a page and set credentials.\n2.  `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged-in user. When you pass the second args `session`, you could perform the validation using passed session.\n3.  If `restore` and `check` were not successful, `login` is executed\n4.  `login` should fill in login form\n5.  After successful login, `fetch` is executed to save cookies into memory or file.\n\n#### Example: Simple login\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      // loginAdmin function is defined in `steps_file.js`\n      login: (I) => I.loginAdmin(),\n      // if we see `Admin` on page, we assume we are logged in\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      }\n    }\n  }\n}\n```\n\n#### Example: Multiple users\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'loginAs', // use `loginAs` instead of login\n  users: {\n    user: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'user@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('User', '.navbar');\n      },\n    },\n    admin: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'admin@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin', '.navbar');\n      },\n    },\n  }\n}\n```\n\n#### Example: Keep cookies between tests\n\nIf you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.\nBut you need to login once work until session expires.\nFor this case, disable `fetch` and `restore` methods.\n\n```js\nhelpers: {\n   WebDriver: {\n     // config goes here\n     keepCookies: true; // keep cookies for all tests\n   }\n},\nplugins: {\n   auth: {\n     users: {\n       admin: {\n         login: (I) => {\n           I.amOnPage('/login');\n           I.fillField('email', 'admin@site.com');\n           I.fillField('password', '123456');\n           I.click('Login');\n         },\n         check: (I) => {\n           I.amOnPage('/dashboard');\n           I.see('Admin', '.navbar');\n         },\n         fetch: () => {}, // empty function\n         restore: () => {}, // empty funciton\n       }\n    }\n  }\n}\n```\n\n#### Example: Getting sessions from local storage\n\nIf your session is stored in local storage instead of cookies you still can obtain sessions.\n\n```js\nplugins: {\n   auth: {\n    admin: {\n      login: (I) => I.loginAsAdmin(),\n      check: (I) => I.see('Admin', '.navbar'),\n      fetch: (I) => {\n        return I.executeScript(() => localStorage.getItem('session_id'));\n      },\n      restore: (I, session) => {\n        I.amOnPage('/');\n        I.executeScript((session) => localStorage.setItem('session_id', session), session);\n      },\n    }\n  }\n}\n```\n\n#### Tips: Using async function in the auth\n\nIf you use async functions in the auth plugin, login function should be used with `await` keyword.\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n#### Tips: Using session to validate user\n\nInstead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I, session) => {\n         // Throwing an error in `check` will make CodeceptJS perform the login step for the user\n         if (session.profile.email !== the.email.you.expect@some-mail.com) {\n              throw new Error ('Wrong user signed in');\n        }\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/autoDelay.md":"---\npermalink: /plugins/auto-delay\neditLink: false\nsidebar: auto\ntitle: autoDelay\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## autoDelay\n\nSometimes it takes some time for a page to respond to user's actions.\nDepending on app's performance this can be either slow or fast.\n\nFor instance, if you click a button and nothing happens - probably JS event is not attached to this button yet\nAlso, if you fill field and input validation doesn't accept your input - maybe because you typed value too fast.\n\nThis plugin allows to slow down tests execution when a test running too fast.\nIt puts a tiny delay for before and after action commands.\n\nCommands affected (by default):\n\n*   `click`\n*   `fillField`\n*   `checkOption`\n*   `pressKey`\n*   `doubleClick`\n*   `rightClick`\n\n#### Configuration\n\n```js\nplugins: {\n   autoDelay: {\n     enabled: true\n   }\n}\n```\n\nPossible config options:\n\n*   `methods`: list of affected commands. Can be overridden\n*   `delayBefore`: put a delay before a command. 100ms by default\n*   `delayAfter`: put a delay after a command. 200ms by default\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/browser.md":"---\npermalink: /plugins/browser\neditLink: false\nsidebar: auto\ntitle: browser\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## browser\n\nOverrides browser helper config from the command line. Works for all browser helpers\n(Playwright, Puppeteer, WebDriver, Appium) without touching `codecept.conf`.\n\nEnable it via `-p` option with one or more colon-chained args:\n\n    npx codeceptjs run -p browser:show\n    npx codeceptjs run -p browser:hide\n    npx codeceptjs run -p browser:browser=firefox\n    npx codeceptjs run -p browser:windowSize=1024x768:video=false\n    npx codeceptjs run -p browser:hide:browser=webkit:windowSize=800x600\n\n#### Args\n\n*   **show** — force visible browser\n*   **hide** — force headless (also injects `--headless` into WebDriver chrome/firefox capability args)\n*   **`<key>=<value>`** — set `helpers.<eachBrowserHelper>.<key> = <value>`. Three keys\n    get per-helper translation via `setBrowserConfig`:\n    *   `browser=<name>` — Puppeteer receives `product`, Playwright receives `browser`\n    *   `windowSize=WxH` — also adds `--window-size=W,H` chromium/chrome args\n    *   `show=true|false` — toggles `show` on Playwright/Puppeteer; injects/strips\n        `--headless` in WebDriver chrome/firefox capability args\n\nValues stay as strings. `true` / `false` are coerced to booleans.\n\nRequires `@codeceptjs/configure` to be installed; if missing, the plugin\nlogs a hint and skips the override.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/coverage.md":"---\npermalink: /plugins/coverage\neditLink: false\nsidebar: auto\ntitle: coverage\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## coverage\n\nDumps code coverage from Playwright/Puppeteer after every test.\n\n#### Configuration\n\n```js\nplugins: {\n   coverage: {\n     enabled: true,\n     debug: true,\n     name: 'CodeceptJS Coverage Report',\n     outputDir: 'output/coverage'\n   }\n}\n```\n\nPossible config options, More could be found at [monocart-coverage-reports][1]\n\n*   `debug`: debug info. By default, false.\n*   `name`: coverage report name.\n*   `outputDir`: path to coverage report.\n*   `sourceFilter`: filter the source files.\n*   `sourcePath`: option to resolve a custom path.\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options\n","docs/plugins/customLocator.md":"---\npermalink: /plugins/custom-locator\neditLink: false\nsidebar: auto\ntitle: customLocator\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customLocator\n\nCreates a [custom locator][1] by using special attributes in HTML.\n\nIf you have a convention to use `data-test-id` or `data-qa` attributes to mark active elements for e2e tests,\nyou can enable this plugin to simplify matching elements with these attributes:\n\n```js\n// replace this:\nI.click({ css: '[data-test-id=register_button]');\n// with this:\nI.click('$register_button');\n```\n\nThis plugin will create a valid XPath locator for you.\n\n#### Configuration\n\n*   `enabled` (default: `false`) should a locator be enabled\n*   `prefix` (default: `$`) sets a prefix for a custom locator.\n*   `attribute` (default: `data-test-id`) to set an attribute to be matched.\n*   `strategy` (default: `xpath`) actual locator strategy to use in query (`css` or `xpath`).\n*   `showActual` (default: false) show in the output actually produced XPath or CSS locator. By default shows custom locator value.\n\n#### Examples:\n\nUsing `data-test` attribute with `$` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   attribute: 'data-test'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('$user'); // matches => [data-test=user]\nI.click('$sign-up'); // matches => [data-test=sign-up]\n```\n\nUsing `data-qa` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: 'data-qa'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up]\n```\n\nUsing `data-qa` OR `data-test` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'xpath'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]\nI.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]\n```\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'css'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user],[data-test=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://codecept.io/locators#custom-locators\n","docs/plugins/customReporter.md":"---\npermalink: /plugins/custom-reporter\neditLink: false\nsidebar: auto\ntitle: customReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customReporter\n\nSample custom reporter for CodeceptJS.\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/expose.md":"---\npermalink: /plugins/expose\neditLink: false\nsidebar: auto\ntitle: expose\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## expose\n\nExposes properties from helper instances as injectable test arguments.\nUse it to access the underlying Playwright/Puppeteer `page`, the wdio `browser` client,\nor any other helper internal directly from a Scenario:\n\n```js\nScenario('listen for requests', async ({ I, page, browser }) => {\n  page.on('request', r => console.log(r.url()))\n  await page.evaluate(() => 1 + 1)\n  I.amOnPage('/')\n})\n```\n\nThe injected value is a live proxy: every property access reads the *current*\nhelper property, so mid-test reassignments (popups, `switchToNextTab`,\n`openNewTab`) are reflected automatically. Calls are not wrapped as\nCodeceptJS steps — `await page.evaluate(...)` runs as native Playwright.\n\n#### Configuration\n\n`inject` maps an injection name to a `HelperName.propertyName` string. A\nvalue with no dot is shorthand for \"first configured browser helper that\nexposes this property\" (allowed properties: `page`, `browser`,\n`browserContext`, `context`).\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'Playwright.page',\n      browser: 'Playwright.browser',\n      browserContext: 'Playwright.browserContext',\n      frame: 'Playwright.context',  // current frame set by switchTo\n      wdio: 'WebDriver.browser',\n    }\n  }\n}\n```\n\nShorthand:\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'page',  // resolves to Playwright.page or Puppeteer.page\n    }\n  }\n}\n```\n\n#### Caveats\n\n*   The injected value is a `Proxy`, not the actual `Page`/`Browser` instance,\n    so `page instanceof Page` is `false`. Use duck typing instead.\n*   Cached method references lose the live binding. Call `page.click(...)`,\n    not `const click = page.click; click(...)`.\n*   In dry-run mode the underlying helper property is `undefined`; accessing\n    any property on the proxy returns `undefined` rather than throwing.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/heal.md":"---\npermalink: /plugins/heal\neditLink: false\nsidebar: auto\ntitle: heal\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## heal\n\nSelf-healing tests with AI.\n\nRead more about healing in [Self-Healing Tests][1]\n\n```js\nplugins: {\n  heal: {\n   enabled: true,\n   on: 'fail',\n  }\n}\n```\n\nMore config options are available:\n\n*   `healLimit` - how many steps can be healed in a single test (default: 2)\n*   `on` - trigger mode. `fail` (default), `file` (filter to a path), `url` (filter to a URL pattern).\n\n#### `on=` modes\n\nHeal always runs on step failures; `on=` narrows when it engages.\n\n*   **fail** — heal any failing step (default)\n*   **file** — heal only failures in `path=...[;line=...]`\n*   **url** — heal only failures when the current URL matches `pattern=...`\n\n`on=step` and `on=test` are not supported and are rejected with an error.\n\n### Parameters\n\n*   `config`   \n\n[1]: https://codecept.io/heal/\n","docs/plugins/junitReporter.md":"---\npermalink: /plugins/junit-reporter\neditLink: false\nsidebar: auto\ntitle: junitReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## junitReporter\n\nGenerates a JUnit-compatible XML report after a test run.\n\nUnlike Mocha's `mocha-junit-reporter`, this plugin understands CodeceptJS steps and substeps.\nFor every `<testcase>` it includes:\n\n*   `<properties>` — the test's meta information: every `meta` key from `Scenario('...', { meta })`, plus its `tags` and `retries`\n*   `<system-out>` — an indented step/substep log (substeps are nested under their meta step); only failed steps are marked\n*   `<failure>` — for failed tests: the error message, type, stack trace and (optionally) the step trace\n\nThe produced file is consumable by Jenkins, GitLab CI, CircleCI, GitHub Actions test reporters, etc.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"junitReporter\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `outputName`: file name for the report. Default: `report.xml`.\n*   `output`: directory where the report is stored, relative to the project root. Default: the `output` directory. With `run-workers` and `multiple` runs, each run writes its own report into its run folder (e.g. `output/dev_chromium1`) and this option is ignored.\n*   `testGroupName`: value of the `name` attribute on the root `<testsuites>` element. Default: `CodeceptJS`.\n*   `attachMeta`: add the test's meta information (`meta` keys, `tags`, `retries`) as `<properties>`. Default: true.\n*   `attachSteps`: add the step/substep log as `<system-out>`. Default: true.\n*   `stepsInFailure`: append the step trace to the `<failure>` body. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p junitReporter\n    npx codeceptjs run -p junitReporter:outputName=junit.xml\n\n> ℹ When running with `run-workers`, steps are serialized between processes and substep nesting is flattened.\n\n### Parameters\n\n*   `config` **any**  \n","docs/plugins/pageInfo.md":"---\npermalink: /plugins/page-info\neditLink: false\nsidebar: auto\ntitle: pageInfo\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pageInfo\n\nCollects information from web page after each failed test and adds it to the test as an artifact.\nIt is suggested to enable this plugin if you run tests on CI and you need to debug failed tests.\nThis plugin can be paired with `analyze` plugin to provide more context.\n\nIt collects URL, HTML errors (by classes), and browser logs.\n\nEnable this plugin in config:\n\n```js\nplugins: {\n pageInfo: {\n  enabled: true,\n}\n```\n\nAdditional config options:\n\n*   `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)\n*   `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)\n\n### Parameters\n\n*   `config`   \n","docs/plugins/pause.md":"---\npermalink: /plugins/pause\neditLink: false\nsidebar: auto\ntitle: pause\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pause\n\nPauses test execution interactively. Replaces the legacy `pauseOnFail`\nplugin. The default `on=fail` matches the old `pauseOnFail` behavior.\n\n#### Configuration\n\n```js\nplugins: {\n  pause: {\n    enabled: false,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — pause when a step fails (default). Failures inside `tryTo` and\n    non-final `retryTo` attempts are ignored\n*   **test** — pause after each test\n*   **step** — pause before the first step (interactive walk-through)\n*   **file** — pause when execution reaches `path=...[;line=...]`\n*   **url** — pause when the browser URL matches `pattern=...`\n\nCLI examples:\n\n    npx codeceptjs run -p pause\n    npx codeceptjs run -p pause:on=step\n    npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43\n    npx codeceptjs run -p pause:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`   \n","docs/plugins/pauseOnFail.md":"---\npermalink: /plugins/pause-on-fail\neditLink: false\nsidebar: auto\ntitle: pauseOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pauseOnFail\n\nStarts an interactive pause when a test fails.\n\n**Deprecated:** use the `pause` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/retryFailedStep.md":"---\npermalink: /plugins/retry-failed-step\neditLink: false\nsidebar: auto\ntitle: retryFailedStep\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## retryFailedStep\n\nRetries each failed step in a test.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    retryFailedStep: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins retryFailedStep\n\n#### Configuration:\n\n*   `retries` - number of retries (by default 3),\n*   `factor` - The exponential factor to use. Default is 1.5.\n*   `minTimeout` - The number of milliseconds before starting the first retry. Default is 150.\n*   `maxTimeout` - The maximum number of milliseconds between two retries. Default is 10000.\n*   `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.\n*   `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:\n    *   `amOnPage`\n    *   `wait*`\n    *   `send*`\n    *   `execute*`\n    *   `run*`\n    *   `have*`\n*   `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n    To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well.\n*   `deferToScenarioRetries` - when enabled (default), step retries are automatically disabled if scenario retries are configured to avoid excessive total retries.\n\n#### Example\n\n```js\nplugins: {\n    retryFailedStep: {\n        enabled: true,\n        ignoredSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ]\n    }\n}\n```\n\n#### Disable Per Test\n\nThis plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps:\n\nUse scenario configuration to disable plugin for a test\n\n```js\nScenario('scenario tite', { disableRetryFailedStep: true }, () => {\n   // test goes here\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/screencast.md":"---\npermalink: /plugins/screencast\neditLink: false\nsidebar: auto\ntitle: screencast\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screencast\n\nRecords a video of tests. Uses Playwright's `page.screencast` API (WebM) when the active\nhelper is Playwright, or raw CDP `Page.startScreencast` (APNG, assembled in-process) when the\nactive helper is `CDPBrowser` or a subclass (`Obscura`, `Kitesurf`, ...). Which path is used is\ndetected automatically per test run; nothing in the config changes between them.\n\nWhen `captions` is enabled, action annotations are burned into the video — Playwright only,\nvia `page.screencast.showActions()`/`showChapter()`; silently absent on the CDP path, since CDP\nscreencast frames are raw, uncomposited page captures with no overlay mechanism. `subtitles`\n(a standalone `.srt`) works identically on both paths, since it's driven by step events, not by\nthe video API. Default `on=fail` keeps videos for failed tests only; `on=test` keeps every\ntest's video.\n\nNote: enabling Playwright's helper-level `video: true` together with this plugin produces two\nindependent recordings (`output/videos/*.webm` from the helper, `output/screencast/*.webm` from\nthis plugin).\n\n#### Configuration\n\n```js\nplugins: {\n  screencast: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — record while running; delete on pass, keep on fail (default)\n*   **test** — record and keep every test's video\n\nOther config options:\n\n*   `captions`: burn-in action overlays via `page.screencast.showActions()`. Playwright only. Default: true.\n*   `subtitles`: also write a standalone `.srt` file alongside the video. Default: false.\n*   `video`: record a video. With `video=false, subtitles=true`, only the `.srt` is produced. Default: true.\n*   `size`: pass-through `{ width, height }` — `screencast.start`'s `size` on Playwright, `maxWidth`/`maxHeight` on the CDP path.\n*   `quality`: pass-through 0–100 for `screencast.start` (Playwright) or CDP `Page.startScreencast` (CDPBrowser family).\n\nCLI examples:\n\n    npx codeceptjs run -p screencast\n    npx codeceptjs run -p screencast:on=test\n    npx codeceptjs run -p screencast:on=test;captions=false;subtitles=true\n\n### Parameters\n\n*   `config`   \n","docs/plugins/screenshot.md":"---\npermalink: /plugins/screenshot\neditLink: false\nsidebar: auto\ntitle: screenshot\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshot\n\nSaves screenshots from the browser at points triggered by `on=`.\n\nReplaces the legacy `screenshotOnFail` plugin. Default `on=fail` preserves the\nold behavior (screenshot when a test fails). Pass `slides=true` (with `on=step`)\nto generate a step-by-step slideshow report — replaces the legacy\n`stepByStepReport` plugin.\n\n#### Configuration\n\n```js\nplugins: {\n  screenshot: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — screenshot when a test fails (default)\n*   **test** — screenshot at the end of every test\n*   **step** — screenshot after every step\n*   **file** — screenshot for steps in `path=...[;line=...]`\n*   **url** — screenshot when the current browser URL matches `pattern=...`\n\nOther config options:\n\n*   `uniqueScreenshotNames`: use unique names for screenshot. Default: false.\n*   `fullPageScreenshots`: make full page screenshots. Default: false.\n*   `disableScreenshots`: legacy switch to skip the plugin entirely.\n*   `slides`: generate a step-by-step slideshow report (requires `on=step`). Default: false.\n*   `deleteSuccessful`: when `slides=true`, drop slideshow directories of passing tests. Default: true.\n*   `animateSlides`: when `slides=true`, animate transitions between slides. Default: true.\n*   `ignoreSteps`: when `slides=true`, RegExps of step names to skip in the slideshow.\n\nCLI examples:\n\n    npx codeceptjs run -p screenshot\n    npx codeceptjs run -p screenshot:on=step\n    npx codeceptjs run -p screenshot:on=step;slides=true\n    npx codeceptjs run -p screenshot:on=file:path=tests/login_test.js\n    npx codeceptjs run -p screenshot:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`   \n","docs/plugins/screenshotOnFail.md":"---\npermalink: /plugins/screenshot-on-fail\neditLink: false\nsidebar: auto\ntitle: screenshotOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshotOnFail\n\nSaves a screenshot when a test fails.\n\n**Deprecated:** use the `screenshot` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/sitemap.md":"---\npermalink: /plugins/sitemap\neditLink: false\nsidebar: auto\ntitle: sitemap\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## sitemap\n\nCollects all pages visited while tests are running and saves them as a [sitemap][1] XML file.\n\nThe plugin listens to browser navigation events, so it doesn't slow down test steps.\nPage loads and SPA route changes (`history.pushState`) of the top-level page are recorded, iframes are ignored.\nEvery unique `http(s)` URL is added to the sitemap. URL fragments (`#...`) are dropped.\n\nSupported helpers: Playwright, Puppeteer, and WebDriver with BiDi protocol enabled (default).\nThe sitemap is written once all tests finish.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"sitemap\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `file`: path to the sitemap file, relative to the output directory. Default: `sitemap.xml`, saved as `output/sitemap.xml`.\n*   `stripQuery`: remove query strings so `/posts?page=1` and `/posts?page=2` become one `/posts` entry. Set to `false` to keep them. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p sitemap\n    npx codeceptjs run -p sitemap:file=visited.xml\n    npx codeceptjs run -p sitemap:stripQuery=false\n\nWorks with `run-workers`: URLs from all workers are merged into a single sitemap.\n\n### Parameters\n\n*   `config` **any**  \n\n[1]: https://www.sitemaps.org/protocol.html\n","docs/plugins/stepTimeout.md":"---\npermalink: /plugins/step-timeout\neditLink: false\nsidebar: auto\ntitle: stepTimeout\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## stepTimeout\n\nSet timeout for test steps globally.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    stepTimeout: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins stepTimeout\n\n#### Configuration:\n\n*   `timeout` - global step timeout, default 150 seconds\n\n*   `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false\n\n*   `noTimeoutSteps` - an array of steps with no timeout. Default:\n\n    *   `amOnPage`\n    *   `wait*`\n\n    you could set your own noTimeoutSteps which would replace the default one.\n\n*   `customTimeoutSteps` - an array of step actions with custom timeout. Use it to override or extend noTimeoutSteps.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n\n#### Example\n\n```js\nplugins: {\n    stepTimeout: {\n        enabled: true,\n        overrideStepLimits: true,\n        noTimeoutSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ],\n        customTimeoutSteps: [\n          ['myFlakyStep*', 1],\n          ['scrollWhichRequiresTimeout', 5],\n        ]\n    }\n}\n```\n\n### Parameters\n\n*   `config` &#x20;\n"},"files":{"CLAUDE.md":"# CodeceptJS ESM Migration Plan\n\nThis project is migrating from 3.x to ESM replacing all require() calls with imports.\n\n## Compare with Reference\n\nEach time unsure what to do refer to the same file implementation in **3.x branch**\n3.x branch contains stable version of all core classes that work\n\n## Running Tests\n\nFocus on acceptance tests:\n\nFor instance this helps to understand if final migration is ok:\n\n```\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --debug --grep within\n```\n\nDo not say: it works befire running specific acceptance test.\nTests may stuck so always run them with timeout call:\n\n```\ntimeout=30000 ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n```\n\nWeb Server for this tests are running on port 8000 and it works but responds 500 for HEAD requests.\n\n## Princinples\n\nAll unit tests for Playwright/Puppeteer/WebdriverIO are passing. But acceptance tests are failing due to promises composition. Complexity of promises composition comes from promise chaining and async/await syntax. And `session` mechanism from `promise.js` which allows spawning different promises chains and sync them up in the end.\n\nThe full test suite extensively uses plugins so refer to plugins and corresponding helpers as well\n\nWe are building test framework. So even passing ests are not indicator of successful usage. Side effects like: timeouts, bad output, errors, memory leaks, etc are affecting performance.\nFor instance, if a tests stuck and won't finish this is very bad.\n\n## Coding Style\n\n- Never add comments unless explicitly required.\n- DO not mention plugins or helpers inside core classes\n","docs/agents.md":"---\npermalink: /agents\ntitle: Agentic Testing\n---\n\n# Agentic Testing\n\n## What Makes CodeceptJS Agent-friendly\n\nCodeceptJS 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.\n\nAgents get full control over test and browser execution: \n\n- **Full HTML access and ARIA snapshots.** Agents see buttons with icons, empty labels, and other elements Playwright MCP's accessibility tree omits.\n\n- **Query HTML directly with CSS, ARIA, Semantic Locators or XPath** Agents can freely run XPath or CSS to efficiently search over HTML.\n\n- **Browser logs and page state as files.** Agents read via native filesystem tools instead of extra requests, eliminating redundant context dumps.\n\n- **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.\n\n- **Testing-first framework, not browser control.** CodeceptJS is built for testing, so agents stay focused on test scenarios instead of raw browser commands.\n\nCodeceptJS 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.\n\n## Essential Setup\n\nTwo things make agent testing work: the **skills** that teach the agent CodeceptJS, and the **MCP server** that lets it drive the browser. Set both up once, from your project directory:\n\n```bash\nnpx codeceptjs init:agent claude\n```\n\nUse `codex`, `cursor`, or `opencode` instead of `claude` for other agents, or omit the name to pick from a list. `npx codeceptjs init` offers the same step at the end.\n\n### Manual setup\n\nInstall the skills:\n\n```bash\nnpx skills add codeceptjs/skills\n```\n\nConnect the MCP server (`npx codeceptjs-mcp`).\n\n**Claude Code:**\n\n```bash\nclaude mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Codex:**\n\n```bash\ncodex mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Cursor** — add to `.cursor/mcp.json`:\n\n```json\n{ \"mcpServers\": { \"codeceptjs\": { \"command\": \"npx\", \"args\": [\"codeceptjs-mcp\"] } } }\n```\n\nSee [/mcp](/mcp) for full client setup. Now the agent is ready to run the loop.\n\n## The loop\n\nWhether the agent is writing a new test or fixing an old one, it follows the same cycle.\n\n1. **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.\n2. **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.\n3. **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.\n4. **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.\n5. **Move forward.** The agent looks at the new state and chooses the next command. Steps 2–4 repeat until the scenario is whole.\n6. **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.\n\n\n## How It Works\n\nCodeceptJS 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.\n\n## Why MCP\n\nThe traditional agent testing loop is test/fix/retry, where the agent executes a test, watches it fail, reads artifacts, performs code fixes, and reruns the test. The agent applies fixes by intelligent guess — looking at the ARIA tree, HTML, and screenshot — then assumes the fix is enough and reruns the test hoping it will pass. If the guess is wrong and the test runs for over a minute, it may take dozens of minutes of iteration and a lot of wasted tokens.\n\nTo improve that flow, the agent can spawn a browser and open the page the way the test does. This lets it interact with the page more freely and perform multi-step actions. But putting that experience back into test code is not efficient either: actions executed in the browser may not be relevant in test context, so the agent ends up in another guess-and-try loop.\n\nThe problem is that **the test runs in a different context than the agent**.\n\nThe agent can launch a test but can't control it while it's running. It can't access the browser. It can't set a breakpoint.\n\nThis is where CodeceptJS MCP steps in. Connected to the agent, it can:\n\n- run a test and pause it on failure\n- interact with the browser in a test context\n- test locators and perform actions live while the test is running\n- write successful actions to the test file\n\nThis lets the agent get a test working in one iteration. The agent can live-write the test before your eyes by exploring the page and performing actions that eventually land in the CodeceptJS test file.\n\n**Live debugging of tests** is what CodeceptJS MCP provides. The agent receives feedback faster — not from a whole test execution but from specific actions on a specific page — so it can adjust and react faster, trying different approaches.\n\nThe 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).\n\n\n## How the agent reads the page\n\nMCP commands are token efficient — they don't stream large HTML pages back to the model. MCP writes artifacts to disk under `output/trace_*/` and returns file paths. The agent reads each artifact with its own bash tools — `cat`, `grep`, `jq`.\n\nA `run_code` response, for example, looks like this:\n\n```json\n{\n  \"status\": \"success\",\n  \"artifacts\": {\n    \"url\": \"http://localhost:8000/\",\n    \"html\": \"file:///output/trace_run_code_.../mcp_page.html\",\n    \"aria\": \"file:///output/trace_run_code_.../mcp_aria.txt\",\n    \"screenshot\": \"file:///output/trace_run_code_.../mcp_screenshot.png\",\n    \"console\": \"file:///output/trace_run_code_.../mcp_console.json\",\n    \"storage\": \"file:///output/trace_run_code_.../mcp_storage.json\"\n  }\n}\n```\n\nOnly `url` is inline. The rest are paths the agent opens with the right tool:\n\n| Artifact | How the agent reads it |\n|----------|------------------------|\n| `*_screenshot.png` | As an image — most agents are multimodal |\n| `*_aria.txt` | Whole — small and structured |\n| `*_page.html` | With `grep` — too large for context, searchable for specific elements/attributes |\n| `*_console.json` | With `jq` — filter for errors, 4xx/5xx, deprecation warnings |\n| `*_storage.json` | Whole — cookies and `localStorage` snapshot |\n| `trace.md` | Whole — markdown index linking every step to its artifacts |\n\nSaved HTML is formatted, with non-semantic elements stripped out: `<style>`, `<script>`, Tailwind-style trash classes, and inline `style=\"\"` attributes. `grep` can then effectively find the correct tree branch in raw page source. ARIA snapshots are smaller and more structured than HTML, which is why the agent prefers them when picking locators.\n\n## Usage Examples\n\nWhen MCP and skills are connected, the agent receives predefined workflows and can act effectively for testing purposes. Common scenarios it handles:\n\n### Writing a new test\n\nYou ask: \"Add a test for the checkout flow.\"\n\nThe agent writes a stub:\n\n```javascript\nScenario('checkout', ({ I }) => {\n  I.amOnPage('/cart')\n  pause()\n})\n```\n\nIt runs the stub. The browser opens at `/cart` and yields control at `pause()`. The agent reads the ARIA tree, runs `I.click('Add to cart')`, sees the cart total update — that line goes into the verified sequence. It runs `I.fillField('Email', '...')`, sees the field accept the value, records it. Through `I.click('Continue to payment')`, `I.see('Payment')`, `I.fillField('Card', secret(process.env.TEST_CARD))`, `I.click('Pay')`, `I.see('Order confirmed')` — each command commits only after the response confirms it worked.\n\nWhen the scenario is whole, the agent edits the test file: replaces `pause()` with the verified sequence, renames the scenario, wraps credentials with `secret()`. It reruns the file end-to-end with `aiTrace` on and hands you the diff.\n\n### Fixing a failing test\n\nA test fails. You point the agent at the scenario.\n\nIt opens `output/trace_<TestName>_*/trace.md` from the last run, reads the steps, and finds the one marked failed. Most of the time the screenshot and ARIA from that step explain the cause — \"Save\" is now \"Save changes,\" or a spinner is gating the next action. The agent patches the line and reruns.\n\nWhen the trace doesn't say enough, the agent passes a step number to `run_test` so the test pauses right before the failing step. From the live page, it tries `I.click({ role: 'button', name: 'Save changes' })`, sees the modal close. Or `I.waitForInvisible('.spinner', 10)` followed by the original click — watches it pass. Whatever holds goes into the test.\n\nThe fix lands with a one-line note explaining what changed.\n\n### Auto-fixing on CI\n\nAfter a failed run, the agent reads every trace under `output/`, clusters failures by signature, and patches what fits a small set of safe fixes (locator drift, missing waits, raw `I.wait(N)` replacement). It reruns only the failing scenarios, compares against the baseline, and writes a markdown report at `output/ci-fix.md`.\n\nIf the fix held, the PR goes green. If it didn't, every edit is rolled back with `git checkout` and the report says which patterns the agent couldn't safely handle. No half-applied fixes left behind, no `retries: 3` masking the problem.\n\n## Assertions in plain language\n\nSome outcomes are hard to pin to a locator: \"checkout form has all required fields\", \"success message is shown\". Instead of building a fragile chain of `see*` checks, the agent can write the expected outcome as a statement with the [Decision helper](/assertions#decision-assertions):\n\n```js\nI.decide([\n  'order summary lists the purchased items',\n  'success message is shown',\n])\n```\n\nThe agent runs the statement on the live page like any other command and keeps it only if it passes. The test then checks it on every run.\n\nTo let the agent use decisions, [configure a decision model](/ai#configure-decision-models) and enable the Decision helper.\n\nDecision models like [Jev](https://openrouter.ai/typesafe/jev-1.13) are built for this. They answer in a fraction of a second, cost a fraction of a cent per request, and return a probability instead of free text. That makes them fast and cheap enough to run on every CI build, and predictable enough to keep in a test.\n\n## Skills bundle\n\nSkills teach the agent best practices for using CodeceptJS. Plug them in when you develop tests with agents, and update them regularly to ensure you use CodeceptJS in the most effective way.\n\n| Skill | Use case |\n|-------|----------|\n| `writing-codeceptjs-tests` | Author or extend a scenario. Runs the loop above with a stub-and-pause flow for greenfield work, incremental `run_code` for known flows. |\n| `debugging-codeceptjs-tests` | A test is failing or flaky. Reads the trace, decides whether to patch from the trace alone or set a breakpoint on the live page. |\n| `ci-fix-tests` | Conservative auto-repair on CI |\n| `refactoring-codeceptjs-tests` | Extract page objects, tame long locators, move raw browser code into helpers. Proposes in batches. |\n| `codeceptjs-fundamentals` | Obtain actual CodeceptJS knowledge. |\n| `codeceptjs-exploration` | Pick a stable locator from messy markup. |\n| `codeceptjs-run-analysis` | Read trace artifacts, cluster CI failures into root causes, verify a fix held across many traces. |\n| `codeceptjs-auth` | Authorize efficiently with `auth` plugin. |\n\n## Pointers\n\n- [/mcp](/mcp) — full MCP tool reference, client setup\n- [/aitrace](/aitrace) — trace plugin configuration and capture options\n- [/debugging](/debugging) — pause modes, IDE setup, the `pause` plugin\n- [skills repo](https://github.com/codeceptjs/skills) — source and install for non-Claude clients\n","docs/api.md":"---\npermalink: /api\ntitle: API Testing\n---\n\n## API Testing\n\nCodeceptJS provides a way to write tests in declarative manner for REST and GraphQL APIs. \n\nTake a look:\n\n```js\nI.sendGetRequest('/users/1');\n// returns { \"user\": { \"name\": \"jon\" }, \"projects\": [] }\nI.seeResponseCodeIsSuccessful();\nI.seeResponseContainsKeys(['user', 'projects']);\nI.seeResponseContainsJson({ user: { name: 'jon' } });\nI.seeResponseMatchesJsonSchema($ => {\n  return $.object(\n    user: $.object({\n      name: $.string(),\n    }),\n    projects: $.array()\n  )\n});\n```\nIn this code we checked API request for:\n\n* status code\n* data inclusion\n* data structure\n\nThese are the things you should generally test your APIs for.\n\n> 🤓 It is recommended to check only invariable parts of responses. Check for required fields and only values you control. For instance, it is not recommended to check id fields, date fields, as they can be frequently changed.\n\n## Installation\n\nInstall CodeceptJS if it is not installed yet.\n\n```\nnpm i codeceptjs --save-dev\n```\n\nInitialize CodeceptJS and select REST or GraphQL helper when asked for a helper:\n\n```\nnpx codeceptjs init\n```\n\n## Configuration\n\nEnsure that inside `codecept.conf.js` in helpers section `REST` or `GraphQL` helpers are enabled.\n\n* If you use `REST` helper add `JSONResponse` helper below with no extra config:\n\n```js\n// inside codecept.conf.js\n// ...\n  helpers: {\n    REST: {\n      endpoint: 'http://localhost:3000/api'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {}\n  }\n```\n* If you use `GraphQL` helper add `JSONResponse` helper, configuring it to use GraphQL for requests:\n\n```js\n  helpers: {\n    GraphQL: {\n      endpoint: 'http://localhost:3000/graphql'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {\n      requestHelper: 'GraphQL',\n    }\n  }\n```\n\nOriginally, REST and GraphQL helpers were not designed for API testing. \nThey were used to perform API requests for browser tests. As so, they lack assertion methods to API responses.\n\n[`JSONResponse`](/helpers/json-response/) helper adds response assertions. \n\n> 💡 In CodeceptJS assertions start with `see` prefix. Learn more about assertions by [opening reference for JSONResponse](/helpers/json-response/) helper.\n\nGenerate TypeScript definitions to get auto-completions for JSONResponse:\n\n```\nnpx codeceptjs def\n```\n\nAfter helpers were configured and typings were generated, you can start writing first API test. By default, CodeceptJS saves tests in `tests` directory and uses `*_test.js` suffix. The `init` command created the first test for you to start.\n\n> Check [API Examples](https://github.com/codeceptjs/api-examples) to see tests implementations.\n\n## Requests\n\n[REST](/helpers/rest/) or [GraphQL](/helpers/graph-ql/) helpers implement methods for making API requests.\nBoth helpers send requests via HTTP protocol from CodeceptJS process. \nFor most cases, you will need to have authentication. It can be passed via headers, which can be added to helper's configuration in `codecept.conf.js`. \n\n```js\nhelpers: {\n  REST: {\n    defaultHeaders: {\n      // use Bearer Authorization\n      'Authorization': 'Bearer 11111',\n      'Content-Type': 'application/json',\n      'Accept': 'application/json',\n    },\n  }\n}\n```\n\nOr you can use the browser cookies if you are running browser session.\nIn this case use `setSharedCookies()` from `@codeceptjs/configure` package:\n\n```js\nimport { setSharedCookies } from '@codeceptjs/configure'\n\n// call before exporting config\nsetSharedCookies()\n\nexport const config = {\n  // ...\n  helpers: {  \n    // also works with Playwright or Puppeteer\n    WebDriver: {\n      //... \n    },\n\n    REST: { \n      // ...\n    }\n  }\n}\n```\n\n### REST\n\nREST helper can send GET/POST/PATCH/etc requests to REST API endpoint:\n\n* [`I.sendGetRequest()`](/helpers/rest#sendgetrequest)\n* [`I.sendPostRequest()`](/helpers/rest#sendpostrequest)\n* [`I.sendPutRequest()`](/helpers/rest#sendputrequest)\n* [`I.sendPatchRequest()`](/helpers/rest#sendpatchrequest)\n* [`I.sendDeleteRequest()`](/helpers/rest#senddeleterequest)\n* [`I.sendDeleteRequestWithPayload()`](/helpers/rest#senddeleterequestwithpayload)\n* ...\n\nAuthentication headers can be set in [helper's config](https://codecept.io/helpers/rest/#configuration) or per test with headers or special methods like `I.amBearerAuthenticated`.\n\nExample:\n\n```js\nFeature('Users endpoint')\n\nScenario('create user', ({ I }) => {\n    // this way we pass Bearer token\n  I.amBearerAuthenticated(secret('token-is-here'));\n  // for custom authorization with headers use\n  // I.haveRequestHeaders method\n\n  // here we send a POST request\n  const response = await I.sendPostRequest('/users', {\n    name: 'joe',\n    email: 'joe@mail.com'\n  });\n  // usually we won't need direct access to response object for API testing \n  // but you can obtain it from request\n\n  // check the last request was successful\n  // this method introduced by JSONResponse helper\n  I.seeResponseCodeIsSuccessful();\n})\n```\n\n### GraphQL\n\nGraphQL have request format different then in REST API, but the response format is the same.\nIt's plain old JSON. This why `JSONResponse` helper works for both API types.\nConfigure authorization headers in `codecept.conf.js` and make your first query:  \n\n```js\nFeature('Users endpoint')\n\nScenario('get user by query', ({ I }) => {\n  // make GraphQL query or mutation\n  const resp = await I.sendQuery('{ user(id: 0) { id name email }}');\n  I.seeResponseCodeIsSuccessful();\n\n  // GraphQL always returns key data as part of response\n  I.seeResponseContainsKeys(['data']);\n\n  // check data for partial inclusion\n  I.seeResponseContainsJson({\n    data: {\n      user: {\n        name: 'john doe',\n        email: 'johnd@mutex.com',\n      },\n    },\n  });\n});\n```\n\nGraphQL helper has two methods available:\n\n* [`I.sendQuery()`](/helpers/graph-ql#sendquery)\n* [`I.sendMutation()`](/helpers/graph-ql#sendmutation)\n\n## Assertions\n\n`JSONResponse` provides set of assertions for responses in JSON format. These assertions were designed to check only invariable parts of responses. So instead of checking that response equals to the one provided, we will check for data inclusion and structure matching.\n\nFor most of cases, you won't need to perform assertions by accessing `response` object directly. All assretions are performed under hood inside `JSONResponse` module. It is recommended to keep it that way, to keep tests readable and make test log to contain all assertions.\n\n```js\nScenario('I make API call', ({ I }) => {\n  // request was made by REST \n  // or by GraphQL helper\n\n  // check that response code is 2xx\n  I.seeResponseCodeIsSuccessful();\n\n  // check that response contains keys\n  I.seeResponseContainsKeys(['data', 'pages', 'meta']);\n});\n```\n\n### Response Status Codes\n\n[Response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) can be checked to be equal to some value or to be in a specific range. \nTo check that response code is `200` call `I.seeResponseCodeIs`:\n\n```js\nI.seeResponseCodeIs(200);\n```\nBut because other response codes in 2xx range are also valid responses, you can use `seeResponseCodeIsSuccessful()` which will match 200 (OK), 201 (Created), 206 (Partial Content) and others. Methods to check 3xx, 4xx, 5xx response statuses also available.\n\n```js\n// matches 200, 201, 202, ... 206\nI.seeResponseCodeIsSuccessful();\n\n// matches 300...308\nI.seeResponseCodeIsRedirection();\n\n// matches 400..451\nI.seeResponseCodeIsClientError();\n\n// matches 500-511\nI.seeResponseCodeIsServerError();\n```\n\n### Structure\n\nThe most basic thing to check in response is existence of keys in JSON object. Use [`I.seeResponseContainsKeys()`](/helpers/json-response#seeresponsecontainskeys) method for it:\n\n```js\n// response is { \"name\": \"joe\", \"email\": \"joe@joe.com\" }\nI.seeResponseContainsKeys(['name', 'email']);\n```\n\n> ℹ️ If response is an array, it will check that every element in array have provided keys\n\nHowever, this is a very naive approach. It won't work for arrays or nested objects.\nTo check complex JSON structures `JSONResponse` helper uses [`Zod`](https://zod.dev) library.\nIt has rich API to validate JSON by the schema defined using JavaScript.\n\n```js\n// import zod library,\n// it is installed with CodeceptJS\nimport { z } from 'zod';\n\n// create schema definition using Zod API\nconst schema = z.object({\n  email: z.string().email(),\n  phone: z.string().regex(/^\\d{3}-\\d{3}-\\d{4}$/),\n  birthday: z.string().datetime().max(new Date('2004-01-01'))\n});\n\n// check that response matches that schema\nI.seeResponseMatchesJsonSchema(schema);\n```\n\n> 📋 **Migration Note**: CodeceptJS has migrated from Joi to Zod v4 for JSON schema validation.\n> If you have existing tests using Joi, please update them:\n> * Replace `const Joi = require('joi')` with `import { z } from 'zod'`\n> * Replace `Joi.object().keys({...})` with `z.object({...})`\n> * Replace `Joi.string().email()` with `z.string().email()`\n> * Replace `Joi.date()` with appropriate `z.string()` or `z.date()` types\n> * See [Zod documentation](https://zod.dev) for complete API reference\n\n### Data Inclusion\n\nTo check that response contains expected data use `I.seeResponseContainsJson` method. \nIt will check the response data for partial match. \n\n```js\nI.seeResponseContainsJson({\n  user: {\n    email: 'user@user.com'\n  }\n})\n```\n\n> ℹ️ If response is an array, it will check that at least one element in array matches JSON\n\nTo perform arbitrary assertions on a response object use `seeResponseValidByCallback`. \nIt allows you to do any kind of assertions by using `expect` from [`chai`](https://www.chaijs.com) library.\n\n```js\nI.seeResponseValidByCallback(({ data, status, expect }) => {\n  // we receive data and expect to combine them for good assertion\n  expect(data.users.length).to.be.gte(10);\n})\n```\n\n## Extending JSONResponse\n\nTo add more assertions it is recommended to create a custom helper.\nInside it you can get access to latest JSON response:\n\n```js\n// inside a custom helper\nmakeSomeCustomAssertion() {\n  const response = this.helpers.JSONResponse.response;\n}\n```","docs/architecture.md":"---\npermalink: /architecture\ntitle: Architecture\n---\n\n# CodeceptJS Architecture\n\nHow CodeceptJS runs a test, and the internal modules you build [plugins, listeners, and helpers](/hooks) against.\n\n## How a Test Runs\n\nCodeceptJS is built on top of [Mocha](https://mochajs.org). A run goes through these stages:\n\n1. **Load.** CodeceptJS reads the config, builds the [container](#container) (helpers, support objects, plugins), and runs the `bootstrap` hook. `event.all.before` fires.\n2. **Suite.** For each suite, `event.suite.before` fires. Helper `_beforeSuite` hooks run.\n3. **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.\n4. **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`.\n5. **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.\n\nThe 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.\n\n## The Internal API\n\nCodeceptJS exposes its internals as named exports of the `codeceptjs` package. Import only what you need:\n\n```js\nimport { recorder, event, output, container, config } from 'codeceptjs'\n```\n\n| Export | What it is |\n| --- | --- |\n| [`codecept`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/codecept.js) | the test runner class |\n| [`config`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/config.js) | the loaded configuration |\n| [`container`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/container.js) | dependency-injection container: helpers, support objects, plugins, the Mocha instance |\n| [`recorder`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/recorder.js) | the global promise chain that orders every step |\n| [`event`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/event.js) | the event dispatcher and the names of all lifecycle events |\n| [`output`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/output.js) | the printer used for all console output |\n| [`store`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/store.js) | global state of the run — current test/step, run modes, directories |\n| [`helper`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/helper.js) | the base class every helper extends |\n| [`actor`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/actor.js) | the base class behind the `I` object |\n\n> 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.\n\nThe [API reference](https://github.com/codeceptjs/CodeceptJS/tree/master/docs/api) on GitHub documents these modules; the source is the final word.\n\n## The Recorder\n\nThe 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:\n\n```js\nimport { event, recorder } from 'codeceptjs'\n\nevent.dispatcher.on(event.test.before, () => {\n  recorder.add('seed fixture data', async () => {\n    await api.post('/users', { name: 'john', email: 'john@example.com' })\n  })\n})\n```\n\n- `recorder.add(name, fn)` — append `fn` (async, or returning a promise) to the chain. The name shows up in `--verbose` output.\n- `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`.\n- `recorder.retry({ retries, when })` — retry failing steps that match `when`. See [conditional retries](/helpers#conditional-retries).\n\nRun tests with `--verbose` to watch the recorder schedule and execute each entry.\n\n## Container\n\nThe container resolves helpers and support objects by name:\n\n```js\nimport { container } from 'codeceptjs'\n\nconst helpers = container.helpers()          // every helper, keyed by name\nconst { Playwright } = container.helpers()   // one helper\nconst support = container.support()          // every support object\nconst { UserPage } = container.support()     // one page object\nconst plugins = container.plugins()          // enabled plugins\nconst mocha = container.mocha()              // the current Mocha instance\n```\n\nAdd objects at runtime — useful from a `bootstrap` script:\n\n```js\nimport { container } from 'codeceptjs'\nimport UserPage from './pages/user.js'\n\ncontainer.append({\n  helpers: { MyHelper: new MyHelper({ host: 'http://example.com' }) },\n  support: { UserPage },\n})\n```\n\n## Events\n\n`event.dispatcher` is a Node `EventEmitter`. Attach listeners to it from a [plugin](/hooks#plugins) or `bootstrap` script.\n\nEvents are **sync** or **async**:\n\n- **sync** — fires the moment the action happens. Do synchronous work only.\n- **async** — fires when the action is *scheduled*. To do async work in the right order, queue it with `recorder.add()`.\n\n| Event | Kind | When |\n| --- | --- | --- |\n| `event.all.before` | — | before any test runs |\n| `event.suite.before(suite)` | async | before a suite |\n| `event.test.started(test)` | sync | at the very start of a test |\n| `event.test.before(test)` | async | after `Before` hooks from helpers and the test are run |\n| `event.test.passed(test)` | sync | test passed |\n| `event.test.failed(test, err)` | sync | test failed |\n| `event.test.skipped(test)` | sync | test skipped |\n| `event.test.after(test)` | async | after each test |\n| `event.test.finished(test)` | sync | test finished |\n| `event.suite.after(suite)` | async | after a suite |\n| `event.step.before(step)` | async | step scheduled for execution |\n| `event.step.started(step)` | sync | step starts executing |\n| `event.step.passed(step)` | sync | step passed |\n| `event.step.failed(step, err)` | sync | step failed |\n| `event.step.after(step)` | async | after a step |\n| `event.step.finished(step)` | sync | step finished |\n| `event.step.comment(step)` | sync | a comment such as `I.say(...)` |\n| `event.bddStep.before(step)` / `event.bddStep.after(step)` | async | around a Gherkin step |\n| `event.hook.started(hook)` / `event.hook.passed` / `event.hook.failed` / `event.hook.finished` | sync | around `Before` / `After` / `BeforeSuite` / `AfterSuite` hooks |\n| `event.all.after` | — | after all tests |\n| `event.all.result(result)` | — | when results are printed |\n| `event.all.failures(failures)` | — | when a run reports failures |\n| `event.workers.before` / `event.workers.after` / `event.workers.result(result)` | — | around a [parallel run](/parallel) (parent process only) |\n\nThe [built-in listeners](https://github.com/codeceptjs/CodeceptJS/tree/master/lib/listener) are working examples — every reporter and several plugins are listeners.\n\n### Test object\n\nTest events pass a test object with these fields:\n\n- `title` — the test title\n- `body` — the test function as a string\n- `opts` — test options such as `retries` (see [test options](/advanced#test-options))\n- `pending` — `true` while scheduled, `false` once finished\n- `tags` — array of [tags](/test-structure#tags) for this test\n- `artifacts` — files attached to this test (screenshots, videos, …), shared across reporters\n- `file` — path to the test file\n- `steps` — executed steps (only on `test.passed`, `test.failed`, `test.finished`)\n- `skipInfo` — present when the test was skipped: `{ message, description }`\n\n### Step object\n\nStep events pass a step object with these fields:\n\n- `name` — the step name, such as `see` or `click`\n- `actor` — the current actor, usually `I`\n- `helper` — the helper instance that executes this step\n- `helperMethod` — the helper method, usually the same as `name`\n- `status` — `passed` or `failed`\n- `prefix` — for a step inside a `within` block, the within text (e.g. `Within .js-signup-form`)\n- `args` — the arguments passed to the step\n\n## Config\n\n```js\nimport { config } from 'codeceptjs'\n\nconfig.get()                       // the full config object\nconfig.get('myKey')                // one value\nconfig.get('myKey', 'fallback')    // one value, with a default\n```\n\n## Output\n\nOutput has four verbosity levels, each toggled by a CLI flag:\n\n| Level | Flag | Use |\n| --- | --- | --- |\n| default | — | `output.print` — basic information |\n| steps | `--steps` | step execution |\n| debug | `--debug` | steps plus `output.debug` |\n| verbose | `--verbose` | debug plus `output.log` (internal logs and recorder activity) |\n\n```js\nimport { output } from 'codeceptjs'\n\noutput.print('basic information')\noutput.debug('debug information')\noutput.log('verbose logging information')\n```\n\nUse these instead of `console.log` so messages respect the chosen verbosity.\n\n## Store\n\n`store` holds the state of the current run — the executing test, suite, and step, the active run modes (`dryRun`, `debugMode`, `workerMode`, …), and the project directories. Listeners, plugins, and helpers read it to know where in the [lifecycle](#events) they are without that information being passed to them:\n\n```js\nimport { store } from 'codeceptjs'\n\nevent.dispatcher.on(event.step.before, () => {\n  if (store.dryRun) return                    // no side effects on a dry run\n  output.debug(`in ${store.currentTest?.title}`)\n})\n```\n\nCodeceptJS keeps the state fields up to date for you. See the [Store reference](/store) for every field and when to write to it.\n\n## Helpers and the Actor\n\nThe `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).\n\n## Running CodeceptJS from Code\n\nCodeceptJS can be driven from your own script. Create the runner with a config and options, initialize it, then bootstrap, load tests, and run:\n\n```js\nimport { codecept as Codecept } from 'codeceptjs'\n\nconst config = { helpers: { Playwright: { browser: 'chromium', url: 'http://localhost' } } }\nconst opts = { steps: true }\n\nconst codecept = new Codecept(config, opts)\ncodecept.init(import.meta.dirname)   // the test root directory\n\ntry {\n  await codecept.bootstrap()\n  codecept.loadTests('**/*_test.js')\n  await codecept.run()               // pass a test file path to run only that file\n} catch (err) {\n  console.error(err)\n  process.exitCode = 1\n} finally {\n  await codecept.teardown()\n}\n```\n\n> To run tests inside workers from a script, see [parallel execution](/parallel).\n\n---\n\n**See also:** [Extending CodeceptJS](/hooks) · [Custom Helpers](/helpers) · [Plugins](/plugins) · [Bootstrap & Teardown](/bootstrap)\n","docs/plugins/aiTrace.md":"---\npermalink: /plugins/ai-trace\neditLink: false\nsidebar: auto\ntitle: aiTrace\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## aiTrace\n\nGenerates AI-friendly trace files for debugging with AI agents.\nThis plugin creates a markdown file with test execution logs and links to all artifacts\n(screenshots, HTML, ARIA snapshots, browser logs, HTTP requests) for each step.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"aiTrace\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `deleteSuccessful`: delete traces for successfully executed tests. Default: false.\n*   `fullPageScreenshots`: should full page screenshots be used. Default: false.\n*   `output`: a directory where traces should be stored. Default: `output`.\n*   `captureHTML`: capture HTML for each step. Default: true.\n*   `captureARIA`: capture ARIA snapshot for each step. Default: true.\n*   `captureBrowserLogs`: capture browser console logs. Default: true.\n*   `captureHTTP`: capture HTTP requests (requires `trace` or `recordHar` enabled in helper config). Default: true.\n*   `captureDebugOutput`: capture CodeceptJS debug output. Default: true.\n*   `ignoreSteps`: steps to ignore in trace. Array of RegExps is expected.\n*   `on`: trigger mode — `step` (default), `fail`, `test`, `file`, `url`.\n\n#### `on=` modes\n\n*   **step** — persist every step (default)\n*   **fail** — persist only the failed step\n*   **test** — persist only the last step of each test\n*   **file** — persist steps from `path=...[;line=...]`\n*   **url** — persist when the current URL matches `pattern=...`\n\n### Parameters\n\n*   `config` **any**  \n","docs/plugins/analyze.md":"---\npermalink: /plugins/analyze\neditLink: false\nsidebar: auto\ntitle: analyze\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## analyze\n\nUses AI to analyze test failures and provide insights\n\nThis plugin analyzes failed tests using AI to provide detailed explanations and group similar failures.\nWhen enabled with --ai flag, it generates reports after test execution.\n\n#### Usage\n\n```js\n// in codecept.conf.js\nexports.config = {\n  plugins: {\n    analyze: {\n      enabled: true,\n      clusterize: 5,\n      analyze: 2,\n      vision: false\n    }\n  }\n}\n```\n\n#### Configuration\n\n*   `clusterize` (number) - minimum number of failures to trigger clustering analysis. Default: 5\n*   `analyze` (number) - maximum number of individual test failures to analyze in detail. Default: 2\n*   `vision` (boolean) - enables visual analysis of test screenshots. Default: false\n*   `categories` (array) - list of failure categories for classification. Defaults to:\n    *   Browser connection error / browser crash\n    *   Network errors (server error, timeout, etc)\n    *   HTML / page elements (not found, not visible, etc)\n    *   Navigation errors (404, etc)\n    *   Code errors (syntax error, JS errors, etc)\n    *   Library & framework errors\n    *   Data errors (password incorrect, invalid format, etc)\n    *   Assertion failures\n    *   Other errors\n*   `prompts` (object) - customize AI prompts for analysis\n    *   `clusterize` - prompt for clustering analysis\n    *   `analyze` - prompt for individual test analysis\n\n#### Features\n\n*   Groups similar failures when number of failures >= clusterize value\n*   Provides detailed analysis of individual failures\n*   Analyzes screenshots if vision=true and screenshots are available\n*   Classifies failures into predefined categories\n*   Suggests possible causes and solutions\n\n### Parameters\n\n*   `config` **[Object][1]** Plugin configuration \n\nReturns **void**&#x20;\n\n[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n","docs/plugins/auth.md":"---\npermalink: /plugins/auth\neditLink: false\nsidebar: auto\ntitle: auth\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## auth\n\nLogs user in for the first test and reuses session for next tests.\nWorks by saving cookies into memory or file.\nIf a session expires automatically logs in again.\n\n> For better development experience cookies can be saved into file, so a session can be reused while writing tests.\n\n#### Usage\n\n1.  Enable this plugin and configure as described below\n2.  Define user session names (example: `user`, `editor`, `admin`, etc).\n3.  Define how users are logged in and how to check that user is logged in\n4.  Use `login` object inside your tests to log in:\n\n```js\n// inside a test file\n// use login to inject auto-login function\nFeature('Login');\n\nBefore(({ login }) => {\n   login('user'); // login using user session\n});\n\n// Alternatively log in for one scenario.\nScenario('log me in', ( { I, login } ) => {\n   login('admin');\n   I.see('I am logged in');\n});\n```\n\n#### Configuration\n\n*   `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.\n*   `inject` (default: `login`) - name of the login function to use\n*   `users` - an array containing different session names and functions to:\n    *   `login` - sign in into the system\n    *   `check` - check that user is logged in\n    *   `fetch` - to get current cookies (by default `I.grabCookie()`)\n    *   `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)\n\n#### How It Works\n\n1.  `restore` method is executed. It should open a page and set credentials.\n2.  `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged-in user. When you pass the second args `session`, you could perform the validation using passed session.\n3.  If `restore` and `check` were not successful, `login` is executed\n4.  `login` should fill in login form\n5.  After successful login, `fetch` is executed to save cookies into memory or file.\n\n#### Example: Simple login\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      // loginAdmin function is defined in `steps_file.js`\n      login: (I) => I.loginAdmin(),\n      // if we see `Admin` on page, we assume we are logged in\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      }\n    }\n  }\n}\n```\n\n#### Example: Multiple users\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'loginAs', // use `loginAs` instead of login\n  users: {\n    user: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'user@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('User', '.navbar');\n      },\n    },\n    admin: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'admin@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin', '.navbar');\n      },\n    },\n  }\n}\n```\n\n#### Example: Keep cookies between tests\n\nIf you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.\nBut you need to login once work until session expires.\nFor this case, disable `fetch` and `restore` methods.\n\n```js\nhelpers: {\n   WebDriver: {\n     // config goes here\n     keepCookies: true; // keep cookies for all tests\n   }\n},\nplugins: {\n   auth: {\n     users: {\n       admin: {\n         login: (I) => {\n           I.amOnPage('/login');\n           I.fillField('email', 'admin@site.com');\n           I.fillField('password', '123456');\n           I.click('Login');\n         },\n         check: (I) => {\n           I.amOnPage('/dashboard');\n           I.see('Admin', '.navbar');\n         },\n         fetch: () => {}, // empty function\n         restore: () => {}, // empty funciton\n       }\n    }\n  }\n}\n```\n\n#### Example: Getting sessions from local storage\n\nIf your session is stored in local storage instead of cookies you still can obtain sessions.\n\n```js\nplugins: {\n   auth: {\n    admin: {\n      login: (I) => I.loginAsAdmin(),\n      check: (I) => I.see('Admin', '.navbar'),\n      fetch: (I) => {\n        return I.executeScript(() => localStorage.getItem('session_id'));\n      },\n      restore: (I, session) => {\n        I.amOnPage('/');\n        I.executeScript((session) => localStorage.setItem('session_id', session), session);\n      },\n    }\n  }\n}\n```\n\n#### Tips: Using async function in the auth\n\nIf you use async functions in the auth plugin, login function should be used with `await` keyword.\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n#### Tips: Using session to validate user\n\nInstead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I, session) => {\n         // Throwing an error in `check` will make CodeceptJS perform the login step for the user\n         if (session.profile.email !== the.email.you.expect@some-mail.com) {\n              throw new Error ('Wrong user signed in');\n        }\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/autoDelay.md":"---\npermalink: /plugins/auto-delay\neditLink: false\nsidebar: auto\ntitle: autoDelay\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## autoDelay\n\nSometimes it takes some time for a page to respond to user's actions.\nDepending on app's performance this can be either slow or fast.\n\nFor instance, if you click a button and nothing happens - probably JS event is not attached to this button yet\nAlso, if you fill field and input validation doesn't accept your input - maybe because you typed value too fast.\n\nThis plugin allows to slow down tests execution when a test running too fast.\nIt puts a tiny delay for before and after action commands.\n\nCommands affected (by default):\n\n*   `click`\n*   `fillField`\n*   `checkOption`\n*   `pressKey`\n*   `doubleClick`\n*   `rightClick`\n\n#### Configuration\n\n```js\nplugins: {\n   autoDelay: {\n     enabled: true\n   }\n}\n```\n\nPossible config options:\n\n*   `methods`: list of affected commands. Can be overridden\n*   `delayBefore`: put a delay before a command. 100ms by default\n*   `delayAfter`: put a delay after a command. 200ms by default\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/browser.md":"---\npermalink: /plugins/browser\neditLink: false\nsidebar: auto\ntitle: browser\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## browser\n\nOverrides browser helper config from the command line. Works for all browser helpers\n(Playwright, Puppeteer, WebDriver, Appium) without touching `codecept.conf`.\n\nEnable it via `-p` option with one or more colon-chained args:\n\n    npx codeceptjs run -p browser:show\n    npx codeceptjs run -p browser:hide\n    npx codeceptjs run -p browser:browser=firefox\n    npx codeceptjs run -p browser:windowSize=1024x768:video=false\n    npx codeceptjs run -p browser:hide:browser=webkit:windowSize=800x600\n\n#### Args\n\n*   **show** — force visible browser\n*   **hide** — force headless (also injects `--headless` into WebDriver chrome/firefox capability args)\n*   **`<key>=<value>`** — set `helpers.<eachBrowserHelper>.<key> = <value>`. Three keys\n    get per-helper translation via `setBrowserConfig`:\n    *   `browser=<name>` — Puppeteer receives `product`, Playwright receives `browser`\n    *   `windowSize=WxH` — also adds `--window-size=W,H` chromium/chrome args\n    *   `show=true|false` — toggles `show` on Playwright/Puppeteer; injects/strips\n        `--headless` in WebDriver chrome/firefox capability args\n\nValues stay as strings. `true` / `false` are coerced to booleans.\n\nRequires `@codeceptjs/configure` to be installed; if missing, the plugin\nlogs a hint and skips the override.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/coverage.md":"---\npermalink: /plugins/coverage\neditLink: false\nsidebar: auto\ntitle: coverage\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## coverage\n\nDumps code coverage from Playwright/Puppeteer after every test.\n\n#### Configuration\n\n```js\nplugins: {\n   coverage: {\n     enabled: true,\n     debug: true,\n     name: 'CodeceptJS Coverage Report',\n     outputDir: 'output/coverage'\n   }\n}\n```\n\nPossible config options, More could be found at [monocart-coverage-reports][1]\n\n*   `debug`: debug info. By default, false.\n*   `name`: coverage report name.\n*   `outputDir`: path to coverage report.\n*   `sourceFilter`: filter the source files.\n*   `sourcePath`: option to resolve a custom path.\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options\n","docs/plugins/customLocator.md":"---\npermalink: /plugins/custom-locator\neditLink: false\nsidebar: auto\ntitle: customLocator\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customLocator\n\nCreates a [custom locator][1] by using special attributes in HTML.\n\nIf you have a convention to use `data-test-id` or `data-qa` attributes to mark active elements for e2e tests,\nyou can enable this plugin to simplify matching elements with these attributes:\n\n```js\n// replace this:\nI.click({ css: '[data-test-id=register_button]');\n// with this:\nI.click('$register_button');\n```\n\nThis plugin will create a valid XPath locator for you.\n\n#### Configuration\n\n*   `enabled` (default: `false`) should a locator be enabled\n*   `prefix` (default: `$`) sets a prefix for a custom locator.\n*   `attribute` (default: `data-test-id`) to set an attribute to be matched.\n*   `strategy` (default: `xpath`) actual locator strategy to use in query (`css` or `xpath`).\n*   `showActual` (default: false) show in the output actually produced XPath or CSS locator. By default shows custom locator value.\n\n#### Examples:\n\nUsing `data-test` attribute with `$` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   attribute: 'data-test'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('$user'); // matches => [data-test=user]\nI.click('$sign-up'); // matches => [data-test=sign-up]\n```\n\nUsing `data-qa` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: 'data-qa'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up]\n```\n\nUsing `data-qa` OR `data-test` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'xpath'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]\nI.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]\n```\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'css'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user],[data-test=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://codecept.io/locators#custom-locators\n","docs/plugins/customReporter.md":"---\npermalink: /plugins/custom-reporter\neditLink: false\nsidebar: auto\ntitle: customReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customReporter\n\nSample custom reporter for CodeceptJS.\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/expose.md":"---\npermalink: /plugins/expose\neditLink: false\nsidebar: auto\ntitle: expose\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## expose\n\nExposes properties from helper instances as injectable test arguments.\nUse it to access the underlying Playwright/Puppeteer `page`, the wdio `browser` client,\nor any other helper internal directly from a Scenario:\n\n```js\nScenario('listen for requests', async ({ I, page, browser }) => {\n  page.on('request', r => console.log(r.url()))\n  await page.evaluate(() => 1 + 1)\n  I.amOnPage('/')\n})\n```\n\nThe injected value is a live proxy: every property access reads the *current*\nhelper property, so mid-test reassignments (popups, `switchToNextTab`,\n`openNewTab`) are reflected automatically. Calls are not wrapped as\nCodeceptJS steps — `await page.evaluate(...)` runs as native Playwright.\n\n#### Configuration\n\n`inject` maps an injection name to a `HelperName.propertyName` string. A\nvalue with no dot is shorthand for \"first configured browser helper that\nexposes this property\" (allowed properties: `page`, `browser`,\n`browserContext`, `context`).\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'Playwright.page',\n      browser: 'Playwright.browser',\n      browserContext: 'Playwright.browserContext',\n      frame: 'Playwright.context',  // current frame set by switchTo\n      wdio: 'WebDriver.browser',\n    }\n  }\n}\n```\n\nShorthand:\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'page',  // resolves to Playwright.page or Puppeteer.page\n    }\n  }\n}\n```\n\n#### Caveats\n\n*   The injected value is a `Proxy`, not the actual `Page`/`Browser` instance,\n    so `page instanceof Page` is `false`. Use duck typing instead.\n*   Cached method references lose the live binding. Call `page.click(...)`,\n    not `const click = page.click; click(...)`.\n*   In dry-run mode the underlying helper property is `undefined`; accessing\n    any property on the proxy returns `undefined` rather than throwing.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/heal.md":"---\npermalink: /plugins/heal\neditLink: false\nsidebar: auto\ntitle: heal\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## heal\n\nSelf-healing tests with AI.\n\nRead more about healing in [Self-Healing Tests][1]\n\n```js\nplugins: {\n  heal: {\n   enabled: true,\n   on: 'fail',\n  }\n}\n```\n\nMore config options are available:\n\n*   `healLimit` - how many steps can be healed in a single test (default: 2)\n*   `on` - trigger mode. `fail` (default), `file` (filter to a path), `url` (filter to a URL pattern).\n\n#### `on=` modes\n\nHeal always runs on step failures; `on=` narrows when it engages.\n\n*   **fail** — heal any failing step (default)\n*   **file** — heal only failures in `path=...[;line=...]`\n*   **url** — heal only failures when the current URL matches `pattern=...`\n\n`on=step` and `on=test` are not supported and are rejected with an error.\n\n### Parameters\n\n*   `config`   \n\n[1]: https://codecept.io/heal/\n","docs/plugins/junitReporter.md":"---\npermalink: /plugins/junit-reporter\neditLink: false\nsidebar: auto\ntitle: junitReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## junitReporter\n\nGenerates a JUnit-compatible XML report after a test run.\n\nUnlike Mocha's `mocha-junit-reporter`, this plugin understands CodeceptJS steps and substeps.\nFor every `<testcase>` it includes:\n\n*   `<properties>` — the test's meta information: every `meta` key from `Scenario('...', { meta })`, plus its `tags` and `retries`\n*   `<system-out>` — an indented step/substep log (substeps are nested under their meta step); only failed steps are marked\n*   `<failure>` — for failed tests: the error message, type, stack trace and (optionally) the step trace\n\nThe produced file is consumable by Jenkins, GitLab CI, CircleCI, GitHub Actions test reporters, etc.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"junitReporter\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `outputName`: file name for the report. Default: `report.xml`.\n*   `output`: directory where the report is stored, relative to the project root. Default: the `output` directory. With `run-workers` and `multiple` runs, each run writes its own report into its run folder (e.g. `output/dev_chromium1`) and this option is ignored.\n*   `testGroupName`: value of the `name` attribute on the root `<testsuites>` element. Default: `CodeceptJS`.\n*   `attachMeta`: add the test's meta information (`meta` keys, `tags`, `retries`) as `<properties>`. Default: true.\n*   `attachSteps`: add the step/substep log as `<system-out>`. Default: true.\n*   `stepsInFailure`: append the step trace to the `<failure>` body. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p junitReporter\n    npx codeceptjs run -p junitReporter:outputName=junit.xml\n\n> ℹ When running with `run-workers`, steps are serialized between processes and substep nesting is flattened.\n\n### Parameters\n\n*   `config` **any**  \n","docs/plugins/pageInfo.md":"---\npermalink: /plugins/page-info\neditLink: false\nsidebar: auto\ntitle: pageInfo\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pageInfo\n\nCollects information from web page after each failed test and adds it to the test as an artifact.\nIt is suggested to enable this plugin if you run tests on CI and you need to debug failed tests.\nThis plugin can be paired with `analyze` plugin to provide more context.\n\nIt collects URL, HTML errors (by classes), and browser logs.\n\nEnable this plugin in config:\n\n```js\nplugins: {\n pageInfo: {\n  enabled: true,\n}\n```\n\nAdditional config options:\n\n*   `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)\n*   `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)\n\n### Parameters\n\n*   `config`   \n","docs/plugins/pause.md":"---\npermalink: /plugins/pause\neditLink: false\nsidebar: auto\ntitle: pause\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pause\n\nPauses test execution interactively. Replaces the legacy `pauseOnFail`\nplugin. The default `on=fail` matches the old `pauseOnFail` behavior.\n\n#### Configuration\n\n```js\nplugins: {\n  pause: {\n    enabled: false,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — pause when a step fails (default). Failures inside `tryTo` and\n    non-final `retryTo` attempts are ignored\n*   **test** — pause after each test\n*   **step** — pause before the first step (interactive walk-through)\n*   **file** — pause when execution reaches `path=...[;line=...]`\n*   **url** — pause when the browser URL matches `pattern=...`\n\nCLI examples:\n\n    npx codeceptjs run -p pause\n    npx codeceptjs run -p pause:on=step\n    npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43\n    npx codeceptjs run -p pause:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`   \n","docs/plugins/pauseOnFail.md":"---\npermalink: /plugins/pause-on-fail\neditLink: false\nsidebar: auto\ntitle: pauseOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pauseOnFail\n\nStarts an interactive pause when a test fails.\n\n**Deprecated:** use the `pause` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/retryFailedStep.md":"---\npermalink: /plugins/retry-failed-step\neditLink: false\nsidebar: auto\ntitle: retryFailedStep\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## retryFailedStep\n\nRetries each failed step in a test.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    retryFailedStep: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins retryFailedStep\n\n#### Configuration:\n\n*   `retries` - number of retries (by default 3),\n*   `factor` - The exponential factor to use. Default is 1.5.\n*   `minTimeout` - The number of milliseconds before starting the first retry. Default is 150.\n*   `maxTimeout` - The maximum number of milliseconds between two retries. Default is 10000.\n*   `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.\n*   `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:\n    *   `amOnPage`\n    *   `wait*`\n    *   `send*`\n    *   `execute*`\n    *   `run*`\n    *   `have*`\n*   `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n    To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well.\n*   `deferToScenarioRetries` - when enabled (default), step retries are automatically disabled if scenario retries are configured to avoid excessive total retries.\n\n#### Example\n\n```js\nplugins: {\n    retryFailedStep: {\n        enabled: true,\n        ignoredSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ]\n    }\n}\n```\n\n#### Disable Per Test\n\nThis plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps:\n\nUse scenario configuration to disable plugin for a test\n\n```js\nScenario('scenario tite', { disableRetryFailedStep: true }, () => {\n   // test goes here\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n","docs/plugins/screencast.md":"---\npermalink: /plugins/screencast\neditLink: false\nsidebar: auto\ntitle: screencast\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screencast\n\nRecords a video of tests. Uses Playwright's `page.screencast` API (WebM) when the active\nhelper is Playwright, or raw CDP `Page.startScreencast` (APNG, assembled in-process) when the\nactive helper is `CDPBrowser` or a subclass (`Obscura`, `Kitesurf`, ...). Which path is used is\ndetected automatically per test run; nothing in the config changes between them.\n\nWhen `captions` is enabled, action annotations are burned into the video — Playwright only,\nvia `page.screencast.showActions()`/`showChapter()`; silently absent on the CDP path, since CDP\nscreencast frames are raw, uncomposited page captures with no overlay mechanism. `subtitles`\n(a standalone `.srt`) works identically on both paths, since it's driven by step events, not by\nthe video API. Default `on=fail` keeps videos for failed tests only; `on=test` keeps every\ntest's video.\n\nNote: enabling Playwright's helper-level `video: true` together with this plugin produces two\nindependent recordings (`output/videos/*.webm` from the helper, `output/screencast/*.webm` from\nthis plugin).\n\n#### Configuration\n\n```js\nplugins: {\n  screencast: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — record while running; delete on pass, keep on fail (default)\n*   **test** — record and keep every test's video\n\nOther config options:\n\n*   `captions`: burn-in action overlays via `page.screencast.showActions()`. Playwright only. Default: true.\n*   `subtitles`: also write a standalone `.srt` file alongside the video. Default: false.\n*   `video`: record a video. With `video=false, subtitles=true`, only the `.srt` is produced. Default: true.\n*   `size`: pass-through `{ width, height }` — `screencast.start`'s `size` on Playwright, `maxWidth`/`maxHeight` on the CDP path.\n*   `quality`: pass-through 0–100 for `screencast.start` (Playwright) or CDP `Page.startScreencast` (CDPBrowser family).\n\nCLI examples:\n\n    npx codeceptjs run -p screencast\n    npx codeceptjs run -p screencast:on=test\n    npx codeceptjs run -p screencast:on=test;captions=false;subtitles=true\n\n### Parameters\n\n*   `config`   \n","docs/plugins/screenshot.md":"---\npermalink: /plugins/screenshot\neditLink: false\nsidebar: auto\ntitle: screenshot\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshot\n\nSaves screenshots from the browser at points triggered by `on=`.\n\nReplaces the legacy `screenshotOnFail` plugin. Default `on=fail` preserves the\nold behavior (screenshot when a test fails). Pass `slides=true` (with `on=step`)\nto generate a step-by-step slideshow report — replaces the legacy\n`stepByStepReport` plugin.\n\n#### Configuration\n\n```js\nplugins: {\n  screenshot: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — screenshot when a test fails (default)\n*   **test** — screenshot at the end of every test\n*   **step** — screenshot after every step\n*   **file** — screenshot for steps in `path=...[;line=...]`\n*   **url** — screenshot when the current browser URL matches `pattern=...`\n\nOther config options:\n\n*   `uniqueScreenshotNames`: use unique names for screenshot. Default: false.\n*   `fullPageScreenshots`: make full page screenshots. Default: false.\n*   `disableScreenshots`: legacy switch to skip the plugin entirely.\n*   `slides`: generate a step-by-step slideshow report (requires `on=step`). Default: false.\n*   `deleteSuccessful`: when `slides=true`, drop slideshow directories of passing tests. Default: true.\n*   `animateSlides`: when `slides=true`, animate transitions between slides. Default: true.\n*   `ignoreSteps`: when `slides=true`, RegExps of step names to skip in the slideshow.\n\nCLI examples:\n\n    npx codeceptjs run -p screenshot\n    npx codeceptjs run -p screenshot:on=step\n    npx codeceptjs run -p screenshot:on=step;slides=true\n    npx codeceptjs run -p screenshot:on=file:path=tests/login_test.js\n    npx codeceptjs run -p screenshot:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`   \n","docs/plugins/screenshotOnFail.md":"---\npermalink: /plugins/screenshot-on-fail\neditLink: false\nsidebar: auto\ntitle: screenshotOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshotOnFail\n\nSaves a screenshot when a test fails.\n\n**Deprecated:** use the `screenshot` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`   \n","docs/plugins/sitemap.md":"---\npermalink: /plugins/sitemap\neditLink: false\nsidebar: auto\ntitle: sitemap\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## sitemap\n\nCollects all pages visited while tests are running and saves them as a [sitemap][1] XML file.\n\nThe plugin listens to browser navigation events, so it doesn't slow down test steps.\nPage loads and SPA route changes (`history.pushState`) of the top-level page are recorded, iframes are ignored.\nEvery unique `http(s)` URL is added to the sitemap. URL fragments (`#...`) are dropped.\n\nSupported helpers: Playwright, Puppeteer, and WebDriver with BiDi protocol enabled (default).\nThe sitemap is written once all tests finish.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"sitemap\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `file`: path to the sitemap file, relative to the output directory. Default: `sitemap.xml`, saved as `output/sitemap.xml`.\n*   `stripQuery`: remove query strings so `/posts?page=1` and `/posts?page=2` become one `/posts` entry. Set to `false` to keep them. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p sitemap\n    npx codeceptjs run -p sitemap:file=visited.xml\n    npx codeceptjs run -p sitemap:stripQuery=false\n\nWorks with `run-workers`: URLs from all workers are merged into a single sitemap.\n\n### Parameters\n\n*   `config` **any**  \n\n[1]: https://www.sitemaps.org/protocol.html\n","docs/plugins/stepTimeout.md":"---\npermalink: /plugins/step-timeout\neditLink: false\nsidebar: auto\ntitle: stepTimeout\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## stepTimeout\n\nSet timeout for test steps globally.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    stepTimeout: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins stepTimeout\n\n#### Configuration:\n\n*   `timeout` - global step timeout, default 150 seconds\n\n*   `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false\n\n*   `noTimeoutSteps` - an array of steps with no timeout. Default:\n\n    *   `amOnPage`\n    *   `wait*`\n\n    you could set your own noTimeoutSteps which would replace the default one.\n\n*   `customTimeoutSteps` - an array of step actions with custom timeout. Use it to override or extend noTimeoutSteps.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n\n#### Example\n\n```js\nplugins: {\n    stepTimeout: {\n        enabled: true,\n        overrideStepLimits: true,\n        noTimeoutSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ],\n        customTimeoutSteps: [\n          ['myFlakyStep*', 1],\n          ['scrollWhichRequiresTimeout', 5],\n        ]\n    }\n}\n```\n\n### Parameters\n\n*   `config` &#x20;\n"},"items":[{"name":"agents.md","path":"docs/agents.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/agents.md","title":"docs - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"---\npermalink: /agents\ntitle: Agentic Testing\n---\n\n# Agentic Testing\n\n## What Makes CodeceptJS Agent-friendly\n\nCodeceptJS 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.\n\nAgents get full control over test and browser execution: \n\n- **Full HTML access and ARIA snapshots.** Agents see buttons with icons, empty labels, and other elements Playwright MCP's accessibility tree omits.\n\n- **Query HTML directly with CSS, ARIA, Semantic Locators or XPath** Agents can freely run XPath or CSS to efficiently search over HTML.\n\n- **Browser logs and page state as files.** Agents read via native filesystem tools instead of extra requests, eliminating redundant context dumps.\n\n- **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.\n\n- **Testing-first framework, not browser control.** CodeceptJS is built for testing, so agents stay focused on test scenarios instead of raw browser commands.\n\nCodeceptJS 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.\n\n## Essential Setup\n\nTwo things make agent testing work: the **skills** that teach the agent CodeceptJS, and the **MCP server** that lets it drive the browser. Set both up once, from your project directory:\n\n```bash\nnpx codeceptjs init:agent claude\n```\n\nUse `codex`, `cursor`, or `opencode` instead of `claude` for other agents, or omit the name to pick from a list. `npx codeceptjs init` offers the same step at the end.\n\n### Manual setup\n\nInstall the skills:\n\n```bash\nnpx skills add codeceptjs/skills\n```\n\nConnect the MCP server (`npx codeceptjs-mcp`).\n\n**Claude Code:**\n\n```bash\nclaude mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Codex:**\n\n```bash\ncodex mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Cursor** — add to `.cursor/mcp.json`:\n\n```json\n{ \"mcpServers\": { \"codeceptjs\": { \"command\": \"npx\", \"args\": [\"codeceptjs-mcp\"] } } }\n```\n\nSee [/mcp](/mcp) for full client setup. Now the agent is ready to run the loop.\n\n## The loop\n\nWhether the agent is writing a new test or fixing an old one, it follows the same cycle.\n\n1. **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.\n2. **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.\n3. **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.\n4. **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.\n5. **Move forward.** The agent looks at the new state and chooses the next command. Steps 2–4 repeat until the scenario is whole.\n6. **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.\n\n\n## How It Works\n\nCodeceptJS 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.\n\n## Why MCP\n\nThe traditional agent testing loop is test/fix/retry, where the agent executes a test, watches it fail, reads artifacts, performs code fixes, and reruns the test. The agent applies fixes by intelligent guess — looking at the ARIA tree, HTML, and screenshot — then assumes the fix is enough and reruns the test hoping it will pass. If the guess is wrong and the test runs for over a minute, it may take dozens of minutes of iteration and a lot of wasted tokens.\n\nTo improve that flow, the agent can spawn a browser and open the page the way the test does. This lets it interact with the page more freely and perform multi-step actions. But putting that experience back into test code is not efficient either: actions executed in the browser may not be relevant in test context, so the agent ends up in another guess-and-try loop.\n\nThe problem is that **the test runs in a different context than the agent**.\n\nThe agent can launch a test but can't control it while it's running. It can't access the browser. It can't set a breakpoint.\n\nThis is where CodeceptJS MCP steps in. Connected to the agent, it can:\n\n- run a test and pause it on failure\n- interact with the browser in a test context\n- test locators and perform actions live while the test is running\n- write successful actions to the test file\n\nThis lets the agent get a test working in one iteration. The agent can live-write the test before your eyes by exploring the page and performing actions that eventually land in the CodeceptJS test file.\n\n**Live debugging of tests** is what CodeceptJS MCP provides. The agent receives feedback faster — not from a whole test execution but from specific actions on a specific page — so it can adjust and react faster, trying different approaches.\n\nThe 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).\n\n\n## How the agent reads the page\n\nMCP commands are token efficient — they don't stream large HTML pages back to the model. MCP writes artifacts to disk under `output/trace_*/` and returns file paths. The agent reads each artifact with its own bash tools — `cat`, `grep`, `jq`.\n\nA `run_code` response, for example, looks like this:\n\n```json\n{\n  \"status\": \"success\",\n  \"artifacts\": {\n    \"url\": \"http://localhost:8000/\",\n    \"html\": \"file:///output/trace_run_code_.../mcp_page.html\",\n    \"aria\": \"file:///output/trace_run_code_.../mcp_aria.txt\",\n    \"screenshot\": \"file:///output/trace_run_code_.../mcp_screenshot.png\",\n    \"console\": \"file:///output/trace_run_code_.../mcp_console.json\",\n    \"storage\": \"file:///output/trace_run_code_.../mcp_storage.json\"\n  }\n}\n```\n\nOnly `url` is inline. The rest are paths the agent opens with the right tool:\n\n| Artifact | How the agent reads it |\n|----------|------------------------|\n| `*_screenshot.png` | As an image — most agents are multimodal |\n| `*_aria.txt` | Whole — small and structured |\n| `*_page.html` | With `grep` — too large for context, searchable for specific elements/attributes |\n| `*_console.json` | With `jq` — filter for errors, 4xx/5xx, deprecation warnings |\n| `*_storage.json` | Whole — cookies and `localStorage` snapshot |\n| `trace.md` | Whole — markdown index linking every step to its artifacts |\n\nSaved HTML is formatted, with non-semantic elements stripped out: `<style>`, `<script>`, Tailwind-style trash classes, and inline `style=\"\"` attributes. `grep` can then effectively find the correct tree branch in raw page source. ARIA snapshots are smaller and more structured than HTML, which is why the agent prefers them when picking locators.\n\n## Usage Examples\n\nWhen MCP and skills are connected, the agent receives predefined workflows and can act effectively for testing purposes. Common scenarios it handles:\n\n### Writing a new test\n\nYou ask: \"Add a test for the checkout flow.\"\n\nThe agent writes a stub:\n\n```javascript\nScenario('checkout', ({ I }) => {\n  I.amOnPage('/cart')\n  pause()\n})\n```\n\nIt runs the stub. The browser opens at `/cart` and yields control at `pause()`. The agent reads the ARIA tree, runs `I.click('Add to cart')`, sees the cart total update — that line goes into the verified sequence. It runs `I.fillField('Email', '...')`, sees the field accept the value, records it. Through `I.click('Continue to payment')`, `I.see('Payment')`, `I.fillField('Card', secret(process.env.TEST_CARD))`, `I.click('Pay')`, `I.see('Order confirmed')` — each command commits only after the response confirms it worked.\n\nWhen the scenario is whole, the agent edits the test file: replaces `pause()` with the verified sequence, renames the scenario, wraps credentials with `secret()`. It reruns the file end-to-end with `aiTrace` on and hands you the diff.\n\n### Fixing a failing test\n\nA test fails. You point the agent at the scenario.\n\nIt opens `output/trace_<TestName>_*/trace.md` from the last run, reads the steps, and finds the one marked failed. Most of the time the screenshot and ARIA from that step explain the cause — \"Save\" is now \"Save changes,\" or a spinner is gating the next action. The agent patches the line and reruns.\n\nWhen the trace doesn't say enough, the agent passes a step number to `run_test` so the test pauses right before the failing step. From the live page, it tries `I.click({ role: 'button', name: 'Save changes' })`, sees the modal close. Or `I.waitForInvisible('.spinner', 10)` followed by the original click — watches it pass. Whatever holds goes into the test.\n\nThe fix lands with a one-line note explaining what changed.\n\n### Auto-fixing on CI\n\nAfter a failed run, the agent reads every trace under `output/`, clusters failures by signature, and patches what fits a small set of safe fixes (locator drift, missing waits, raw `I.wait(N)` replacement). It reruns only the failing scenarios, compares against the baseline, and writes a markdown report at `output/ci-fix.md`.\n\nIf the fix held, the PR goes green. If it didn't, every edit is rolled back with `git checkout` and the report says which patterns the agent couldn't safely handle. No half-applied fixes left behind, no `retries: 3` masking the problem.\n\n## Assertions in plain language\n\nSome outcomes are hard to pin to a locator: \"checkout form has all required fields\", \"success message is shown\". Instead of building a fragile chain of `see*` checks, the agent can write the expected outcome as a statement with the [Decision helper](/assertions#decision-assertions):\n\n```js\nI.decide([\n  'order summary lists the purchased items',\n  'success message is shown',\n])\n```\n\nThe agent runs the statement on the live page like any other command and keeps it only if it passes. The test then checks it on every run.\n\nTo let the agent use decisions, [configure a decision model](/ai#configure-decision-models) and enable the Decision helper.\n\nDecision models like [Jev](https://openrouter.ai/typesafe/jev-1.13) are built for this. They answer in a fraction of a second, cost a fraction of a cent per request, and return a probability instead of free text. That makes them fast and cheap enough to run on every CI build, and predictable enough to keep in a test.\n\n## Skills bundle\n\nSkills teach the agent best practices for using CodeceptJS. Plug them in when you develop tests with agents, and update them regularly to ensure you use CodeceptJS in the most effective way.\n\n| Skill | Use case |\n|-------|----------|\n| `writing-codeceptjs-tests` | Author or extend a scenario. Runs the loop above with a stub-and-pause flow for greenfield work, incremental `run_code` for known flows. |\n| `debugging-codeceptjs-tests` | A test is failing or flaky. Reads the trace, decides whether to patch from the trace alone or set a breakpoint on the live page. |\n| `ci-fix-tests` | Conservative auto-repair on CI |\n| `refactoring-codeceptjs-tests` | Extract page objects, tame long locators, move raw browser code into helpers. Proposes in batches. |\n| `codeceptjs-fundamentals` | Obtain actual CodeceptJS knowledge. |\n| `codeceptjs-exploration` | Pick a stable locator from messy markup. |\n| `codeceptjs-run-analysis` | Read trace artifacts, cluster CI failures into root causes, verify a fix held across many traces. |\n| `codeceptjs-auth` | Authorize efficiently with `auth` plugin. |\n\n## Pointers\n\n- [/mcp](/mcp) — full MCP tool reference, client setup\n- [/aitrace](/aitrace) — trace plugin configuration and capture options\n- [/debugging](/debugging) — pause modes, IDE setup, the `pause` plugin\n- [skills repo](https://github.com/codeceptjs/skills) — source and install for non-Claude clients\n","frontmatter":{"permalink":"/agents","title":"Agentic Testing"},"isInternal":false,"tokens":2996,"sizeBytes":12605},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CodeceptJS ESM Migration Plan\n\nThis project is migrating from 3.x to ESM replacing all require() calls with imports.\n\n## Compare with Reference\n\nEach time unsure what to do refer to the same file implementation in **3.x branch**\n3.x branch contains stable version of all core classes that work\n\n## Running Tests\n\nFocus on acceptance tests:\n\nFor instance this helps to understand if final migration is ok:\n\n```\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --debug --grep within\n```\n\nDo not say: it works befire running specific acceptance test.\nTests may stuck so always run them with timeout call:\n\n```\ntimeout=30000 ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n```\n\nWeb Server for this tests are running on port 8000 and it works but responds 500 for HEAD requests.\n\n## Princinples\n\nAll unit tests for Playwright/Puppeteer/WebdriverIO are passing. But acceptance tests are failing due to promises composition. Complexity of promises composition comes from promise chaining and async/await syntax. And `session` mechanism from `promise.js` which allows spawning different promises chains and sync them up in the end.\n\nThe full test suite extensively uses plugins so refer to plugins and corresponding helpers as well\n\nWe are building test framework. So even passing ests are not indicator of successful usage. Side effects like: timeouts, bad output, errors, memory leaks, etc are affecting performance.\nFor instance, if a tests stuck and won't finish this is very bad.\n\n## Coding Style\n\n- Never add comments unless explicitly required.\n- DO not mention plugins or helpers inside core classes\n","isInternal":false,"tokens":383,"sizeBytes":1778},{"name":"aiTrace.md","path":"docs/plugins/aiTrace.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/aiTrace.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/ai-trace\neditLink: false\nsidebar: auto\ntitle: aiTrace\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## aiTrace\n\nGenerates AI-friendly trace files for debugging with AI agents.\nThis plugin creates a markdown file with test execution logs and links to all artifacts\n(screenshots, HTML, ARIA snapshots, browser logs, HTTP requests) for each step.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"aiTrace\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `deleteSuccessful`: delete traces for successfully executed tests. Default: false.\n*   `fullPageScreenshots`: should full page screenshots be used. Default: false.\n*   `output`: a directory where traces should be stored. Default: `output`.\n*   `captureHTML`: capture HTML for each step. Default: true.\n*   `captureARIA`: capture ARIA snapshot for each step. Default: true.\n*   `captureBrowserLogs`: capture browser console logs. Default: true.\n*   `captureHTTP`: capture HTTP requests (requires `trace` or `recordHar` enabled in helper config). Default: true.\n*   `captureDebugOutput`: capture CodeceptJS debug output. Default: true.\n*   `ignoreSteps`: steps to ignore in trace. Array of RegExps is expected.\n*   `on`: trigger mode — `step` (default), `fail`, `test`, `file`, `url`.\n\n#### `on=` modes\n\n*   **step** — persist every step (default)\n*   **fail** — persist only the failed step\n*   **test** — persist only the last step of each test\n*   **file** — persist steps from `path=...[;line=...]`\n*   **url** — persist when the current URL matches `pattern=...`\n\n### Parameters\n\n*   `config` **any**  \n","frontmatter":{"permalink":"/plugins/ai-trace","editLink":false,"sidebar":"auto","title":"aiTrace"},"isInternal":false,"tokens":414,"sizeBytes":1661},{"name":"analyze.md","path":"docs/plugins/analyze.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/analyze.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/analyze\neditLink: false\nsidebar: auto\ntitle: analyze\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## analyze\n\nUses AI to analyze test failures and provide insights\n\nThis plugin analyzes failed tests using AI to provide detailed explanations and group similar failures.\nWhen enabled with --ai flag, it generates reports after test execution.\n\n#### Usage\n\n```js\n// in codecept.conf.js\nexports.config = {\n  plugins: {\n    analyze: {\n      enabled: true,\n      clusterize: 5,\n      analyze: 2,\n      vision: false\n    }\n  }\n}\n```\n\n#### Configuration\n\n*   `clusterize` (number) - minimum number of failures to trigger clustering analysis. Default: 5\n*   `analyze` (number) - maximum number of individual test failures to analyze in detail. Default: 2\n*   `vision` (boolean) - enables visual analysis of test screenshots. Default: false\n*   `categories` (array) - list of failure categories for classification. Defaults to:\n    *   Browser connection error / browser crash\n    *   Network errors (server error, timeout, etc)\n    *   HTML / page elements (not found, not visible, etc)\n    *   Navigation errors (404, etc)\n    *   Code errors (syntax error, JS errors, etc)\n    *   Library & framework errors\n    *   Data errors (password incorrect, invalid format, etc)\n    *   Assertion failures\n    *   Other errors\n*   `prompts` (object) - customize AI prompts for analysis\n    *   `clusterize` - prompt for clustering analysis\n    *   `analyze` - prompt for individual test analysis\n\n#### Features\n\n*   Groups similar failures when number of failures >= clusterize value\n*   Provides detailed analysis of individual failures\n*   Analyzes screenshots if vision=true and screenshots are available\n*   Classifies failures into predefined categories\n*   Suggests possible causes and solutions\n\n### Parameters\n\n*   `config` **[Object][1]** Plugin configuration \n\nReturns **void**&#x20;\n\n[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n","frontmatter":{"permalink":"/plugins/analyze","editLink":false,"sidebar":"auto","title":"analyze"},"isInternal":false,"tokens":469,"sizeBytes":2046},{"name":"auth.md","path":"docs/plugins/auth.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/auth.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/auth\neditLink: false\nsidebar: auto\ntitle: auth\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## auth\n\nLogs user in for the first test and reuses session for next tests.\nWorks by saving cookies into memory or file.\nIf a session expires automatically logs in again.\n\n> For better development experience cookies can be saved into file, so a session can be reused while writing tests.\n\n#### Usage\n\n1.  Enable this plugin and configure as described below\n2.  Define user session names (example: `user`, `editor`, `admin`, etc).\n3.  Define how users are logged in and how to check that user is logged in\n4.  Use `login` object inside your tests to log in:\n\n```js\n// inside a test file\n// use login to inject auto-login function\nFeature('Login');\n\nBefore(({ login }) => {\n   login('user'); // login using user session\n});\n\n// Alternatively log in for one scenario.\nScenario('log me in', ( { I, login } ) => {\n   login('admin');\n   I.see('I am logged in');\n});\n```\n\n#### Configuration\n\n*   `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.\n*   `inject` (default: `login`) - name of the login function to use\n*   `users` - an array containing different session names and functions to:\n    *   `login` - sign in into the system\n    *   `check` - check that user is logged in\n    *   `fetch` - to get current cookies (by default `I.grabCookie()`)\n    *   `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)\n\n#### How It Works\n\n1.  `restore` method is executed. It should open a page and set credentials.\n2.  `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged-in user. When you pass the second args `session`, you could perform the validation using passed session.\n3.  If `restore` and `check` were not successful, `login` is executed\n4.  `login` should fill in login form\n5.  After successful login, `fetch` is executed to save cookies into memory or file.\n\n#### Example: Simple login\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      // loginAdmin function is defined in `steps_file.js`\n      login: (I) => I.loginAdmin(),\n      // if we see `Admin` on page, we assume we are logged in\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      }\n    }\n  }\n}\n```\n\n#### Example: Multiple users\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'loginAs', // use `loginAs` instead of login\n  users: {\n    user: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'user@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('User', '.navbar');\n      },\n    },\n    admin: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'admin@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin', '.navbar');\n      },\n    },\n  }\n}\n```\n\n#### Example: Keep cookies between tests\n\nIf you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.\nBut you need to login once work until session expires.\nFor this case, disable `fetch` and `restore` methods.\n\n```js\nhelpers: {\n   WebDriver: {\n     // config goes here\n     keepCookies: true; // keep cookies for all tests\n   }\n},\nplugins: {\n   auth: {\n     users: {\n       admin: {\n         login: (I) => {\n           I.amOnPage('/login');\n           I.fillField('email', 'admin@site.com');\n           I.fillField('password', '123456');\n           I.click('Login');\n         },\n         check: (I) => {\n           I.amOnPage('/dashboard');\n           I.see('Admin', '.navbar');\n         },\n         fetch: () => {}, // empty function\n         restore: () => {}, // empty funciton\n       }\n    }\n  }\n}\n```\n\n#### Example: Getting sessions from local storage\n\nIf your session is stored in local storage instead of cookies you still can obtain sessions.\n\n```js\nplugins: {\n   auth: {\n    admin: {\n      login: (I) => I.loginAsAdmin(),\n      check: (I) => I.see('Admin', '.navbar'),\n      fetch: (I) => {\n        return I.executeScript(() => localStorage.getItem('session_id'));\n      },\n      restore: (I, session) => {\n        I.amOnPage('/');\n        I.executeScript((session) => localStorage.setItem('session_id', session), session);\n      },\n    }\n  }\n}\n```\n\n#### Tips: Using async function in the auth\n\nIf you use async functions in the auth plugin, login function should be used with `await` keyword.\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n#### Tips: Using session to validate user\n\nInstead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I, session) => {\n         // Throwing an error in `check` will make CodeceptJS perform the login step for the user\n         if (session.profile.email !== the.email.you.expect@some-mail.com) {\n              throw new Error ('Wrong user signed in');\n        }\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n","frontmatter":{"permalink":"/plugins/auth","editLink":false,"sidebar":"auto","title":"auth"},"isInternal":false,"tokens":1607,"sizeBytes":6271},{"name":"autoDelay.md","path":"docs/plugins/autoDelay.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/autoDelay.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/auto-delay\neditLink: false\nsidebar: auto\ntitle: autoDelay\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## autoDelay\n\nSometimes it takes some time for a page to respond to user's actions.\nDepending on app's performance this can be either slow or fast.\n\nFor instance, if you click a button and nothing happens - probably JS event is not attached to this button yet\nAlso, if you fill field and input validation doesn't accept your input - maybe because you typed value too fast.\n\nThis plugin allows to slow down tests execution when a test running too fast.\nIt puts a tiny delay for before and after action commands.\n\nCommands affected (by default):\n\n*   `click`\n*   `fillField`\n*   `checkOption`\n*   `pressKey`\n*   `doubleClick`\n*   `rightClick`\n\n#### Configuration\n\n```js\nplugins: {\n   autoDelay: {\n     enabled: true\n   }\n}\n```\n\nPossible config options:\n\n*   `methods`: list of affected commands. Can be overridden\n*   `delayBefore`: put a delay before a command. 100ms by default\n*   `delayAfter`: put a delay after a command. 200ms by default\n\n### Parameters\n\n*   `config` &#x20;\n","frontmatter":{"permalink":"/plugins/auto-delay","editLink":false,"sidebar":"auto","title":"autoDelay"},"isInternal":false,"tokens":277,"sizeBytes":1162},{"name":"browser.md","path":"docs/plugins/browser.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/browser.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/browser\neditLink: false\nsidebar: auto\ntitle: browser\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## browser\n\nOverrides browser helper config from the command line. Works for all browser helpers\n(Playwright, Puppeteer, WebDriver, Appium) without touching `codecept.conf`.\n\nEnable it via `-p` option with one or more colon-chained args:\n\n    npx codeceptjs run -p browser:show\n    npx codeceptjs run -p browser:hide\n    npx codeceptjs run -p browser:browser=firefox\n    npx codeceptjs run -p browser:windowSize=1024x768:video=false\n    npx codeceptjs run -p browser:hide:browser=webkit:windowSize=800x600\n\n#### Args\n\n*   **show** — force visible browser\n*   **hide** — force headless (also injects `--headless` into WebDriver chrome/firefox capability args)\n*   **`<key>=<value>`** — set `helpers.<eachBrowserHelper>.<key> = <value>`. Three keys\n    get per-helper translation via `setBrowserConfig`:\n    *   `browser=<name>` — Puppeteer receives `product`, Playwright receives `browser`\n    *   `windowSize=WxH` — also adds `--window-size=W,H` chromium/chrome args\n    *   `show=true|false` — toggles `show` on Playwright/Puppeteer; injects/strips\n        `--headless` in WebDriver chrome/firefox capability args\n\nValues stay as strings. `true` / `false` are coerced to booleans.\n\nRequires `@codeceptjs/configure` to be installed; if missing, the plugin\nlogs a hint and skips the override.\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/browser","editLink":false,"sidebar":"auto","title":"browser"},"isInternal":false,"tokens":401,"sizeBytes":1515},{"name":"coverage.md","path":"docs/plugins/coverage.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/coverage.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/coverage\neditLink: false\nsidebar: auto\ntitle: coverage\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## coverage\n\nDumps code coverage from Playwright/Puppeteer after every test.\n\n#### Configuration\n\n```js\nplugins: {\n   coverage: {\n     enabled: true,\n     debug: true,\n     name: 'CodeceptJS Coverage Report',\n     outputDir: 'output/coverage'\n   }\n}\n```\n\nPossible config options, More could be found at [monocart-coverage-reports][1]\n\n*   `debug`: debug info. By default, false.\n*   `name`: coverage report name.\n*   `outputDir`: path to coverage report.\n*   `sourceFilter`: filter the source files.\n*   `sourcePath`: option to resolve a custom path.\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options\n","frontmatter":{"permalink":"/plugins/coverage","editLink":false,"sidebar":"auto","title":"coverage"},"isInternal":false,"tokens":220,"sizeBytes":861},{"name":"customLocator.md","path":"docs/plugins/customLocator.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/customLocator.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/custom-locator\neditLink: false\nsidebar: auto\ntitle: customLocator\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customLocator\n\nCreates a [custom locator][1] by using special attributes in HTML.\n\nIf you have a convention to use `data-test-id` or `data-qa` attributes to mark active elements for e2e tests,\nyou can enable this plugin to simplify matching elements with these attributes:\n\n```js\n// replace this:\nI.click({ css: '[data-test-id=register_button]');\n// with this:\nI.click('$register_button');\n```\n\nThis plugin will create a valid XPath locator for you.\n\n#### Configuration\n\n*   `enabled` (default: `false`) should a locator be enabled\n*   `prefix` (default: `$`) sets a prefix for a custom locator.\n*   `attribute` (default: `data-test-id`) to set an attribute to be matched.\n*   `strategy` (default: `xpath`) actual locator strategy to use in query (`css` or `xpath`).\n*   `showActual` (default: false) show in the output actually produced XPath or CSS locator. By default shows custom locator value.\n\n#### Examples:\n\nUsing `data-test` attribute with `$` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   attribute: 'data-test'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('$user'); // matches => [data-test=user]\nI.click('$sign-up'); // matches => [data-test=sign-up]\n```\n\nUsing `data-qa` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: 'data-qa'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up]\n```\n\nUsing `data-qa` OR `data-test` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'xpath'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]\nI.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]\n```\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'css'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user],[data-test=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://codecept.io/locators#custom-locators\n","frontmatter":{"permalink":"/plugins/custom-locator","editLink":false,"sidebar":"auto","title":"customLocator"},"isInternal":false,"tokens":690,"sizeBytes":2509},{"name":"customReporter.md","path":"docs/plugins/customReporter.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/customReporter.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/custom-reporter\neditLink: false\nsidebar: auto\ntitle: customReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customReporter\n\nSample custom reporter for CodeceptJS.\n\n### Parameters\n\n*   `config` &#x20;\n","frontmatter":{"permalink":"/plugins/custom-reporter","editLink":false,"sidebar":"auto","title":"customReporter"},"isInternal":false,"tokens":64,"sizeBytes":288},{"name":"expose.md","path":"docs/plugins/expose.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/expose.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/expose\neditLink: false\nsidebar: auto\ntitle: expose\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## expose\n\nExposes properties from helper instances as injectable test arguments.\nUse it to access the underlying Playwright/Puppeteer `page`, the wdio `browser` client,\nor any other helper internal directly from a Scenario:\n\n```js\nScenario('listen for requests', async ({ I, page, browser }) => {\n  page.on('request', r => console.log(r.url()))\n  await page.evaluate(() => 1 + 1)\n  I.amOnPage('/')\n})\n```\n\nThe injected value is a live proxy: every property access reads the *current*\nhelper property, so mid-test reassignments (popups, `switchToNextTab`,\n`openNewTab`) are reflected automatically. Calls are not wrapped as\nCodeceptJS steps — `await page.evaluate(...)` runs as native Playwright.\n\n#### Configuration\n\n`inject` maps an injection name to a `HelperName.propertyName` string. A\nvalue with no dot is shorthand for \"first configured browser helper that\nexposes this property\" (allowed properties: `page`, `browser`,\n`browserContext`, `context`).\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'Playwright.page',\n      browser: 'Playwright.browser',\n      browserContext: 'Playwright.browserContext',\n      frame: 'Playwright.context',  // current frame set by switchTo\n      wdio: 'WebDriver.browser',\n    }\n  }\n}\n```\n\nShorthand:\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'page',  // resolves to Playwright.page or Puppeteer.page\n    }\n  }\n}\n```\n\n#### Caveats\n\n*   The injected value is a `Proxy`, not the actual `Page`/`Browser` instance,\n    so `page instanceof Page` is `false`. Use duck typing instead.\n*   Cached method references lose the live binding. Call `page.click(...)`,\n    not `const click = page.click; click(...)`.\n*   In dry-run mode the underlying helper property is `undefined`; accessing\n    any property on the proxy returns `undefined` rather than throwing.\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/expose","editLink":false,"sidebar":"auto","title":"expose"},"isInternal":false,"tokens":496,"sizeBytes":2063},{"name":"heal.md","path":"docs/plugins/heal.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/heal.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/heal\neditLink: false\nsidebar: auto\ntitle: heal\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## heal\n\nSelf-healing tests with AI.\n\nRead more about healing in [Self-Healing Tests][1]\n\n```js\nplugins: {\n  heal: {\n   enabled: true,\n   on: 'fail',\n  }\n}\n```\n\nMore config options are available:\n\n*   `healLimit` - how many steps can be healed in a single test (default: 2)\n*   `on` - trigger mode. `fail` (default), `file` (filter to a path), `url` (filter to a URL pattern).\n\n#### `on=` modes\n\nHeal always runs on step failures; `on=` narrows when it engages.\n\n*   **fail** — heal any failing step (default)\n*   **file** — heal only failures in `path=...[;line=...]`\n*   **url** — heal only failures when the current URL matches `pattern=...`\n\n`on=step` and `on=test` are not supported and are rejected with an error.\n\n### Parameters\n\n*   `config`   \n\n[1]: https://codecept.io/heal/\n","frontmatter":{"permalink":"/plugins/heal","editLink":false,"sidebar":"auto","title":"heal"},"isInternal":false,"tokens":267,"sizeBytes":963},{"name":"junitReporter.md","path":"docs/plugins/junitReporter.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/junitReporter.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/junit-reporter\neditLink: false\nsidebar: auto\ntitle: junitReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## junitReporter\n\nGenerates a JUnit-compatible XML report after a test run.\n\nUnlike Mocha's `mocha-junit-reporter`, this plugin understands CodeceptJS steps and substeps.\nFor every `<testcase>` it includes:\n\n*   `<properties>` — the test's meta information: every `meta` key from `Scenario('...', { meta })`, plus its `tags` and `retries`\n*   `<system-out>` — an indented step/substep log (substeps are nested under their meta step); only failed steps are marked\n*   `<failure>` — for failed tests: the error message, type, stack trace and (optionally) the step trace\n\nThe produced file is consumable by Jenkins, GitLab CI, CircleCI, GitHub Actions test reporters, etc.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"junitReporter\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `outputName`: file name for the report. Default: `report.xml`.\n*   `output`: directory where the report is stored, relative to the project root. Default: the `output` directory. With `run-workers` and `multiple` runs, each run writes its own report into its run folder (e.g. `output/dev_chromium1`) and this option is ignored.\n*   `testGroupName`: value of the `name` attribute on the root `<testsuites>` element. Default: `CodeceptJS`.\n*   `attachMeta`: add the test's meta information (`meta` keys, `tags`, `retries`) as `<properties>`. Default: true.\n*   `attachSteps`: add the step/substep log as `<system-out>`. Default: true.\n*   `stepsInFailure`: append the step trace to the `<failure>` body. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p junitReporter\n    npx codeceptjs run -p junitReporter:outputName=junit.xml\n\n> ℹ When running with `run-workers`, steps are serialized between processes and substep nesting is flattened.\n\n### Parameters\n\n*   `config` **any**  \n","frontmatter":{"permalink":"/plugins/junit-reporter","editLink":false,"sidebar":"auto","title":"junitReporter"},"isInternal":false,"tokens":494,"sizeBytes":1979},{"name":"pageInfo.md","path":"docs/plugins/pageInfo.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/pageInfo.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/page-info\neditLink: false\nsidebar: auto\ntitle: pageInfo\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pageInfo\n\nCollects information from web page after each failed test and adds it to the test as an artifact.\nIt is suggested to enable this plugin if you run tests on CI and you need to debug failed tests.\nThis plugin can be paired with `analyze` plugin to provide more context.\n\nIt collects URL, HTML errors (by classes), and browser logs.\n\nEnable this plugin in config:\n\n```js\nplugins: {\n pageInfo: {\n  enabled: true,\n}\n```\n\nAdditional config options:\n\n*   `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)\n*   `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/page-info","editLink":false,"sidebar":"auto","title":"pageInfo"},"isInternal":false,"tokens":205,"sizeBytes":879},{"name":"pause.md","path":"docs/plugins/pause.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/pause.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/pause\neditLink: false\nsidebar: auto\ntitle: pause\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pause\n\nPauses test execution interactively. Replaces the legacy `pauseOnFail`\nplugin. The default `on=fail` matches the old `pauseOnFail` behavior.\n\n#### Configuration\n\n```js\nplugins: {\n  pause: {\n    enabled: false,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — pause when a step fails (default). Failures inside `tryTo` and\n    non-final `retryTo` attempts are ignored\n*   **test** — pause after each test\n*   **step** — pause before the first step (interactive walk-through)\n*   **file** — pause when execution reaches `path=...[;line=...]`\n*   **url** — pause when the browser URL matches `pattern=...`\n\nCLI examples:\n\n    npx codeceptjs run -p pause\n    npx codeceptjs run -p pause:on=step\n    npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43\n    npx codeceptjs run -p pause:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/pause","editLink":false,"sidebar":"auto","title":"pause"},"isInternal":false,"tokens":285,"sizeBytes":1058},{"name":"pauseOnFail.md","path":"docs/plugins/pauseOnFail.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/pauseOnFail.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/pause-on-fail\neditLink: false\nsidebar: auto\ntitle: pauseOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pauseOnFail\n\nStarts an interactive pause when a test fails.\n\n**Deprecated:** use the `pause` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/pause-on-fail","editLink":false,"sidebar":"auto","title":"pauseOnFail"},"isInternal":false,"tokens":90,"sizeBytes":374},{"name":"retryFailedStep.md","path":"docs/plugins/retryFailedStep.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/retryFailedStep.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/retry-failed-step\neditLink: false\nsidebar: auto\ntitle: retryFailedStep\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## retryFailedStep\n\nRetries each failed step in a test.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    retryFailedStep: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins retryFailedStep\n\n#### Configuration:\n\n*   `retries` - number of retries (by default 3),\n*   `factor` - The exponential factor to use. Default is 1.5.\n*   `minTimeout` - The number of milliseconds before starting the first retry. Default is 150.\n*   `maxTimeout` - The maximum number of milliseconds between two retries. Default is 10000.\n*   `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.\n*   `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:\n    *   `amOnPage`\n    *   `wait*`\n    *   `send*`\n    *   `execute*`\n    *   `run*`\n    *   `have*`\n*   `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n    To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well.\n*   `deferToScenarioRetries` - when enabled (default), step retries are automatically disabled if scenario retries are configured to avoid excessive total retries.\n\n#### Example\n\n```js\nplugins: {\n    retryFailedStep: {\n        enabled: true,\n        ignoredSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ]\n    }\n}\n```\n\n#### Disable Per Test\n\nThis plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps:\n\nUse scenario configuration to disable plugin for a test\n\n```js\nScenario('scenario tite', { disableRetryFailedStep: true }, () => {\n   // test goes here\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n","frontmatter":{"permalink":"/plugins/retry-failed-step","editLink":false,"sidebar":"auto","title":"retryFailedStep"},"isInternal":false,"tokens":533,"sizeBytes":2137},{"name":"screencast.md","path":"docs/plugins/screencast.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/screencast.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/screencast\neditLink: false\nsidebar: auto\ntitle: screencast\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screencast\n\nRecords a video of tests. Uses Playwright's `page.screencast` API (WebM) when the active\nhelper is Playwright, or raw CDP `Page.startScreencast` (APNG, assembled in-process) when the\nactive helper is `CDPBrowser` or a subclass (`Obscura`, `Kitesurf`, ...). Which path is used is\ndetected automatically per test run; nothing in the config changes between them.\n\nWhen `captions` is enabled, action annotations are burned into the video — Playwright only,\nvia `page.screencast.showActions()`/`showChapter()`; silently absent on the CDP path, since CDP\nscreencast frames are raw, uncomposited page captures with no overlay mechanism. `subtitles`\n(a standalone `.srt`) works identically on both paths, since it's driven by step events, not by\nthe video API. Default `on=fail` keeps videos for failed tests only; `on=test` keeps every\ntest's video.\n\nNote: enabling Playwright's helper-level `video: true` together with this plugin produces two\nindependent recordings (`output/videos/*.webm` from the helper, `output/screencast/*.webm` from\nthis plugin).\n\n#### Configuration\n\n```js\nplugins: {\n  screencast: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — record while running; delete on pass, keep on fail (default)\n*   **test** — record and keep every test's video\n\nOther config options:\n\n*   `captions`: burn-in action overlays via `page.screencast.showActions()`. Playwright only. Default: true.\n*   `subtitles`: also write a standalone `.srt` file alongside the video. Default: false.\n*   `video`: record a video. With `video=false, subtitles=true`, only the `.srt` is produced. Default: true.\n*   `size`: pass-through `{ width, height }` — `screencast.start`'s `size` on Playwright, `maxWidth`/`maxHeight` on the CDP path.\n*   `quality`: pass-through 0–100 for `screencast.start` (Playwright) or CDP `Page.startScreencast` (CDPBrowser family).\n\nCLI examples:\n\n    npx codeceptjs run -p screencast\n    npx codeceptjs run -p screencast:on=test\n    npx codeceptjs run -p screencast:on=test;captions=false;subtitles=true\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/screencast","editLink":false,"sidebar":"auto","title":"screencast"},"isInternal":false,"tokens":621,"sizeBytes":2290},{"name":"screenshot.md","path":"docs/plugins/screenshot.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/screenshot.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/screenshot\neditLink: false\nsidebar: auto\ntitle: screenshot\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshot\n\nSaves screenshots from the browser at points triggered by `on=`.\n\nReplaces the legacy `screenshotOnFail` plugin. Default `on=fail` preserves the\nold behavior (screenshot when a test fails). Pass `slides=true` (with `on=step`)\nto generate a step-by-step slideshow report — replaces the legacy\n`stepByStepReport` plugin.\n\n#### Configuration\n\n```js\nplugins: {\n  screenshot: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — screenshot when a test fails (default)\n*   **test** — screenshot at the end of every test\n*   **step** — screenshot after every step\n*   **file** — screenshot for steps in `path=...[;line=...]`\n*   **url** — screenshot when the current browser URL matches `pattern=...`\n\nOther config options:\n\n*   `uniqueScreenshotNames`: use unique names for screenshot. Default: false.\n*   `fullPageScreenshots`: make full page screenshots. Default: false.\n*   `disableScreenshots`: legacy switch to skip the plugin entirely.\n*   `slides`: generate a step-by-step slideshow report (requires `on=step`). Default: false.\n*   `deleteSuccessful`: when `slides=true`, drop slideshow directories of passing tests. Default: true.\n*   `animateSlides`: when `slides=true`, animate transitions between slides. Default: true.\n*   `ignoreSteps`: when `slides=true`, RegExps of step names to skip in the slideshow.\n\nCLI examples:\n\n    npx codeceptjs run -p screenshot\n    npx codeceptjs run -p screenshot:on=step\n    npx codeceptjs run -p screenshot:on=step;slides=true\n    npx codeceptjs run -p screenshot:on=file:path=tests/login_test.js\n    npx codeceptjs run -p screenshot:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/screenshot","editLink":false,"sidebar":"auto","title":"screenshot"},"isInternal":false,"tokens":462,"sizeBytes":1870},{"name":"screenshotOnFail.md","path":"docs/plugins/screenshotOnFail.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/screenshotOnFail.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/screenshot-on-fail\neditLink: false\nsidebar: auto\ntitle: screenshotOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshotOnFail\n\nSaves a screenshot when a test fails.\n\n**Deprecated:** use the `screenshot` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`   \n","frontmatter":{"permalink":"/plugins/screenshot-on-fail","editLink":false,"sidebar":"auto","title":"screenshotOnFail"},"isInternal":false,"tokens":90,"sizeBytes":385},{"name":"sitemap.md","path":"docs/plugins/sitemap.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/sitemap.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/sitemap\neditLink: false\nsidebar: auto\ntitle: sitemap\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## sitemap\n\nCollects all pages visited while tests are running and saves them as a [sitemap][1] XML file.\n\nThe plugin listens to browser navigation events, so it doesn't slow down test steps.\nPage loads and SPA route changes (`history.pushState`) of the top-level page are recorded, iframes are ignored.\nEvery unique `http(s)` URL is added to the sitemap. URL fragments (`#...`) are dropped.\n\nSupported helpers: Playwright, Puppeteer, and WebDriver with BiDi protocol enabled (default).\nThe sitemap is written once all tests finish.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"sitemap\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `file`: path to the sitemap file, relative to the output directory. Default: `sitemap.xml`, saved as `output/sitemap.xml`.\n*   `stripQuery`: remove query strings so `/posts?page=1` and `/posts?page=2` become one `/posts` entry. Set to `false` to keep them. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p sitemap\n    npx codeceptjs run -p sitemap:file=visited.xml\n    npx codeceptjs run -p sitemap:stripQuery=false\n\nWorks with `run-workers`: URLs from all workers are merged into a single sitemap.\n\n### Parameters\n\n*   `config` **any**  \n\n[1]: https://www.sitemaps.org/protocol.html\n","frontmatter":{"permalink":"/plugins/sitemap","editLink":false,"sidebar":"auto","title":"sitemap"},"isInternal":false,"tokens":355,"sizeBytes":1423},{"name":"stepTimeout.md","path":"docs/plugins/stepTimeout.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/plugins/stepTimeout.md","title":"Plugins Skill","category":"anthropic-skill","format":"markdown","content":"---\npermalink: /plugins/step-timeout\neditLink: false\nsidebar: auto\ntitle: stepTimeout\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## stepTimeout\n\nSet timeout for test steps globally.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    stepTimeout: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins stepTimeout\n\n#### Configuration:\n\n*   `timeout` - global step timeout, default 150 seconds\n\n*   `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false\n\n*   `noTimeoutSteps` - an array of steps with no timeout. Default:\n\n    *   `amOnPage`\n    *   `wait*`\n\n    you could set your own noTimeoutSteps which would replace the default one.\n\n*   `customTimeoutSteps` - an array of step actions with custom timeout. Use it to override or extend noTimeoutSteps.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n\n#### Example\n\n```js\nplugins: {\n    stepTimeout: {\n        enabled: true,\n        overrideStepLimits: true,\n        noTimeoutSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ],\n        customTimeoutSteps: [\n          ['myFlakyStep*', 1],\n          ['scrollWhichRequiresTimeout', 5],\n        ]\n    }\n}\n```\n\n### Parameters\n\n*   `config` &#x20;\n","frontmatter":{"permalink":"/plugins/step-timeout","editLink":false,"sidebar":"auto","title":"stepTimeout"},"isInternal":false,"tokens":365,"sizeBytes":1518},{"name":"api.md","path":"docs/api.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/api.md","title":"docs - API Interface Contract & Specifications","category":"project-spec","format":"markdown","content":"---\npermalink: /api\ntitle: API Testing\n---\n\n## API Testing\n\nCodeceptJS provides a way to write tests in declarative manner for REST and GraphQL APIs. \n\nTake a look:\n\n```js\nI.sendGetRequest('/users/1');\n// returns { \"user\": { \"name\": \"jon\" }, \"projects\": [] }\nI.seeResponseCodeIsSuccessful();\nI.seeResponseContainsKeys(['user', 'projects']);\nI.seeResponseContainsJson({ user: { name: 'jon' } });\nI.seeResponseMatchesJsonSchema($ => {\n  return $.object(\n    user: $.object({\n      name: $.string(),\n    }),\n    projects: $.array()\n  )\n});\n```\nIn this code we checked API request for:\n\n* status code\n* data inclusion\n* data structure\n\nThese are the things you should generally test your APIs for.\n\n> 🤓 It is recommended to check only invariable parts of responses. Check for required fields and only values you control. For instance, it is not recommended to check id fields, date fields, as they can be frequently changed.\n\n## Installation\n\nInstall CodeceptJS if it is not installed yet.\n\n```\nnpm i codeceptjs --save-dev\n```\n\nInitialize CodeceptJS and select REST or GraphQL helper when asked for a helper:\n\n```\nnpx codeceptjs init\n```\n\n## Configuration\n\nEnsure that inside `codecept.conf.js` in helpers section `REST` or `GraphQL` helpers are enabled.\n\n* If you use `REST` helper add `JSONResponse` helper below with no extra config:\n\n```js\n// inside codecept.conf.js\n// ...\n  helpers: {\n    REST: {\n      endpoint: 'http://localhost:3000/api'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {}\n  }\n```\n* If you use `GraphQL` helper add `JSONResponse` helper, configuring it to use GraphQL for requests:\n\n```js\n  helpers: {\n    GraphQL: {\n      endpoint: 'http://localhost:3000/graphql'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {\n      requestHelper: 'GraphQL',\n    }\n  }\n```\n\nOriginally, REST and GraphQL helpers were not designed for API testing. \nThey were used to perform API requests for browser tests. As so, they lack assertion methods to API responses.\n\n[`JSONResponse`](/helpers/json-response/) helper adds response assertions. \n\n> 💡 In CodeceptJS assertions start with `see` prefix. Learn more about assertions by [opening reference for JSONResponse](/helpers/json-response/) helper.\n\nGenerate TypeScript definitions to get auto-completions for JSONResponse:\n\n```\nnpx codeceptjs def\n```\n\nAfter helpers were configured and typings were generated, you can start writing first API test. By default, CodeceptJS saves tests in `tests` directory and uses `*_test.js` suffix. The `init` command created the first test for you to start.\n\n> Check [API Examples](https://github.com/codeceptjs/api-examples) to see tests implementations.\n\n## Requests\n\n[REST](/helpers/rest/) or [GraphQL](/helpers/graph-ql/) helpers implement methods for making API requests.\nBoth helpers send requests via HTTP protocol from CodeceptJS process. \nFor most cases, you will need to have authentication. It can be passed via headers, which can be added to helper's configuration in `codecept.conf.js`. \n\n```js\nhelpers: {\n  REST: {\n    defaultHeaders: {\n      // use Bearer Authorization\n      'Authorization': 'Bearer 11111',\n      'Content-Type': 'application/json',\n      'Accept': 'application/json',\n    },\n  }\n}\n```\n\nOr you can use the browser cookies if you are running browser session.\nIn this case use `setSharedCookies()` from `@codeceptjs/configure` package:\n\n```js\nimport { setSharedCookies } from '@codeceptjs/configure'\n\n// call before exporting config\nsetSharedCookies()\n\nexport const config = {\n  // ...\n  helpers: {  \n    // also works with Playwright or Puppeteer\n    WebDriver: {\n      //... \n    },\n\n    REST: { \n      // ...\n    }\n  }\n}\n```\n\n### REST\n\nREST helper can send GET/POST/PATCH/etc requests to REST API endpoint:\n\n* [`I.sendGetRequest()`](/helpers/rest#sendgetrequest)\n* [`I.sendPostRequest()`](/helpers/rest#sendpostrequest)\n* [`I.sendPutRequest()`](/helpers/rest#sendputrequest)\n* [`I.sendPatchRequest()`](/helpers/rest#sendpatchrequest)\n* [`I.sendDeleteRequest()`](/helpers/rest#senddeleterequest)\n* [`I.sendDeleteRequestWithPayload()`](/helpers/rest#senddeleterequestwithpayload)\n* ...\n\nAuthentication headers can be set in [helper's config](https://codecept.io/helpers/rest/#configuration) or per test with headers or special methods like `I.amBearerAuthenticated`.\n\nExample:\n\n```js\nFeature('Users endpoint')\n\nScenario('create user', ({ I }) => {\n    // this way we pass Bearer token\n  I.amBearerAuthenticated(secret('token-is-here'));\n  // for custom authorization with headers use\n  // I.haveRequestHeaders method\n\n  // here we send a POST request\n  const response = await I.sendPostRequest('/users', {\n    name: 'joe',\n    email: 'joe@mail.com'\n  });\n  // usually we won't need direct access to response object for API testing \n  // but you can obtain it from request\n\n  // check the last request was successful\n  // this method introduced by JSONResponse helper\n  I.seeResponseCodeIsSuccessful();\n})\n```\n\n### GraphQL\n\nGraphQL have request format different then in REST API, but the response format is the same.\nIt's plain old JSON. This why `JSONResponse` helper works for both API types.\nConfigure authorization headers in `codecept.conf.js` and make your first query:  \n\n```js\nFeature('Users endpoint')\n\nScenario('get user by query', ({ I }) => {\n  // make GraphQL query or mutation\n  const resp = await I.sendQuery('{ user(id: 0) { id name email }}');\n  I.seeResponseCodeIsSuccessful();\n\n  // GraphQL always returns key data as part of response\n  I.seeResponseContainsKeys(['data']);\n\n  // check data for partial inclusion\n  I.seeResponseContainsJson({\n    data: {\n      user: {\n        name: 'john doe',\n        email: 'johnd@mutex.com',\n      },\n    },\n  });\n});\n```\n\nGraphQL helper has two methods available:\n\n* [`I.sendQuery()`](/helpers/graph-ql#sendquery)\n* [`I.sendMutation()`](/helpers/graph-ql#sendmutation)\n\n## Assertions\n\n`JSONResponse` provides set of assertions for responses in JSON format. These assertions were designed to check only invariable parts of responses. So instead of checking that response equals to the one provided, we will check for data inclusion and structure matching.\n\nFor most of cases, you won't need to perform assertions by accessing `response` object directly. All assretions are performed under hood inside `JSONResponse` module. It is recommended to keep it that way, to keep tests readable and make test log to contain all assertions.\n\n```js\nScenario('I make API call', ({ I }) => {\n  // request was made by REST \n  // or by GraphQL helper\n\n  // check that response code is 2xx\n  I.seeResponseCodeIsSuccessful();\n\n  // check that response contains keys\n  I.seeResponseContainsKeys(['data', 'pages', 'meta']);\n});\n```\n\n### Response Status Codes\n\n[Response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) can be checked to be equal to some value or to be in a specific range. \nTo check that response code is `200` call `I.seeResponseCodeIs`:\n\n```js\nI.seeResponseCodeIs(200);\n```\nBut because other response codes in 2xx range are also valid responses, you can use `seeResponseCodeIsSuccessful()` which will match 200 (OK), 201 (Created), 206 (Partial Content) and others. Methods to check 3xx, 4xx, 5xx response statuses also available.\n\n```js\n// matches 200, 201, 202, ... 206\nI.seeResponseCodeIsSuccessful();\n\n// matches 300...308\nI.seeResponseCodeIsRedirection();\n\n// matches 400..451\nI.seeResponseCodeIsClientError();\n\n// matches 500-511\nI.seeResponseCodeIsServerError();\n```\n\n### Structure\n\nThe most basic thing to check in response is existence of keys in JSON object. Use [`I.seeResponseContainsKeys()`](/helpers/json-response#seeresponsecontainskeys) method for it:\n\n```js\n// response is { \"name\": \"joe\", \"email\": \"joe@joe.com\" }\nI.seeResponseContainsKeys(['name', 'email']);\n```\n\n> ℹ️ If response is an array, it will check that every element in array have provided keys\n\nHowever, this is a very naive approach. It won't work for arrays or nested objects.\nTo check complex JSON structures `JSONResponse` helper uses [`Zod`](https://zod.dev) library.\nIt has rich API to validate JSON by the schema defined using JavaScript.\n\n```js\n// import zod library,\n// it is installed with CodeceptJS\nimport { z } from 'zod';\n\n// create schema definition using Zod API\nconst schema = z.object({\n  email: z.string().email(),\n  phone: z.string().regex(/^\\d{3}-\\d{3}-\\d{4}$/),\n  birthday: z.string().datetime().max(new Date('2004-01-01'))\n});\n\n// check that response matches that schema\nI.seeResponseMatchesJsonSchema(schema);\n```\n\n> 📋 **Migration Note**: CodeceptJS has migrated from Joi to Zod v4 for JSON schema validation.\n> If you have existing tests using Joi, please update them:\n> * Replace `const Joi = require('joi')` with `import { z } from 'zod'`\n> * Replace `Joi.object().keys({...})` with `z.object({...})`\n> * Replace `Joi.string().email()` with `z.string().email()`\n> * Replace `Joi.date()` with appropriate `z.string()` or `z.date()` types\n> * See [Zod documentation](https://zod.dev) for complete API reference\n\n### Data Inclusion\n\nTo check that response contains expected data use `I.seeResponseContainsJson` method. \nIt will check the response data for partial match. \n\n```js\nI.seeResponseContainsJson({\n  user: {\n    email: 'user@user.com'\n  }\n})\n```\n\n> ℹ️ If response is an array, it will check that at least one element in array matches JSON\n\nTo perform arbitrary assertions on a response object use `seeResponseValidByCallback`. \nIt allows you to do any kind of assertions by using `expect` from [`chai`](https://www.chaijs.com) library.\n\n```js\nI.seeResponseValidByCallback(({ data, status, expect }) => {\n  // we receive data and expect to combine them for good assertion\n  expect(data.users.length).to.be.gte(10);\n})\n```\n\n## Extending JSONResponse\n\nTo add more assertions it is recommended to create a custom helper.\nInside it you can get access to latest JSON response:\n\n```js\n// inside a custom helper\nmakeSomeCustomAssertion() {\n  const response = this.helpers.JSONResponse.response;\n}\n```","frontmatter":{"permalink":"/api","title":"API Testing"},"isInternal":false,"tokens":2479,"sizeBytes":10051},{"name":"architecture.md","path":"docs/architecture.md","rawUrl":"https://raw.githubusercontent.com/codeceptjs/CodeceptJS/HEAD/docs/architecture.md","title":"docs - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"---\npermalink: /architecture\ntitle: Architecture\n---\n\n# CodeceptJS Architecture\n\nHow CodeceptJS runs a test, and the internal modules you build [plugins, listeners, and helpers](/hooks) against.\n\n## How a Test Runs\n\nCodeceptJS is built on top of [Mocha](https://mochajs.org). A run goes through these stages:\n\n1. **Load.** CodeceptJS reads the config, builds the [container](#container) (helpers, support objects, plugins), and runs the `bootstrap` hook. `event.all.before` fires.\n2. **Suite.** For each suite, `event.suite.before` fires. Helper `_beforeSuite` hooks run.\n3. **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.\n4. **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`.\n5. **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.\n\nThe 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.\n\n## The Internal API\n\nCodeceptJS exposes its internals as named exports of the `codeceptjs` package. Import only what you need:\n\n```js\nimport { recorder, event, output, container, config } from 'codeceptjs'\n```\n\n| Export | What it is |\n| --- | --- |\n| [`codecept`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/codecept.js) | the test runner class |\n| [`config`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/config.js) | the loaded configuration |\n| [`container`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/container.js) | dependency-injection container: helpers, support objects, plugins, the Mocha instance |\n| [`recorder`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/recorder.js) | the global promise chain that orders every step |\n| [`event`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/event.js) | the event dispatcher and the names of all lifecycle events |\n| [`output`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/output.js) | the printer used for all console output |\n| [`store`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/store.js) | global state of the run — current test/step, run modes, directories |\n| [`helper`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/helper.js) | the base class every helper extends |\n| [`actor`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/actor.js) | the base class behind the `I` object |\n\n> 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.\n\nThe [API reference](https://github.com/codeceptjs/CodeceptJS/tree/master/docs/api) on GitHub documents these modules; the source is the final word.\n\n## The Recorder\n\nThe 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:\n\n```js\nimport { event, recorder } from 'codeceptjs'\n\nevent.dispatcher.on(event.test.before, () => {\n  recorder.add('seed fixture data', async () => {\n    await api.post('/users', { name: 'john', email: 'john@example.com' })\n  })\n})\n```\n\n- `recorder.add(name, fn)` — append `fn` (async, or returning a promise) to the chain. The name shows up in `--verbose` output.\n- `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`.\n- `recorder.retry({ retries, when })` — retry failing steps that match `when`. See [conditional retries](/helpers#conditional-retries).\n\nRun tests with `--verbose` to watch the recorder schedule and execute each entry.\n\n## Container\n\nThe container resolves helpers and support objects by name:\n\n```js\nimport { container } from 'codeceptjs'\n\nconst helpers = container.helpers()          // every helper, keyed by name\nconst { Playwright } = container.helpers()   // one helper\nconst support = container.support()          // every support object\nconst { UserPage } = container.support()     // one page object\nconst plugins = container.plugins()          // enabled plugins\nconst mocha = container.mocha()              // the current Mocha instance\n```\n\nAdd objects at runtime — useful from a `bootstrap` script:\n\n```js\nimport { container } from 'codeceptjs'\nimport UserPage from './pages/user.js'\n\ncontainer.append({\n  helpers: { MyHelper: new MyHelper({ host: 'http://example.com' }) },\n  support: { UserPage },\n})\n```\n\n## Events\n\n`event.dispatcher` is a Node `EventEmitter`. Attach listeners to it from a [plugin](/hooks#plugins) or `bootstrap` script.\n\nEvents are **sync** or **async**:\n\n- **sync** — fires the moment the action happens. Do synchronous work only.\n- **async** — fires when the action is *scheduled*. To do async work in the right order, queue it with `recorder.add()`.\n\n| Event | Kind | When |\n| --- | --- | --- |\n| `event.all.before` | — | before any test runs |\n| `event.suite.before(suite)` | async | before a suite |\n| `event.test.started(test)` | sync | at the very start of a test |\n| `event.test.before(test)` | async | after `Before` hooks from helpers and the test are run |\n| `event.test.passed(test)` | sync | test passed |\n| `event.test.failed(test, err)` | sync | test failed |\n| `event.test.skipped(test)` | sync | test skipped |\n| `event.test.after(test)` | async | after each test |\n| `event.test.finished(test)` | sync | test finished |\n| `event.suite.after(suite)` | async | after a suite |\n| `event.step.before(step)` | async | step scheduled for execution |\n| `event.step.started(step)` | sync | step starts executing |\n| `event.step.passed(step)` | sync | step passed |\n| `event.step.failed(step, err)` | sync | step failed |\n| `event.step.after(step)` | async | after a step |\n| `event.step.finished(step)` | sync | step finished |\n| `event.step.comment(step)` | sync | a comment such as `I.say(...)` |\n| `event.bddStep.before(step)` / `event.bddStep.after(step)` | async | around a Gherkin step |\n| `event.hook.started(hook)` / `event.hook.passed` / `event.hook.failed` / `event.hook.finished` | sync | around `Before` / `After` / `BeforeSuite` / `AfterSuite` hooks |\n| `event.all.after` | — | after all tests |\n| `event.all.result(result)` | — | when results are printed |\n| `event.all.failures(failures)` | — | when a run reports failures |\n| `event.workers.before` / `event.workers.after` / `event.workers.result(result)` | — | around a [parallel run](/parallel) (parent process only) |\n\nThe [built-in listeners](https://github.com/codeceptjs/CodeceptJS/tree/master/lib/listener) are working examples — every reporter and several plugins are listeners.\n\n### Test object\n\nTest events pass a test object with these fields:\n\n- `title` — the test title\n- `body` — the test function as a string\n- `opts` — test options such as `retries` (see [test options](/advanced#test-options))\n- `pending` — `true` while scheduled, `false` once finished\n- `tags` — array of [tags](/test-structure#tags) for this test\n- `artifacts` — files attached to this test (screenshots, videos, …), shared across reporters\n- `file` — path to the test file\n- `steps` — executed steps (only on `test.passed`, `test.failed`, `test.finished`)\n- `skipInfo` — present when the test was skipped: `{ message, description }`\n\n### Step object\n\nStep events pass a step object with these fields:\n\n- `name` — the step name, such as `see` or `click`\n- `actor` — the current actor, usually `I`\n- `helper` — the helper instance that executes this step\n- `helperMethod` — the helper method, usually the same as `name`\n- `status` — `passed` or `failed`\n- `prefix` — for a step inside a `within` block, the within text (e.g. `Within .js-signup-form`)\n- `args` — the arguments passed to the step\n\n## Config\n\n```js\nimport { config } from 'codeceptjs'\n\nconfig.get()                       // the full config object\nconfig.get('myKey')                // one value\nconfig.get('myKey', 'fallback')    // one value, with a default\n```\n\n## Output\n\nOutput has four verbosity levels, each toggled by a CLI flag:\n\n| Level | Flag | Use |\n| --- | --- | --- |\n| default | — | `output.print` — basic information |\n| steps | `--steps` | step execution |\n| debug | `--debug` | steps plus `output.debug` |\n| verbose | `--verbose` | debug plus `output.log` (internal logs and recorder activity) |\n\n```js\nimport { output } from 'codeceptjs'\n\noutput.print('basic information')\noutput.debug('debug information')\noutput.log('verbose logging information')\n```\n\nUse these instead of `console.log` so messages respect the chosen verbosity.\n\n## Store\n\n`store` holds the state of the current run — the executing test, suite, and step, the active run modes (`dryRun`, `debugMode`, `workerMode`, …), and the project directories. Listeners, plugins, and helpers read it to know where in the [lifecycle](#events) they are without that information being passed to them:\n\n```js\nimport { store } from 'codeceptjs'\n\nevent.dispatcher.on(event.step.before, () => {\n  if (store.dryRun) return                    // no side effects on a dry run\n  output.debug(`in ${store.currentTest?.title}`)\n})\n```\n\nCodeceptJS keeps the state fields up to date for you. See the [Store reference](/store) for every field and when to write to it.\n\n## Helpers and the Actor\n\nThe `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).\n\n## Running CodeceptJS from Code\n\nCodeceptJS can be driven from your own script. Create the runner with a config and options, initialize it, then bootstrap, load tests, and run:\n\n```js\nimport { codecept as Codecept } from 'codeceptjs'\n\nconst config = { helpers: { Playwright: { browser: 'chromium', url: 'http://localhost' } } }\nconst opts = { steps: true }\n\nconst codecept = new Codecept(config, opts)\ncodecept.init(import.meta.dirname)   // the test root directory\n\ntry {\n  await codecept.bootstrap()\n  codecept.loadTests('**/*_test.js')\n  await codecept.run()               // pass a test file path to run only that file\n} catch (err) {\n  console.error(err)\n  process.exitCode = 1\n} finally {\n  await codecept.teardown()\n}\n```\n\n> To run tests inside workers from a script, see [parallel execution](/parallel).\n\n---\n\n**See also:** [Extending CodeceptJS](/hooks) · [Custom Helpers](/helpers) · [Plugins](/plugins) · [Bootstrap & Teardown](/bootstrap)\n","frontmatter":{"permalink":"/architecture","title":"Architecture"},"isInternal":false,"tokens":2834,"sizeBytes":11344}],"systemPromptSnippet":"<yakaai_skills repo=\"codeceptjs/CodeceptJS\">\n<!-- File: docs/agents.md (Tokens: ~2996 | Category: root-instruction) -->\n---\npermalink: /agents\ntitle: Agentic Testing\n---\n\n# Agentic Testing\n\n## What Makes CodeceptJS Agent-friendly\n\nCodeceptJS 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.\n\nAgents get full control over test and browser execution: \n\n- **Full HTML access and ARIA snapshots.** Agents see buttons with icons, empty labels, and other elements Playwright MCP's accessibility tree omits.\n\n- **Query HTML directly with CSS, ARIA, Semantic Locators or XPath** Agents can freely run XPath or CSS to efficiently search over HTML.\n\n- **Browser logs and page state as files.** Agents read via native filesystem tools instead of extra requests, eliminating redundant context dumps.\n\n- **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.\n\n- **Testing-first framework, not browser control.** CodeceptJS is built for testing, so agents stay focused on test scenarios instead of raw browser commands.\n\nCodeceptJS 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.\n\n## Essential Setup\n\nTwo things make agent testing work: the **skills** that teach the agent CodeceptJS, and the **MCP server** that lets it drive the browser. Set both up once, from your project directory:\n\n```bash\nnpx codeceptjs init:agent claude\n```\n\nUse `codex`, `cursor`, or `opencode` instead of `claude` for other agents, or omit the name to pick from a list. `npx codeceptjs init` offers the same step at the end.\n\n### Manual setup\n\nInstall the skills:\n\n```bash\nnpx skills add codeceptjs/skills\n```\n\nConnect the MCP server (`npx codeceptjs-mcp`).\n\n**Claude Code:**\n\n```bash\nclaude mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Codex:**\n\n```bash\ncodex mcp add codeceptjs -- npx codeceptjs-mcp\n```\n\n**Cursor** — add to `.cursor/mcp.json`:\n\n```json\n{ \"mcpServers\": { \"codeceptjs\": { \"command\": \"npx\", \"args\": [\"codeceptjs-mcp\"] } } }\n```\n\nSee [/mcp](/mcp) for full client setup. Now the agent is ready to run the loop.\n\n## The loop\n\nWhether the agent is writing a new test or fixing an old one, it follows the same cycle.\n\n1. **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.\n2. **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.\n3. **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.\n4. **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.\n5. **Move forward.** The agent looks at the new state and chooses the next command. Steps 2–4 repeat until the scenario is whole.\n6. **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.\n\n\n## How It Works\n\nCodeceptJS 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.\n\n## Why MCP\n\nThe traditional agent testing loop is test/fix/retry, where the agent executes a test, watches it fail, reads artifacts, performs code fixes, and reruns the test. The agent applies fixes by intelligent guess — looking at the ARIA tree, HTML, and screenshot — then assumes the fix is enough and reruns the test hoping it will pass. If the guess is wrong and the test runs for over a minute, it may take dozens of minutes of iteration and a lot of wasted tokens.\n\nTo improve that flow, the agent can spawn a browser and open the page the way the test does. This lets it interact with the page more freely and perform multi-step actions. But putting that experience back into test code is not efficient either: actions executed in the browser may not be relevant in test context, so the agent ends up in another guess-and-try loop.\n\nThe problem is that **the test runs in a different context than the agent**.\n\nThe agent can launch a test but can't control it while it's running. It can't access the browser. It can't set a breakpoint.\n\nThis is where CodeceptJS MCP steps in. Connected to the agent, it can:\n\n- run a test and pause it on failure\n- interact with the browser in a test context\n- test locators and perform actions live while the test is running\n- write successful actions to the test file\n\nThis lets the agent get a test working in one iteration. The agent can live-write the test before your eyes by exploring the page and performing actions that eventually land in the CodeceptJS test file.\n\n**Live debugging of tests** is what CodeceptJS MCP provides. The agent receives feedback faster — not from a whole test execution but from specific actions on a specific page — so it can adjust and react faster, trying different approaches.\n\nThe 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).\n\n\n## How the agent reads the page\n\nMCP commands are token efficient — they don't stream large HTML pages back to the model. MCP writes artifacts to disk under `output/trace_*/` and returns file paths. The agent reads each artifact with its own bash tools — `cat`, `grep`, `jq`.\n\nA `run_code` response, for example, looks like this:\n\n```json\n{\n  \"status\": \"success\",\n  \"artifacts\": {\n    \"url\": \"http://localhost:8000/\",\n    \"html\": \"file:///output/trace_run_code_.../mcp_page.html\",\n    \"aria\": \"file:///output/trace_run_code_.../mcp_aria.txt\",\n    \"screenshot\": \"file:///output/trace_run_code_.../mcp_screenshot.png\",\n    \"console\": \"file:///output/trace_run_code_.../mcp_console.json\",\n    \"storage\": \"file:///output/trace_run_code_.../mcp_storage.json\"\n  }\n}\n```\n\nOnly `url` is inline. The rest are paths the agent opens with the right tool:\n\n| Artifact | How the agent reads it |\n|----------|------------------------|\n| `*_screenshot.png` | As an image — most agents are multimodal |\n| `*_aria.txt` | Whole — small and structured |\n| `*_page.html` | With `grep` — too large for context, searchable for specific elements/attributes |\n| `*_console.json` | With `jq` — filter for errors, 4xx/5xx, deprecation warnings |\n| `*_storage.json` | Whole — cookies and `localStorage` snapshot |\n| `trace.md` | Whole — markdown index linking every step to its artifacts |\n\nSaved HTML is formatted, with non-semantic elements stripped out: `<style>`, `<script>`, Tailwind-style trash classes, and inline `style=\"\"` attributes. `grep` can then effectively find the correct tree branch in raw page source. ARIA snapshots are smaller and more structured than HTML, which is why the agent prefers them when picking locators.\n\n## Usage Examples\n\nWhen MCP and skills are connected, the agent receives predefined workflows and can act effectively for testing purposes. Common scenarios it handles:\n\n### Writing a new test\n\nYou ask: \"Add a test for the checkout flow.\"\n\nThe agent writes a stub:\n\n```javascript\nScenario('checkout', ({ I }) => {\n  I.amOnPage('/cart')\n  pause()\n})\n```\n\nIt runs the stub. The browser opens at `/cart` and yields control at `pause()`. The agent reads the ARIA tree, runs `I.click('Add to cart')`, sees the cart total update — that line goes into the verified sequence. It runs `I.fillField('Email', '...')`, sees the field accept the value, records it. Through `I.click('Continue to payment')`, `I.see('Payment')`, `I.fillField('Card', secret(process.env.TEST_CARD))`, `I.click('Pay')`, `I.see('Order confirmed')` — each command commits only after the response confirms it worked.\n\nWhen the scenario is whole, the agent edits the test file: replaces `pause()` with the verified sequence, renames the scenario, wraps credentials with `secret()`. It reruns the file end-to-end with `aiTrace` on and hands you the diff.\n\n### Fixing a failing test\n\nA test fails. You point the agent at the scenario.\n\nIt opens `output/trace_<TestName>_*/trace.md` from the last run, reads the steps, and finds the one marked failed. Most of the time the screenshot and ARIA from that step explain the cause — \"Save\" is now \"Save changes,\" or a spinner is gating the next action. The agent patches the line and reruns.\n\nWhen the trace doesn't say enough, the agent passes a step number to `run_test` so the test pauses right before the failing step. From the live page, it tries `I.click({ role: 'button', name: 'Save changes' })`, sees the modal close. Or `I.waitForInvisible('.spinner', 10)` followed by the original click — watches it pass. Whatever holds goes into the test.\n\nThe fix lands with a one-line note explaining what changed.\n\n### Auto-fixing on CI\n\nAfter a failed run, the agent reads every trace under `output/`, clusters failures by signature, and patches what fits a small set of safe fixes (locator drift, missing waits, raw `I.wait(N)` replacement). It reruns only the failing scenarios, compares against the baseline, and writes a markdown report at `output/ci-fix.md`.\n\nIf the fix held, the PR goes green. If it didn't, every edit is rolled back with `git checkout` and the report says which patterns the agent couldn't safely handle. No half-applied fixes left behind, no `retries: 3` masking the problem.\n\n## Assertions in plain language\n\nSome outcomes are hard to pin to a locator: \"checkout form has all required fields\", \"success message is shown\". Instead of building a fragile chain of `see*` checks, the agent can write the expected outcome as a statement with the [Decision helper](/assertions#decision-assertions):\n\n```js\nI.decide([\n  'order summary lists the purchased items',\n  'success message is shown',\n])\n```\n\nThe agent runs the statement on the live page like any other command and keeps it only if it passes. The test then checks it on every run.\n\nTo let the agent use decisions, [configure a decision model](/ai#configure-decision-models) and enable the Decision helper.\n\nDecision models like [Jev](https://openrouter.ai/typesafe/jev-1.13) are built for this. They answer in a fraction of a second, cost a fraction of a cent per request, and return a probability instead of free text. That makes them fast and cheap enough to run on every CI build, and predictable enough to keep in a test.\n\n## Skills bundle\n\nSkills teach the agent best practices for using CodeceptJS. Plug them in when you develop tests with agents, and update them regularly to ensure you use CodeceptJS in the most effective way.\n\n| Skill | Use case |\n|-------|----------|\n| `writing-codeceptjs-tests` | Author or extend a scenario. Runs the loop above with a stub-and-pause flow for greenfield work, incremental `run_code` for known flows. |\n| `debugging-codeceptjs-tests` | A test is failing or flaky. Reads the trace, decides whether to patch from the trace alone or set a breakpoint on the live page. |\n| `ci-fix-tests` | Conservative auto-repair on CI |\n| `refactoring-codeceptjs-tests` | Extract page objects, tame long locators, move raw browser code into helpers. Proposes in batches. |\n| `codeceptjs-fundamentals` | Obtain actual CodeceptJS knowledge. |\n| `codeceptjs-exploration` | Pick a stable locator from messy markup. |\n| `codeceptjs-run-analysis` | Read trace artifacts, cluster CI failures into root causes, verify a fix held across many traces. |\n| `codeceptjs-auth` | Authorize efficiently with `auth` plugin. |\n\n## Pointers\n\n- [/mcp](/mcp) — full MCP tool reference, client setup\n- [/aitrace](/aitrace) — trace plugin configuration and capture options\n- [/debugging](/debugging) — pause modes, IDE setup, the `pause` plugin\n- [skills repo](https://github.com/codeceptjs/skills) — source and install for non-Claude clients\n\n<!-- File: CLAUDE.md (Tokens: ~383 | Category: claude-rule) -->\n# CodeceptJS ESM Migration Plan\n\nThis project is migrating from 3.x to ESM replacing all require() calls with imports.\n\n## Compare with Reference\n\nEach time unsure what to do refer to the same file implementation in **3.x branch**\n3.x branch contains stable version of all core classes that work\n\n## Running Tests\n\nFocus on acceptance tests:\n\nFor instance this helps to understand if final migration is ok:\n\n```\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n\nDEBUG=\"codeceptjs:*\" ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --debug --grep within\n```\n\nDo not say: it works befire running specific acceptance test.\nTests may stuck so always run them with timeout call:\n\n```\ntimeout=30000 ./bin/codecept.js run --config test/acceptance/codecept.Playwright.js --verbose\n```\n\nWeb Server for this tests are running on port 8000 and it works but responds 500 for HEAD requests.\n\n## Princinples\n\nAll unit tests for Playwright/Puppeteer/WebdriverIO are passing. But acceptance tests are failing due to promises composition. Complexity of promises composition comes from promise chaining and async/await syntax. And `session` mechanism from `promise.js` which allows spawning different promises chains and sync them up in the end.\n\nThe full test suite extensively uses plugins so refer to plugins and corresponding helpers as well\n\nWe are building test framework. So even passing ests are not indicator of successful usage. Side effects like: timeouts, bad output, errors, memory leaks, etc are affecting performance.\nFor instance, if a tests stuck and won't finish this is very bad.\n\n## Coding Style\n\n- Never add comments unless explicitly required.\n- DO not mention plugins or helpers inside core classes\n\n<!-- File: docs/plugins/aiTrace.md (Tokens: ~414 | Category: anthropic-skill) -->\n---\npermalink: /plugins/ai-trace\neditLink: false\nsidebar: auto\ntitle: aiTrace\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## aiTrace\n\nGenerates AI-friendly trace files for debugging with AI agents.\nThis plugin creates a markdown file with test execution logs and links to all artifacts\n(screenshots, HTML, ARIA snapshots, browser logs, HTTP requests) for each step.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"aiTrace\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `deleteSuccessful`: delete traces for successfully executed tests. Default: false.\n*   `fullPageScreenshots`: should full page screenshots be used. Default: false.\n*   `output`: a directory where traces should be stored. Default: `output`.\n*   `captureHTML`: capture HTML for each step. Default: true.\n*   `captureARIA`: capture ARIA snapshot for each step. Default: true.\n*   `captureBrowserLogs`: capture browser console logs. Default: true.\n*   `captureHTTP`: capture HTTP requests (requires `trace` or `recordHar` enabled in helper config). Default: true.\n*   `captureDebugOutput`: capture CodeceptJS debug output. Default: true.\n*   `ignoreSteps`: steps to ignore in trace. Array of RegExps is expected.\n*   `on`: trigger mode — `step` (default), `fail`, `test`, `file`, `url`.\n\n#### `on=` modes\n\n*   **step** — persist every step (default)\n*   **fail** — persist only the failed step\n*   **test** — persist only the last step of each test\n*   **file** — persist steps from `path=...[;line=...]`\n*   **url** — persist when the current URL matches `pattern=...`\n\n### Parameters\n\n*   `config` **any**\n\n<!-- File: docs/plugins/analyze.md (Tokens: ~469 | Category: anthropic-skill) -->\n---\npermalink: /plugins/analyze\neditLink: false\nsidebar: auto\ntitle: analyze\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## analyze\n\nUses AI to analyze test failures and provide insights\n\nThis plugin analyzes failed tests using AI to provide detailed explanations and group similar failures.\nWhen enabled with --ai flag, it generates reports after test execution.\n\n#### Usage\n\n```js\n// in codecept.conf.js\nexports.config = {\n  plugins: {\n    analyze: {\n      enabled: true,\n      clusterize: 5,\n      analyze: 2,\n      vision: false\n    }\n  }\n}\n```\n\n#### Configuration\n\n*   `clusterize` (number) - minimum number of failures to trigger clustering analysis. Default: 5\n*   `analyze` (number) - maximum number of individual test failures to analyze in detail. Default: 2\n*   `vision` (boolean) - enables visual analysis of test screenshots. Default: false\n*   `categories` (array) - list of failure categories for classification. Defaults to:\n    *   Browser connection error / browser crash\n    *   Network errors (server error, timeout, etc)\n    *   HTML / page elements (not found, not visible, etc)\n    *   Navigation errors (404, etc)\n    *   Code errors (syntax error, JS errors, etc)\n    *   Library & framework errors\n    *   Data errors (password incorrect, invalid format, etc)\n    *   Assertion failures\n    *   Other errors\n*   `prompts` (object) - customize AI prompts for analysis\n    *   `clusterize` - prompt for clustering analysis\n    *   `analyze` - prompt for individual test analysis\n\n#### Features\n\n*   Groups similar failures when number of failures >= clusterize value\n*   Provides detailed analysis of individual failures\n*   Analyzes screenshots if vision=true and screenshots are available\n*   Classifies failures into predefined categories\n*   Suggests possible causes and solutions\n\n### Parameters\n\n*   `config` **[Object][1]** Plugin configuration \n\nReturns **void**&#x20;\n\n[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object\n\n<!-- File: docs/plugins/auth.md (Tokens: ~1607 | Category: anthropic-skill) -->\n---\npermalink: /plugins/auth\neditLink: false\nsidebar: auto\ntitle: auth\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## auth\n\nLogs user in for the first test and reuses session for next tests.\nWorks by saving cookies into memory or file.\nIf a session expires automatically logs in again.\n\n> For better development experience cookies can be saved into file, so a session can be reused while writing tests.\n\n#### Usage\n\n1.  Enable this plugin and configure as described below\n2.  Define user session names (example: `user`, `editor`, `admin`, etc).\n3.  Define how users are logged in and how to check that user is logged in\n4.  Use `login` object inside your tests to log in:\n\n```js\n// inside a test file\n// use login to inject auto-login function\nFeature('Login');\n\nBefore(({ login }) => {\n   login('user'); // login using user session\n});\n\n// Alternatively log in for one scenario.\nScenario('log me in', ( { I, login } ) => {\n   login('admin');\n   I.see('I am logged in');\n});\n```\n\n#### Configuration\n\n*   `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.\n*   `inject` (default: `login`) - name of the login function to use\n*   `users` - an array containing different session names and functions to:\n    *   `login` - sign in into the system\n    *   `check` - check that user is logged in\n    *   `fetch` - to get current cookies (by default `I.grabCookie()`)\n    *   `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)\n\n#### How It Works\n\n1.  `restore` method is executed. It should open a page and set credentials.\n2.  `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged-in user. When you pass the second args `session`, you could perform the validation using passed session.\n3.  If `restore` and `check` were not successful, `login` is executed\n4.  `login` should fill in login form\n5.  After successful login, `fetch` is executed to save cookies into memory or file.\n\n#### Example: Simple login\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      // loginAdmin function is defined in `steps_file.js`\n      login: (I) => I.loginAdmin(),\n      // if we see `Admin` on page, we assume we are logged in\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      }\n    }\n  }\n}\n```\n\n#### Example: Multiple users\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'loginAs', // use `loginAs` instead of login\n  users: {\n    user: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'user@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('User', '.navbar');\n      },\n    },\n    admin: {\n      login: (I) => {\n         I.amOnPage('/login');\n         I.fillField('email', 'admin@site.com');\n         I.fillField('password', '123456');\n         I.click('Login');\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin', '.navbar');\n      },\n    },\n  }\n}\n```\n\n#### Example: Keep cookies between tests\n\nIf you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.\nBut you need to login once work until session expires.\nFor this case, disable `fetch` and `restore` methods.\n\n```js\nhelpers: {\n   WebDriver: {\n     // config goes here\n     keepCookies: true; // keep cookies for all tests\n   }\n},\nplugins: {\n   auth: {\n     users: {\n       admin: {\n         login: (I) => {\n           I.amOnPage('/login');\n           I.fillField('email', 'admin@site.com');\n           I.fillField('password', '123456');\n           I.click('Login');\n         },\n         check: (I) => {\n           I.amOnPage('/dashboard');\n           I.see('Admin', '.navbar');\n         },\n         fetch: () => {}, // empty function\n         restore: () => {}, // empty funciton\n       }\n    }\n  }\n}\n```\n\n#### Example: Getting sessions from local storage\n\nIf your session is stored in local storage instead of cookies you still can obtain sessions.\n\n```js\nplugins: {\n   auth: {\n    admin: {\n      login: (I) => I.loginAsAdmin(),\n      check: (I) => I.see('Admin', '.navbar'),\n      fetch: (I) => {\n        return I.executeScript(() => localStorage.getItem('session_id'));\n      },\n      restore: (I, session) => {\n        I.amOnPage('/');\n        I.executeScript((session) => localStorage.setItem('session_id', session), session);\n      },\n    }\n  }\n}\n```\n\n#### Tips: Using async function in the auth\n\nIf you use async functions in the auth plugin, login function should be used with `await` keyword.\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I) => {\n         I.amOnPage('/');\n         I.see('Admin');\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n#### Tips: Using session to validate user\n\nInstead of asserting on page elements for the current user in `check`, you can use the `session` you saved in `fetch`\n\n```js\nauth: {\n  enabled: true,\n  saveToFile: true,\n  inject: 'login',\n  users: {\n    admin: {\n      login: async (I) => {  // If you use async function in the auth plugin\n         const phrase = await I.grabTextFrom('#phrase')\n         I.fillField('username', 'admin'),\n         I.fillField('password', 'password')\n         I.fillField('phrase', phrase)\n      },\n      check: (I, session) => {\n         // Throwing an error in `check` will make CodeceptJS perform the login step for the user\n         if (session.profile.email !== the.email.you.expect@some-mail.com) {\n              throw new Error ('Wrong user signed in');\n        }\n      },\n    }\n  }\n}\n```\n\n```js\nScenario('login', async ( {I, login} ) => {\n  await login('admin') // you should use `await`\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n<!-- File: docs/plugins/autoDelay.md (Tokens: ~277 | Category: anthropic-skill) -->\n---\npermalink: /plugins/auto-delay\neditLink: false\nsidebar: auto\ntitle: autoDelay\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## autoDelay\n\nSometimes it takes some time for a page to respond to user's actions.\nDepending on app's performance this can be either slow or fast.\n\nFor instance, if you click a button and nothing happens - probably JS event is not attached to this button yet\nAlso, if you fill field and input validation doesn't accept your input - maybe because you typed value too fast.\n\nThis plugin allows to slow down tests execution when a test running too fast.\nIt puts a tiny delay for before and after action commands.\n\nCommands affected (by default):\n\n*   `click`\n*   `fillField`\n*   `checkOption`\n*   `pressKey`\n*   `doubleClick`\n*   `rightClick`\n\n#### Configuration\n\n```js\nplugins: {\n   autoDelay: {\n     enabled: true\n   }\n}\n```\n\nPossible config options:\n\n*   `methods`: list of affected commands. Can be overridden\n*   `delayBefore`: put a delay before a command. 100ms by default\n*   `delayAfter`: put a delay after a command. 200ms by default\n\n### Parameters\n\n*   `config` &#x20;\n\n<!-- File: docs/plugins/browser.md (Tokens: ~401 | Category: anthropic-skill) -->\n---\npermalink: /plugins/browser\neditLink: false\nsidebar: auto\ntitle: browser\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## browser\n\nOverrides browser helper config from the command line. Works for all browser helpers\n(Playwright, Puppeteer, WebDriver, Appium) without touching `codecept.conf`.\n\nEnable it via `-p` option with one or more colon-chained args:\n\n    npx codeceptjs run -p browser:show\n    npx codeceptjs run -p browser:hide\n    npx codeceptjs run -p browser:browser=firefox\n    npx codeceptjs run -p browser:windowSize=1024x768:video=false\n    npx codeceptjs run -p browser:hide:browser=webkit:windowSize=800x600\n\n#### Args\n\n*   **show** — force visible browser\n*   **hide** — force headless (also injects `--headless` into WebDriver chrome/firefox capability args)\n*   **`<key>=<value>`** — set `helpers.<eachBrowserHelper>.<key> = <value>`. Three keys\n    get per-helper translation via `setBrowserConfig`:\n    *   `browser=<name>` — Puppeteer receives `product`, Playwright receives `browser`\n    *   `windowSize=WxH` — also adds `--window-size=W,H` chromium/chrome args\n    *   `show=true|false` — toggles `show` on Playwright/Puppeteer; injects/strips\n        `--headless` in WebDriver chrome/firefox capability args\n\nValues stay as strings. `true` / `false` are coerced to booleans.\n\nRequires `@codeceptjs/configure` to be installed; if missing, the plugin\nlogs a hint and skips the override.\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/coverage.md (Tokens: ~220 | Category: anthropic-skill) -->\n---\npermalink: /plugins/coverage\neditLink: false\nsidebar: auto\ntitle: coverage\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## coverage\n\nDumps code coverage from Playwright/Puppeteer after every test.\n\n#### Configuration\n\n```js\nplugins: {\n   coverage: {\n     enabled: true,\n     debug: true,\n     name: 'CodeceptJS Coverage Report',\n     outputDir: 'output/coverage'\n   }\n}\n```\n\nPossible config options, More could be found at [monocart-coverage-reports][1]\n\n*   `debug`: debug info. By default, false.\n*   `name`: coverage report name.\n*   `outputDir`: path to coverage report.\n*   `sourceFilter`: filter the source files.\n*   `sourcePath`: option to resolve a custom path.\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options\n\n<!-- File: docs/plugins/customLocator.md (Tokens: ~690 | Category: anthropic-skill) -->\n---\npermalink: /plugins/custom-locator\neditLink: false\nsidebar: auto\ntitle: customLocator\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customLocator\n\nCreates a [custom locator][1] by using special attributes in HTML.\n\nIf you have a convention to use `data-test-id` or `data-qa` attributes to mark active elements for e2e tests,\nyou can enable this plugin to simplify matching elements with these attributes:\n\n```js\n// replace this:\nI.click({ css: '[data-test-id=register_button]');\n// with this:\nI.click('$register_button');\n```\n\nThis plugin will create a valid XPath locator for you.\n\n#### Configuration\n\n*   `enabled` (default: `false`) should a locator be enabled\n*   `prefix` (default: `$`) sets a prefix for a custom locator.\n*   `attribute` (default: `data-test-id`) to set an attribute to be matched.\n*   `strategy` (default: `xpath`) actual locator strategy to use in query (`css` or `xpath`).\n*   `showActual` (default: false) show in the output actually produced XPath or CSS locator. By default shows custom locator value.\n\n#### Examples:\n\nUsing `data-test` attribute with `$` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   attribute: 'data-test'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('$user'); // matches => [data-test=user]\nI.click('$sign-up'); // matches => [data-test=sign-up]\n```\n\nUsing `data-qa` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: 'data-qa'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up]\n```\n\nUsing `data-qa` OR `data-test` attribute with `=` prefix:\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'xpath'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]\nI.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]\n```\n\n```js\n// in codecept.conf.js\nplugins: {\n customLocator: {\n   enabled: true,\n   prefix: '=',\n   attribute: ['data-qa', 'data-test'],\n   strategy: 'css'\n }\n}\n```\n\nIn a test:\n\n```js\nI.seeElement('=user'); // matches => [data-qa=user],[data-test=user]\nI.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n[1]: https://codecept.io/locators#custom-locators\n\n<!-- File: docs/plugins/customReporter.md (Tokens: ~64 | Category: anthropic-skill) -->\n---\npermalink: /plugins/custom-reporter\neditLink: false\nsidebar: auto\ntitle: customReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## customReporter\n\nSample custom reporter for CodeceptJS.\n\n### Parameters\n\n*   `config` &#x20;\n\n<!-- File: docs/plugins/expose.md (Tokens: ~496 | Category: anthropic-skill) -->\n---\npermalink: /plugins/expose\neditLink: false\nsidebar: auto\ntitle: expose\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## expose\n\nExposes properties from helper instances as injectable test arguments.\nUse it to access the underlying Playwright/Puppeteer `page`, the wdio `browser` client,\nor any other helper internal directly from a Scenario:\n\n```js\nScenario('listen for requests', async ({ I, page, browser }) => {\n  page.on('request', r => console.log(r.url()))\n  await page.evaluate(() => 1 + 1)\n  I.amOnPage('/')\n})\n```\n\nThe injected value is a live proxy: every property access reads the *current*\nhelper property, so mid-test reassignments (popups, `switchToNextTab`,\n`openNewTab`) are reflected automatically. Calls are not wrapped as\nCodeceptJS steps — `await page.evaluate(...)` runs as native Playwright.\n\n#### Configuration\n\n`inject` maps an injection name to a `HelperName.propertyName` string. A\nvalue with no dot is shorthand for \"first configured browser helper that\nexposes this property\" (allowed properties: `page`, `browser`,\n`browserContext`, `context`).\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'Playwright.page',\n      browser: 'Playwright.browser',\n      browserContext: 'Playwright.browserContext',\n      frame: 'Playwright.context',  // current frame set by switchTo\n      wdio: 'WebDriver.browser',\n    }\n  }\n}\n```\n\nShorthand:\n\n```js\nplugins: {\n  expose: {\n    enabled: true,\n    inject: {\n      page: 'page',  // resolves to Playwright.page or Puppeteer.page\n    }\n  }\n}\n```\n\n#### Caveats\n\n*   The injected value is a `Proxy`, not the actual `Page`/`Browser` instance,\n    so `page instanceof Page` is `false`. Use duck typing instead.\n*   Cached method references lose the live binding. Call `page.click(...)`,\n    not `const click = page.click; click(...)`.\n*   In dry-run mode the underlying helper property is `undefined`; accessing\n    any property on the proxy returns `undefined` rather than throwing.\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/heal.md (Tokens: ~267 | Category: anthropic-skill) -->\n---\npermalink: /plugins/heal\neditLink: false\nsidebar: auto\ntitle: heal\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## heal\n\nSelf-healing tests with AI.\n\nRead more about healing in [Self-Healing Tests][1]\n\n```js\nplugins: {\n  heal: {\n   enabled: true,\n   on: 'fail',\n  }\n}\n```\n\nMore config options are available:\n\n*   `healLimit` - how many steps can be healed in a single test (default: 2)\n*   `on` - trigger mode. `fail` (default), `file` (filter to a path), `url` (filter to a URL pattern).\n\n#### `on=` modes\n\nHeal always runs on step failures; `on=` narrows when it engages.\n\n*   **fail** — heal any failing step (default)\n*   **file** — heal only failures in `path=...[;line=...]`\n*   **url** — heal only failures when the current URL matches `pattern=...`\n\n`on=step` and `on=test` are not supported and are rejected with an error.\n\n### Parameters\n\n*   `config`   \n\n[1]: https://codecept.io/heal/\n\n<!-- File: docs/plugins/junitReporter.md (Tokens: ~494 | Category: anthropic-skill) -->\n---\npermalink: /plugins/junit-reporter\neditLink: false\nsidebar: auto\ntitle: junitReporter\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## junitReporter\n\nGenerates a JUnit-compatible XML report after a test run.\n\nUnlike Mocha's `mocha-junit-reporter`, this plugin understands CodeceptJS steps and substeps.\nFor every `<testcase>` it includes:\n\n*   `<properties>` — the test's meta information: every `meta` key from `Scenario('...', { meta })`, plus its `tags` and `retries`\n*   `<system-out>` — an indented step/substep log (substeps are nested under their meta step); only failed steps are marked\n*   `<failure>` — for failed tests: the error message, type, stack trace and (optionally) the step trace\n\nThe produced file is consumable by Jenkins, GitLab CI, CircleCI, GitHub Actions test reporters, etc.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"junitReporter\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `outputName`: file name for the report. Default: `report.xml`.\n*   `output`: directory where the report is stored, relative to the project root. Default: the `output` directory. With `run-workers` and `multiple` runs, each run writes its own report into its run folder (e.g. `output/dev_chromium1`) and this option is ignored.\n*   `testGroupName`: value of the `name` attribute on the root `<testsuites>` element. Default: `CodeceptJS`.\n*   `attachMeta`: add the test's meta information (`meta` keys, `tags`, `retries`) as `<properties>`. Default: true.\n*   `attachSteps`: add the step/substep log as `<system-out>`. Default: true.\n*   `stepsInFailure`: append the step trace to the `<failure>` body. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p junitReporter\n    npx codeceptjs run -p junitReporter:outputName=junit.xml\n\n> ℹ When running with `run-workers`, steps are serialized between processes and substep nesting is flattened.\n\n### Parameters\n\n*   `config` **any**\n\n<!-- File: docs/plugins/pageInfo.md (Tokens: ~205 | Category: anthropic-skill) -->\n---\npermalink: /plugins/page-info\neditLink: false\nsidebar: auto\ntitle: pageInfo\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pageInfo\n\nCollects information from web page after each failed test and adds it to the test as an artifact.\nIt is suggested to enable this plugin if you run tests on CI and you need to debug failed tests.\nThis plugin can be paired with `analyze` plugin to provide more context.\n\nIt collects URL, HTML errors (by classes), and browser logs.\n\nEnable this plugin in config:\n\n```js\nplugins: {\n pageInfo: {\n  enabled: true,\n}\n```\n\nAdditional config options:\n\n*   `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)\n*   `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/pause.md (Tokens: ~285 | Category: anthropic-skill) -->\n---\npermalink: /plugins/pause\neditLink: false\nsidebar: auto\ntitle: pause\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pause\n\nPauses test execution interactively. Replaces the legacy `pauseOnFail`\nplugin. The default `on=fail` matches the old `pauseOnFail` behavior.\n\n#### Configuration\n\n```js\nplugins: {\n  pause: {\n    enabled: false,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — pause when a step fails (default). Failures inside `tryTo` and\n    non-final `retryTo` attempts are ignored\n*   **test** — pause after each test\n*   **step** — pause before the first step (interactive walk-through)\n*   **file** — pause when execution reaches `path=...[;line=...]`\n*   **url** — pause when the browser URL matches `pattern=...`\n\nCLI examples:\n\n    npx codeceptjs run -p pause\n    npx codeceptjs run -p pause:on=step\n    npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43\n    npx codeceptjs run -p pause:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/pauseOnFail.md (Tokens: ~90 | Category: anthropic-skill) -->\n---\npermalink: /plugins/pause-on-fail\neditLink: false\nsidebar: auto\ntitle: pauseOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## pauseOnFail\n\nStarts an interactive pause when a test fails.\n\n**Deprecated:** use the `pause` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/retryFailedStep.md (Tokens: ~533 | Category: anthropic-skill) -->\n---\npermalink: /plugins/retry-failed-step\neditLink: false\nsidebar: auto\ntitle: retryFailedStep\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## retryFailedStep\n\nRetries each failed step in a test.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    retryFailedStep: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins retryFailedStep\n\n#### Configuration:\n\n*   `retries` - number of retries (by default 3),\n*   `factor` - The exponential factor to use. Default is 1.5.\n*   `minTimeout` - The number of milliseconds before starting the first retry. Default is 150.\n*   `maxTimeout` - The maximum number of milliseconds between two retries. Default is 10000.\n*   `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.\n*   `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:\n    *   `amOnPage`\n    *   `wait*`\n    *   `send*`\n    *   `execute*`\n    *   `run*`\n    *   `have*`\n*   `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n    To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well.\n*   `deferToScenarioRetries` - when enabled (default), step retries are automatically disabled if scenario retries are configured to avoid excessive total retries.\n\n#### Example\n\n```js\nplugins: {\n    retryFailedStep: {\n        enabled: true,\n        ignoredSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ]\n    }\n}\n```\n\n#### Disable Per Test\n\nThis plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps:\n\nUse scenario configuration to disable plugin for a test\n\n```js\nScenario('scenario tite', { disableRetryFailedStep: true }, () => {\n   // test goes here\n})\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n<!-- File: docs/plugins/screencast.md (Tokens: ~621 | Category: anthropic-skill) -->\n---\npermalink: /plugins/screencast\neditLink: false\nsidebar: auto\ntitle: screencast\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screencast\n\nRecords a video of tests. Uses Playwright's `page.screencast` API (WebM) when the active\nhelper is Playwright, or raw CDP `Page.startScreencast` (APNG, assembled in-process) when the\nactive helper is `CDPBrowser` or a subclass (`Obscura`, `Kitesurf`, ...). Which path is used is\ndetected automatically per test run; nothing in the config changes between them.\n\nWhen `captions` is enabled, action annotations are burned into the video — Playwright only,\nvia `page.screencast.showActions()`/`showChapter()`; silently absent on the CDP path, since CDP\nscreencast frames are raw, uncomposited page captures with no overlay mechanism. `subtitles`\n(a standalone `.srt`) works identically on both paths, since it's driven by step events, not by\nthe video API. Default `on=fail` keeps videos for failed tests only; `on=test` keeps every\ntest's video.\n\nNote: enabling Playwright's helper-level `video: true` together with this plugin produces two\nindependent recordings (`output/videos/*.webm` from the helper, `output/screencast/*.webm` from\nthis plugin).\n\n#### Configuration\n\n```js\nplugins: {\n  screencast: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — record while running; delete on pass, keep on fail (default)\n*   **test** — record and keep every test's video\n\nOther config options:\n\n*   `captions`: burn-in action overlays via `page.screencast.showActions()`. Playwright only. Default: true.\n*   `subtitles`: also write a standalone `.srt` file alongside the video. Default: false.\n*   `video`: record a video. With `video=false, subtitles=true`, only the `.srt` is produced. Default: true.\n*   `size`: pass-through `{ width, height }` — `screencast.start`'s `size` on Playwright, `maxWidth`/`maxHeight` on the CDP path.\n*   `quality`: pass-through 0–100 for `screencast.start` (Playwright) or CDP `Page.startScreencast` (CDPBrowser family).\n\nCLI examples:\n\n    npx codeceptjs run -p screencast\n    npx codeceptjs run -p screencast:on=test\n    npx codeceptjs run -p screencast:on=test;captions=false;subtitles=true\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/screenshot.md (Tokens: ~462 | Category: anthropic-skill) -->\n---\npermalink: /plugins/screenshot\neditLink: false\nsidebar: auto\ntitle: screenshot\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshot\n\nSaves screenshots from the browser at points triggered by `on=`.\n\nReplaces the legacy `screenshotOnFail` plugin. Default `on=fail` preserves the\nold behavior (screenshot when a test fails). Pass `slides=true` (with `on=step`)\nto generate a step-by-step slideshow report — replaces the legacy\n`stepByStepReport` plugin.\n\n#### Configuration\n\n```js\nplugins: {\n  screenshot: {\n    enabled: true,\n    on: 'fail',\n  }\n}\n```\n\n#### `on=` modes\n\n*   **fail** — screenshot when a test fails (default)\n*   **test** — screenshot at the end of every test\n*   **step** — screenshot after every step\n*   **file** — screenshot for steps in `path=...[;line=...]`\n*   **url** — screenshot when the current browser URL matches `pattern=...`\n\nOther config options:\n\n*   `uniqueScreenshotNames`: use unique names for screenshot. Default: false.\n*   `fullPageScreenshots`: make full page screenshots. Default: false.\n*   `disableScreenshots`: legacy switch to skip the plugin entirely.\n*   `slides`: generate a step-by-step slideshow report (requires `on=step`). Default: false.\n*   `deleteSuccessful`: when `slides=true`, drop slideshow directories of passing tests. Default: true.\n*   `animateSlides`: when `slides=true`, animate transitions between slides. Default: true.\n*   `ignoreSteps`: when `slides=true`, RegExps of step names to skip in the slideshow.\n\nCLI examples:\n\n    npx codeceptjs run -p screenshot\n    npx codeceptjs run -p screenshot:on=step\n    npx codeceptjs run -p screenshot:on=step;slides=true\n    npx codeceptjs run -p screenshot:on=file:path=tests/login_test.js\n    npx codeceptjs run -p screenshot:on=url:pattern=/users/*\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/screenshotOnFail.md (Tokens: ~90 | Category: anthropic-skill) -->\n---\npermalink: /plugins/screenshot-on-fail\neditLink: false\nsidebar: auto\ntitle: screenshotOnFail\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## screenshotOnFail\n\nSaves a screenshot when a test fails.\n\n**Deprecated:** use the `screenshot` plugin with `on: 'fail'`, which is the default behavior.\n\n### Parameters\n\n*   `config`\n\n<!-- File: docs/plugins/sitemap.md (Tokens: ~355 | Category: anthropic-skill) -->\n---\npermalink: /plugins/sitemap\neditLink: false\nsidebar: auto\ntitle: sitemap\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## sitemap\n\nCollects all pages visited while tests are running and saves them as a [sitemap][1] XML file.\n\nThe plugin listens to browser navigation events, so it doesn't slow down test steps.\nPage loads and SPA route changes (`history.pushState`) of the top-level page are recorded, iframes are ignored.\nEvery unique `http(s)` URL is added to the sitemap. URL fragments (`#...`) are dropped.\n\nSupported helpers: Playwright, Puppeteer, and WebDriver with BiDi protocol enabled (default).\nThe sitemap is written once all tests finish.\n\n#### Configuration\n\n```js\n\"plugins\": {\n   \"sitemap\": {\n     \"enabled\": true\n   }\n }\n```\n\nPossible config options:\n\n*   `file`: path to the sitemap file, relative to the output directory. Default: `sitemap.xml`, saved as `output/sitemap.xml`.\n*   `stripQuery`: remove query strings so `/posts?page=1` and `/posts?page=2` become one `/posts` entry. Set to `false` to keep them. Default: true.\n\nCLI examples:\n\n    npx codeceptjs run -p sitemap\n    npx codeceptjs run -p sitemap:file=visited.xml\n    npx codeceptjs run -p sitemap:stripQuery=false\n\nWorks with `run-workers`: URLs from all workers are merged into a single sitemap.\n\n### Parameters\n\n*   `config` **any**  \n\n[1]: https://www.sitemaps.org/protocol.html\n\n<!-- File: docs/plugins/stepTimeout.md (Tokens: ~365 | Category: anthropic-skill) -->\n---\npermalink: /plugins/step-timeout\neditLink: false\nsidebar: auto\ntitle: stepTimeout\n---\n\n<!-- Generated by documentation.js. Update this documentation by updating the source code. -->\n\n## stepTimeout\n\nSet timeout for test steps globally.\n\nAdd this plugin to config file:\n\n```js\nplugins: {\n    stepTimeout: {\n       enabled: true\n    }\n}\n```\n\nRun tests with plugin enabled:\n\n    npx codeceptjs run --plugins stepTimeout\n\n#### Configuration:\n\n*   `timeout` - global step timeout, default 150 seconds\n\n*   `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false\n\n*   `noTimeoutSteps` - an array of steps with no timeout. Default:\n\n    *   `amOnPage`\n    *   `wait*`\n\n    you could set your own noTimeoutSteps which would replace the default one.\n\n*   `customTimeoutSteps` - an array of step actions with custom timeout. Use it to override or extend noTimeoutSteps.\n    You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.\n\n#### Example\n\n```js\nplugins: {\n    stepTimeout: {\n        enabled: true,\n        overrideStepLimits: true,\n        noTimeoutSteps: [\n          'scroll*', // ignore all scroll steps\n          /Cookie/, // ignore all steps with a Cookie in it (by regexp)\n        ],\n        customTimeoutSteps: [\n          ['myFlakyStep*', 1],\n          ['scrollWhichRequiresTimeout', 5],\n        ]\n    }\n}\n```\n\n### Parameters\n\n*   `config` &#x20;\n\n<!-- File: docs/api.md (Tokens: ~2479 | Category: project-spec) -->\n---\npermalink: /api\ntitle: API Testing\n---\n\n## API Testing\n\nCodeceptJS provides a way to write tests in declarative manner for REST and GraphQL APIs. \n\nTake a look:\n\n```js\nI.sendGetRequest('/users/1');\n// returns { \"user\": { \"name\": \"jon\" }, \"projects\": [] }\nI.seeResponseCodeIsSuccessful();\nI.seeResponseContainsKeys(['user', 'projects']);\nI.seeResponseContainsJson({ user: { name: 'jon' } });\nI.seeResponseMatchesJsonSchema($ => {\n  return $.object(\n    user: $.object({\n      name: $.string(),\n    }),\n    projects: $.array()\n  )\n});\n```\nIn this code we checked API request for:\n\n* status code\n* data inclusion\n* data structure\n\nThese are the things you should generally test your APIs for.\n\n> 🤓 It is recommended to check only invariable parts of responses. Check for required fields and only values you control. For instance, it is not recommended to check id fields, date fields, as they can be frequently changed.\n\n## Installation\n\nInstall CodeceptJS if it is not installed yet.\n\n```\nnpm i codeceptjs --save-dev\n```\n\nInitialize CodeceptJS and select REST or GraphQL helper when asked for a helper:\n\n```\nnpx codeceptjs init\n```\n\n## Configuration\n\nEnsure that inside `codecept.conf.js` in helpers section `REST` or `GraphQL` helpers are enabled.\n\n* If you use `REST` helper add `JSONResponse` helper below with no extra config:\n\n```js\n// inside codecept.conf.js\n// ...\n  helpers: {\n    REST: {\n      endpoint: 'http://localhost:3000/api'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {}\n  }\n```\n* If you use `GraphQL` helper add `JSONResponse` helper, configuring it to use GraphQL for requests:\n\n```js\n  helpers: {\n    GraphQL: {\n      endpoint: 'http://localhost:3000/graphql'\n    },\n    // .. add JSONResponse helper here\n    JSONResponse: {\n      requestHelper: 'GraphQL',\n    }\n  }\n```\n\nOriginally, REST and GraphQL helpers were not designed for API testing. \nThey were used to perform API requests for browser tests. As so, they lack assertion methods to API responses.\n\n[`JSONResponse`](/helpers/json-response/) helper adds response assertions. \n\n> 💡 In CodeceptJS assertions start with `see` prefix. Learn more about assertions by [opening reference for JSONResponse](/helpers/json-response/) helper.\n\nGenerate TypeScript definitions to get auto-completions for JSONResponse:\n\n```\nnpx codeceptjs def\n```\n\nAfter helpers were configured and typings were generated, you can start writing first API test. By default, CodeceptJS saves tests in `tests` directory and uses `*_test.js` suffix. The `init` command created the first test for you to start.\n\n> Check [API Examples](https://github.com/codeceptjs/api-examples) to see tests implementations.\n\n## Requests\n\n[REST](/helpers/rest/) or [GraphQL](/helpers/graph-ql/) helpers implement methods for making API requests.\nBoth helpers send requests via HTTP protocol from CodeceptJS process. \nFor most cases, you will need to have authentication. It can be passed via headers, which can be added to helper's configuration in `codecept.conf.js`. \n\n```js\nhelpers: {\n  REST: {\n    defaultHeaders: {\n      // use Bearer Authorization\n      'Authorization': 'Bearer 11111',\n      'Content-Type': 'application/json',\n      'Accept': 'application/json',\n    },\n  }\n}\n```\n\nOr you can use the browser cookies if you are running browser session.\nIn this case use `setSharedCookies()` from `@codeceptjs/configure` package:\n\n```js\nimport { setSharedCookies } from '@codeceptjs/configure'\n\n// call before exporting config\nsetSharedCookies()\n\nexport const config = {\n  // ...\n  helpers: {  \n    // also works with Playwright or Puppeteer\n    WebDriver: {\n      //... \n    },\n\n    REST: { \n      // ...\n    }\n  }\n}\n```\n\n### REST\n\nREST helper can send GET/POST/PATCH/etc requests to REST API endpoint:\n\n* [`I.sendGetRequest()`](/helpers/rest#sendgetrequest)\n* [`I.sendPostRequest()`](/helpers/rest#sendpostrequest)\n* [`I.sendPutRequest()`](/helpers/rest#sendputrequest)\n* [`I.sendPatchRequest()`](/helpers/rest#sendpatchrequest)\n* [`I.sendDeleteRequest()`](/helpers/rest#senddeleterequest)\n* [`I.sendDeleteRequestWithPayload()`](/helpers/rest#senddeleterequestwithpayload)\n* ...\n\nAuthentication headers can be set in [helper's config](https://codecept.io/helpers/rest/#configuration) or per test with headers or special methods like `I.amBearerAuthenticated`.\n\nExample:\n\n```js\nFeature('Users endpoint')\n\nScenario('create user', ({ I }) => {\n    // this way we pass Bearer token\n  I.amBearerAuthenticated(secret('token-is-here'));\n  // for custom authorization with headers use\n  // I.haveRequestHeaders method\n\n  // here we send a POST request\n  const response = await I.sendPostRequest('/users', {\n    name: 'joe',\n    email: 'joe@mail.com'\n  });\n  // usually we won't need direct access to response object for API testing \n  // but you can obtain it from request\n\n  // check the last request was successful\n  // this method introduced by JSONResponse helper\n  I.seeResponseCodeIsSuccessful();\n})\n```\n\n### GraphQL\n\nGraphQL have request format different then in REST API, but the response format is the same.\nIt's plain old JSON. This why `JSONResponse` helper works for both API types.\nConfigure authorization headers in `codecept.conf.js` and make your first query:  \n\n```js\nFeature('Users endpoint')\n\nScenario('get user by query', ({ I }) => {\n  // make GraphQL query or mutation\n  const resp = await I.sendQuery('{ user(id: 0) { id name email }}');\n  I.seeResponseCodeIsSuccessful();\n\n  // GraphQL always returns key data as part of response\n  I.seeResponseContainsKeys(['data']);\n\n  // check data for partial inclusion\n  I.seeResponseContainsJson({\n    data: {\n      user: {\n        name: 'john doe',\n        email: 'johnd@mutex.com',\n      },\n    },\n  });\n});\n```\n\nGraphQL helper has two methods available:\n\n* [`I.sendQuery()`](/helpers/graph-ql#sendquery)\n* [`I.sendMutation()`](/helpers/graph-ql#sendmutation)\n\n## Assertions\n\n`JSONResponse` provides set of assertions for responses in JSON format. These assertions were designed to check only invariable parts of responses. So instead of checking that response equals to the one provided, we will check for data inclusion and structure matching.\n\nFor most of cases, you won't need to perform assertions by accessing `response` object directly. All assretions are performed under hood inside `JSONResponse` module. It is recommended to keep it that way, to keep tests readable and make test log to contain all assertions.\n\n```js\nScenario('I make API call', ({ I }) => {\n  // request was made by REST \n  // or by GraphQL helper\n\n  // check that response code is 2xx\n  I.seeResponseCodeIsSuccessful();\n\n  // check that response contains keys\n  I.seeResponseContainsKeys(['data', 'pages', 'meta']);\n});\n```\n\n### Response Status Codes\n\n[Response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) can be checked to be equal to some value or to be in a specific range. \nTo check that response code is `200` call `I.seeResponseCodeIs`:\n\n```js\nI.seeResponseCodeIs(200);\n```\nBut because other response codes in 2xx range are also valid responses, you can use `seeResponseCodeIsSuccessful()` which will match 200 (OK), 201 (Created), 206 (Partial Content) and others. Methods to check 3xx, 4xx, 5xx response statuses also available.\n\n```js\n// matches 200, 201, 202, ... 206\nI.seeResponseCodeIsSuccessful();\n\n// matches 300...308\nI.seeResponseCodeIsRedirection();\n\n// matches 400..451\nI.seeResponseCodeIsClientError();\n\n// matches 500-511\nI.seeResponseCodeIsServerError();\n```\n\n### Structure\n\nThe most basic thing to check in response is existence of keys in JSON object. Use [`I.seeResponseContainsKeys()`](/helpers/json-response#seeresponsecontainskeys) method for it:\n\n```js\n// response is { \"name\": \"joe\", \"email\": \"joe@joe.com\" }\nI.seeResponseContainsKeys(['name', 'email']);\n```\n\n> ℹ️ If response is an array, it will check that every element in array have provided keys\n\nHowever, this is a very naive approach. It won't work for arrays or nested objects.\nTo check complex JSON structures `JSONResponse` helper uses [`Zod`](https://zod.dev) library.\nIt has rich API to validate JSON by the schema defined using JavaScript.\n\n```js\n// import zod library,\n// it is installed with CodeceptJS\nimport { z } from 'zod';\n\n// create schema definition using Zod API\nconst schema = z.object({\n  email: z.string().email(),\n  phone: z.string().regex(/^\\d{3}-\\d{3}-\\d{4}$/),\n  birthday: z.string().datetime().max(new Date('2004-01-01'))\n});\n\n// check that response matches that schema\nI.seeResponseMatchesJsonSchema(schema);\n```\n\n> 📋 **Migration Note**: CodeceptJS has migrated from Joi to Zod v4 for JSON schema validation.\n> If you have existing tests using Joi, please update them:\n> * Replace `const Joi = require('joi')` with `import { z } from 'zod'`\n> * Replace `Joi.object().keys({...})` with `z.object({...})`\n> * Replace `Joi.string().email()` with `z.string().email()`\n> * Replace `Joi.date()` with appropriate `z.string()` or `z.date()` types\n> * See [Zod documentation](https://zod.dev) for complete API reference\n\n### Data Inclusion\n\nTo check that response contains expected data use `I.seeResponseContainsJson` method. \nIt will check the response data for partial match. \n\n```js\nI.seeResponseContainsJson({\n  user: {\n    email: 'user@user.com'\n  }\n})\n```\n\n> ℹ️ If response is an array, it will check that at least one element in array matches JSON\n\nTo perform arbitrary assertions on a response object use `seeResponseValidByCallback`. \nIt allows you to do any kind of assertions by using `expect` from [`chai`](https://www.chaijs.com) library.\n\n```js\nI.seeResponseValidByCallback(({ data, status, expect }) => {\n  // we receive data and expect to combine them for good assertion\n  expect(data.users.length).to.be.gte(10);\n})\n```\n\n## Extending JSONResponse\n\nTo add more assertions it is recommended to create a custom helper.\nInside it you can get access to latest JSON response:\n\n```js\n// inside a custom helper\nmakeSomeCustomAssertion() {\n  const response = this.helpers.JSONResponse.response;\n}\n```\n\n<!-- File: docs/architecture.md (Tokens: ~2834 | Category: project-spec) -->\n---\npermalink: /architecture\ntitle: Architecture\n---\n\n# CodeceptJS Architecture\n\nHow CodeceptJS runs a test, and the internal modules you build [plugins, listeners, and helpers](/hooks) against.\n\n## How a Test Runs\n\nCodeceptJS is built on top of [Mocha](https://mochajs.org). A run goes through these stages:\n\n1. **Load.** CodeceptJS reads the config, builds the [container](#container) (helpers, support objects, plugins), and runs the `bootstrap` hook. `event.all.before` fires.\n2. **Suite.** For each suite, `event.suite.before` fires. Helper `_beforeSuite` hooks run.\n3. **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.\n4. **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`.\n5. **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.\n\nThe 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.\n\n## The Internal API\n\nCodeceptJS exposes its internals as named exports of the `codeceptjs` package. Import only what you need:\n\n```js\nimport { recorder, event, output, container, config } from 'codeceptjs'\n```\n\n| Export | What it is |\n| --- | --- |\n| [`codecept`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/codecept.js) | the test runner class |\n| [`config`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/config.js) | the loaded configuration |\n| [`container`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/container.js) | dependency-injection container: helpers, support objects, plugins, the Mocha instance |\n| [`recorder`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/recorder.js) | the global promise chain that orders every step |\n| [`event`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/event.js) | the event dispatcher and the names of all lifecycle events |\n| [`output`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/output.js) | the printer used for all console output |\n| [`store`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/store.js) | global state of the run — current test/step, run modes, directories |\n| [`helper`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/helper.js) | the base class every helper extends |\n| [`actor`](https://github.com/codeceptjs/CodeceptJS/blob/master/lib/actor.js) | the base class behind the `I` object |\n\n> 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.\n\nThe [API reference](https://github.com/codeceptjs/CodeceptJS/tree/master/docs/api) on GitHub documents these modules; the source is the final word.\n\n## The Recorder\n\nThe 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:\n\n```js\nimport { event, recorder } from 'codeceptjs'\n\nevent.dispatcher.on(event.test.before, () => {\n  recorder.add('seed fixture data', async () => {\n    await api.post('/users', { name: 'john', email: 'john@example.com' })\n  })\n})\n```\n\n- `recorder.add(name, fn)` — append `fn` (async, or returning a promise) to the chain. The name shows up in `--verbose` output.\n- `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`.\n- `recorder.retry({ retries, when })` — retry failing steps that match `when`. See [conditional retries](/helpers#conditional-retries).\n\nRun tests with `--verbose` to watch the recorder schedule and execute each entry.\n\n## Container\n\nThe container resolves helpers and support objects by name:\n\n```js\nimport { container } from 'codeceptjs'\n\nconst helpers = container.helpers()          // every helper, keyed by name\nconst { Playwright } = container.helpers()   // one helper\nconst support = container.support()          // every support object\nconst { UserPage } = container.support()     // one page object\nconst plugins = container.plugins()          // enabled plugins\nconst mocha = container.mocha()              // the current Mocha instance\n```\n\nAdd objects at runtime — useful from a `bootstrap` script:\n\n```js\nimport { container } from 'codeceptjs'\nimport UserPage from './pages/user.js'\n\ncontainer.append({\n  helpers: { MyHelper: new MyHelper({ host: 'http://example.com' }) },\n  support: { UserPage },\n})\n```\n\n## Events\n\n`event.dispatcher` is a Node `EventEmitter`. Attach listeners to it from a [plugin](/hooks#plugins) or `bootstrap` script.\n\nEvents are **sync** or **async**:\n\n- **sync** — fires the moment the action happens. Do synchronous work only.\n- **async** — fires when the action is *scheduled*. To do async work in the right order, queue it with `recorder.add()`.\n\n| Event | Kind | When |\n| --- | --- | --- |\n| `event.all.before` | — | before any test runs |\n| `event.suite.before(suite)` | async | before a suite |\n| `event.test.started(test)` | sync | at the very start of a test |\n| `event.test.before(test)` | async | after `Before` hooks from helpers and the test are run |\n| `event.test.passed(test)` | sync | test passed |\n| `event.test.failed(test, err)` | sync | test failed |\n| `event.test.skipped(test)` | sync | test skipped |\n| `event.test.after(test)` | async | after each test |\n| `event.test.finished(test)` | sync | test finished |\n| `event.suite.after(suite)` | async | after a suite |\n| `event.step.before(step)` | async | step scheduled for execution |\n| `event.step.started(step)` | sync | step starts executing |\n| `event.step.passed(step)` | sync | step passed |\n| `event.step.failed(step, err)` | sync | step failed |\n| `event.step.after(step)` | async | after a step |\n| `event.step.finished(step)` | sync | step finished |\n| `event.step.comment(step)` | sync | a comment such as `I.say(...)` |\n| `event.bddStep.before(step)` / `event.bddStep.after(step)` | async | around a Gherkin step |\n| `event.hook.started(hook)` / `event.hook.passed` / `event.hook.failed` / `event.hook.finished` | sync | around `Before` / `After` / `BeforeSuite` / `AfterSuite` hooks |\n| `event.all.after` | — | after all tests |\n| `event.all.result(result)` | — | when results are printed |\n| `event.all.failures(failures)` | — | when a run reports failures |\n| `event.workers.before` / `event.workers.after` / `event.workers.result(result)` | — | around a [parallel run](/parallel) (parent process only) |\n\nThe [built-in listeners](https://github.com/codeceptjs/CodeceptJS/tree/master/lib/listener) are working examples — every reporter and several plugins are listeners.\n\n### Test object\n\nTest events pass a test object with these fields:\n\n- `title` — the test title\n- `body` — the test function as a string\n- `opts` — test options such as `retries` (see [test options](/advanced#test-options))\n- `pending` — `true` while scheduled, `false` once finished\n- `tags` — array of [tags](/test-structure#tags) for this test\n- `artifacts` — files attached to this test (screenshots, videos, …), shared across reporters\n- `file` — path to the test file\n- `steps` — executed steps (only on `test.passed`, `test.failed`, `test.finished`)\n- `skipInfo` — present when the test was skipped: `{ message, description }`\n\n### Step object\n\nStep events pass a step object with these fields:\n\n- `name` — the step name, such as `see` or `click`\n- `actor` — the current actor, usually `I`\n- `helper` — the helper instance that executes this step\n- `helperMethod` — the helper method, usually the same as `name`\n- `status` — `passed` or `failed`\n- `prefix` — for a step inside a `within` block, the within text (e.g. `Within .js-signup-form`)\n- `args` — the arguments passed to the step\n\n## Config\n\n```js\nimport { config } from 'codeceptjs'\n\nconfig.get()                       // the full config object\nconfig.get('myKey')                // one value\nconfig.get('myKey', 'fallback')    // one value, with a default\n```\n\n## Output\n\nOutput has four verbosity levels, each toggled by a CLI flag:\n\n| Level | Flag | Use |\n| --- | --- | --- |\n| default | — | `output.print` — basic information |\n| steps | `--steps` | step execution |\n| debug | `--debug` | steps plus `output.debug` |\n| verbose | `--verbose` | debug plus `output.log` (internal logs and recorder activity) |\n\n```js\nimport { output } from 'codeceptjs'\n\noutput.print('basic information')\noutput.debug('debug information')\noutput.log('verbose logging information')\n```\n\nUse these instead of `console.log` so messages respect the chosen verbosity.\n\n## Store\n\n`store` holds the state of the current run — the executing test, suite, and step, the active run modes (`dryRun`, `debugMode`, `workerMode`, …), and the project directories. Listeners, plugins, and helpers read it to know where in the [lifecycle](#events) they are without that information being passed to them:\n\n```js\nimport { store } from 'codeceptjs'\n\nevent.dispatcher.on(event.step.before, () => {\n  if (store.dryRun) return                    // no side effects on a dry run\n  output.debug(`in ${store.currentTest?.title}`)\n})\n```\n\nCodeceptJS keeps the state fields up to date for you. See the [Store reference](/store) for every field and when to write to it.\n\n## Helpers and the Actor\n\nThe `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).\n\n## Running CodeceptJS from Code\n\nCodeceptJS can be driven from your own script. Create the runner with a config and options, initialize it, then bootstrap, load tests, and run:\n\n```js\nimport { codecept as Codecept } from 'codeceptjs'\n\nconst config = { helpers: { Playwright: { browser: 'chromium', url: 'http://localhost' } } }\nconst opts = { steps: true }\n\nconst codecept = new Codecept(config, opts)\ncodecept.init(import.meta.dirname)   // the test root directory\n\ntry {\n  await codecept.bootstrap()\n  codecept.loadTests('**/*_test.js')\n  await codecept.run()               // pass a test file path to run only that file\n} catch (err) {\n  console.error(err)\n  process.exitCode = 1\n} finally {\n  await codecept.teardown()\n}\n```\n\n> To run tests inside workers from a script, see [parallel execution](/parallel).\n\n---\n\n**See also:** [Extending CodeceptJS](/hooks) · [Custom Helpers](/helpers) · [Plugins](/plugins) · [Bootstrap & Teardown](/bootstrap)\n</yakaai_skills>"}