Skip to content

Tags: codeceptjs/CodeceptJS

Tags

4.0.0-rc.20

Toggle 4.0.0-rc.20's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(mcp): timeout aborts Mocha runner so next run_test isn't blocked (#…

…5551)

* fix(mcp): timeout aborts Mocha runner so next run_test isn't blocked

Previously the run_test / run_step_by_step timeout was just a setTimeout
that rejected the race promise — the Mocha runner kept going, the
recorder chain stayed queued, listeners stayed attached, and pause
sessions kept trapping. Subsequent run_test calls hit "Mocha instance
is currently running". cancel didn't help because pendingRunPromise was
only assigned in the paused branch, so it saw nothing to cancel.

- Assign pendingRunPromise immediately after runPromise creation in
  both run_test and run_step_by_step (was set only on pause).
- Wrap the Promise.race in try/catch + finally; clear the setTimeout
  and route timeout rejections through cancelRun(); return a clean
  status: "failed" payload to the client.
- Make cancelRun() actually abort: look up the Mocha runner via
  mocha._runner / _previousRunner / runner and call runner.abort();
  recorder.reset() to drop any queued tasks. Existing abortRun + pause
  release stay in place for stuck-on-pause cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mcp): drop unused runner.abort + recorder.reset hacks

The pause-and-abortRun chain alone is enough: resolveContinue releases
the current pauseSession, abortRun causes the next pauseNow-queued
pauseSession to reject inside setPauseHandler, the rejection propagates
through the recorder to codecept.run, and Mocha's runningNow clears
naturally. Reaching into mocha._runner / _previousRunner / .runner and
calling recorder.reset() were speculative — the timeout repro still
clears Mocha state without them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(mcp): extract raceRunOutcome helper to dedupe run_test/run_step_by_step

Both tools had the same Promise.race + try/catch + finally + cancelRun
+ failure-response block. Extracted into raceRunOutcome(runPromise,
timeout) which returns a tagged outcome ({outcome:'paused'|'completed'}
or {outcome:'aborted', error}) so the caller branches once.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(mcp): rename raceRunOutcome → waitForTestResult, outcome → status

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

4.0.0-rc.19

Toggle 4.0.0-rc.19's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(codeceptq): CLI to query HTML with CodeceptJS locators (#5550)

* update docs

* updated docs, added browser plugin

* feat(codeceptq): CLI to query HTML with CodeceptJS locators

Adds `codeceptq` — a standalone CLI that takes an HTML stream (stdin or
--file) plus a CodeceptJS locator (CSS / XPath / fuzzy / semantic) and
prints matched elements with line numbers and outerHTML snippets.
Designed to give AI agents a fast feedback loop against `aiTrace`'s
per-step HTML snapshots: "would this locator match at step N?" without
re-running the test or spawning a browser.

- Reuses Locator class for CSS→XPath conversion + semantic builders
  (--field, --click, --checkable, --select).
- Optional context arg scopes matches: `codeceptq 'Save' '.modal' --click`.
- Stable output flags: --limit, --snippet (default 500), --full, --json.
- Exit codes: 0 match, 1 no match, 2 invalid input/XPath.
- formatHtml now uses `inline: []` so every element gets its own line in
  trace HTML — line numbers map 1:1 to elements for codeceptq output.
- 45 runner tests against test/data/checkout.html, github.html,
  gitlab.html, drag_drop.html assert exact line + snippet for every
  locator strategy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(mcp): surface aiTrace dir in run_test / pause payloads

run_test, run_step_by_step, and pausedPayload now include aiTraceDir
(the per-test output/trace_<title>_<hash>/ folder) so agents can point
codeceptq directly at the saved *_page.html snapshots without globbing
or recomputing the hash. Per-test entries in reporterJson.tests[] also
carry the dir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(codeceptq): bump describe timeout to 30s for CI

The 'Sign up' --click case on github.html (2k-line fixture, 12-branch
semantic union XPath) takes ~8s locally and exceeds the default 10s
mocha timeout on slower CI runners. Suite-level timeout matches what
the local runs already use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(codeceptq): pre-resolve //*[@id]/@id subqueries

Locator.clickable.wide and field.labelContains emit predicates of form
[@aria-labelledby = //*[@id][normalize-space(string(.)) = 'X']/@id ].
xpath@0.0.34 re-runs the inner //* scan once per outer element match —
O(N²) on non-trivial docs. The 2k-line github fixture spent 8.5s in
that single branch out of 12.

Pre-resolve the inner subquery once, splice the resulting id (or a
sentinel for no-match) back as a literal so the engine sees a flat
attribute compare.

Github 'Sign up' --click: 9026ms → 276ms (~33×).
Full runner suite: 14s → 6s.

Reverts the 30s describe-level timeout from the previous commit since
the underlying perf issue is now fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(codeceptq): build per-strategy XPath fragments

Replaces the post-hoc regex pre-resolver with strategy-level construction.
Each semantic locator (--click/--field/--checkable) is built as a list of
XPath branches; doc-wide subqueries (label[@for] resolution, ids by visible
text) are evaluated once and inlined as literal predicates instead of
sitting nested inside outer per-element predicates that the engine
re-executes on every match.

Eval loop runs each branch separately and sorts results by source offset
to preserve the document-order contract of XPath unions.

Github 'Sign up' --click: 9000ms → 264ms (independent of XPath engine —
fontoxpath benched the same as xpath@0.0.34 on the original union).
All 45 runner tests pass with identical line/snippet output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(locator): guard aria-labelledby branch with attr-existence predicate

The wide clickable / labelContains field XPath includes:
  .//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = X]/@id]

That predicate forces every element to evaluate the inner //*[@id] subquery,
which is O(N²) on any non-trivial document for pure-JS XPath engines (xpath
npm: 7641ms on a 2k-line page; fontoxpath: 7057ms on the same branch).
Browser engines optimize via join-pushdown.

Adding [@aria-labelledby] as a left-to-right filter predicate first cuts
the slow comparison to only elements that actually have the attribute:

  .//*[@aria-labelledby][@aria-labelledby = //*[@id][...]/@id]

7641ms → 52ms (147×). Semantics identical: in XPath, [A][B] and [A and B]
produce the same result-set, but predicates are evaluated left-to-right,
so the cheap attr-existence check filters out the bulk first.

This is a single-character XPath change — codeceptq goes from 9000ms →
325ms on test/data/github.html with no special-case code. Reverted the
per-strategy reimplementation in lib/command/query.js (back to using
Locator.clickable.wide / Locator.field.byText directly).

Added two unit tests for the aria-labelledby branch in
Locator.clickable.wide (positive + negative).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

4.0.0-rc.18

Toggle 4.0.0-rc.18's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(mcp): per-test plugin overrides + shell session lifecycle (#5547)

* update docs

* updated docs, added browser plugin

* feat(mcp): per-test plugin overrides + shell session lifecycle

- run_test / run_step_by_step accept a `plugins` object that mirrors
  the CLI `-p` flag (e.g. `{ screencast: { saveScreenshots: true },
  aiTrace: { on: 'fail' }, pause: true }`). Container is re-initialized
  when the plugin set changes between calls.
- start_browser / stop_browser now drive a full shell session like
  `codeceptjs shell`: bootstrap, recorder.start, suite.before /
  test.before on start; matching after events plus codecept.teardown
  on stop.
- run_code / snapshot now require an active session (shell or paused
  test) and return a clear error pointing the agent at start_browser
  or run_test otherwise. Plugins and listeners that depend on
  suite.before / test.before now fire correctly during MCP usage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(trace): TraceReader API + ariaDiff in run_code

Move artifact-on-disk reading from mcp-server.js into a TraceReader class
in lib/utils/trace.js. Python-style indexing via first / last / nth, kept
generic across kinds (aria / html / screenshot / console / storage). Sort
by filename — aiTrace's zero-padded step prefix means a lexical sort is
chronological.

run_code uses it to diff ARIA between the last aiTrace capture and the
new one produced by the steps inside this call:

  const reader = new TraceReader(currentAiTraceDir)
  const before = reader.last('aria')
  // run code, aiTrace captures per step
  const after = reader.last('aria')
  if (before !== after) result.ariaDiff = ariaDiff(before, after)

initCodecept now force-enables aiTrace whenever the MCP server initializes
the container — it's the canonical per-step capture, no point in MCP doing
its own grabAriaSnapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(mcp): add Agentic Testing guide; simplify ARIA snapshot pipeline

- docs/agents.md: new top-level page covering the MCP loop (open the
  page → read → run a CodeceptJS command → check → commit), how the
  agent reads page artifacts, and where MCP fits relative to pause().
- lib/aria.js: trim INTERACTIVE_ROLES to roles that actually take
  user input (drop container roles like grid/tablist/menubar);
  remove IGNORED_ROLES unwrap, icon-button auto-naming, and
  bool/null coercion in attribute values. Names are always
  emitted; attribute values are passed through as plain strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

4.0.0-rc.17

Toggle 4.0.0-rc.17's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(locator): prevent click(text, container) from matching the contai…

…ner itself (#5532)

`I.click("Description", 'ul[role="tablist"]')` was clicking the <ul> itself
instead of the intended <li role="tab">. The fallback xpath
`./self::*[contains(normalize-space(string(.)), literal)]` matched the scope
element whenever its concatenated string-value contained the literal — and a
container with text-bearing children (tab labels, menu items) always will.
Playwright then clicked the container's geometric center, landing on whatever
child sat there.

Fix: narrow `Locator.clickable.self` to prefer the deepest descendant whose
string-value contains the literal, and only match self when self *is* that
deepest element (or its @value matches, preserving the input case).

Also extend `Locator.clickable.wide` with ARIA widget roles so clicks hit
the semantic element directly rather than relying on bubble-up:
tab, link, menuitem (+ checkbox/radio variants), option, treeitem.

Tests:
- test/data/app/view/form/tablist.php — ember-like 5-tab fixture with
  a #selected-tab marker driven by click handlers.
- test/helper/Playwright_test.js — regression scenario clicks History →
  Description → Runs → Code template against ul[role="tablist"] and
  asserts the correct tab fired. Plus an explicit <a>+span case scoped
  by tag 'a' to document that behavior.
- test/unit/locator_test.js — three xpath-level tests for clickable.self
  (tablist narrowest-match, <div>Submit</div> self-match, <input value>
  self-match) and two for clickable.wide (role="tab", role="menuitem").

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

3.7.9

Toggle 3.7.9's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
release 3.7.9 (#5535)

4.0.0-rc.16

Toggle 4.0.0-rc.16's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix(fillField): prevent rich-editor keystroke leak to sibling inputs (#…

…5531)

* fix(fillField): prevent rich-editor keystroke leak to sibling inputs

The IFRAME branch typed via the root-page keyboard against an iframe body
that's not contenteditable (Monaco-style editors), so keystrokes landed on
whatever the outer document had focused. Detection also climbed the DOM
when the matched element looked hidden, which could pick up unrelated
editors elsewhere on the page.

Now: detection only walks down from the user's locator, the IFRAME branch
re-detects the real input surface inside the iframe, and every focus/click
is verified against document.activeElement before typing — a failed focus
throws instead of leaking. Backing-textarea fixtures (TinyMCE legacy,
CKEditor 4/5, CodeMirror 5, Summernote) wrapped so #editor is the visible
container. Adds sibling-input regression coverage for IFRAME, CONTENTEDITABLE
and HIDDEN_TEXTAREA paths plus a negative test for hidden backing locators.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(fillField): cross-helper iframe detection + leak guard for hidden form controls

- WebDriver/BiDi rejects element refs as executeScript args from inside a
  switched-frame context, breaking the inner-iframe focus/select calls.
  Run those scripts via document.querySelector on the marker instead, then
  send keystrokes via the already-frame-aware page keyboard.
- Add EDITOR.UNREACHABLE: when the user's locator points at a display:none
  INPUT/TEXTAREA, throw a clear error instead of falling through. Without
  this, Puppeteer's lenient el.type() silently leaks to whatever has focus.
- Test reads outer-input value via executeScript to dodge a pre-existing
  WebDriver grabValueFrom bug that drops empty strings in forEachAsync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

4.0.0-rc.15

Toggle 4.0.0-rc.15's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Update publish-beta.yml

4.0.0-rc.14

Toggle 4.0.0-rc.14's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat: pauseOn plugin with debug modes and CLI plugin arguments (#5520)

* feat: add pauseOn plugin with multiple debug modes and CLI plugin arguments

Extend plugin system to support colon-separated CLI arguments (-p pluginName:arg1:arg2).
Add pauseOn plugin with 4 modes: fail, step, file (breakpoint), and url.
Add dedicated debugging documentation page.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: remove plugin arguments syntax from debugging page

Plugin CLI arguments is a system-wide spec, not debugging-specific.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Improved debugging

* fix: remove -p all special case from dry-run command

The 'all' magic keyword conflicted with the new colon-separated plugin
arguments syntax. Users can still enable specific plugins in dry-run via
-p pluginName1,pluginName2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

4.0.0-rc.12

Toggle 4.0.0-rc.12's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
refactor: eliminate internal globals in favor of store singleton (#5506)

* refactor: eliminate internal globals in favor of store singleton and direct imports

Replace all internal global variable usage with proper ESM patterns:
- Add store.initialize() method with immutable required fields (codeceptDir, outputDir, workerMode)
- Replace global.codecept_dir/output_dir reads with store.codeceptDir/outputDir (~55 sites)
- Replace global.container reads with direct container imports (~15 sites)
- Replace global.debugMode with store.debugMode in WebDriver/Puppeteer
- Replace process.env.RUNS_WITH_WORKERS with store.workerMode
- Replace process.env.FEATURE_ONLY/SCENARIO_ONLY with store.featureOnly/scenarioOnly
- Replace global.maskSensitiveData with store.maskSensitiveData
- Convert singleton guard globals to module-level variables
- Remove dead global.codecept_debug
- Keep user-facing DSL globals (Feature, Scenario, etc.) and backward compat writes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: restore global.Helper and global.codecept_helper lost in ESM migration

These were set in 3.x codecept.js but never ported to 4.x globals.js.
Needed for CJS helpers that use `const Helper = codecept_helper`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: deprecate globals, default noGlobals: true for new projects

- noGlobals: true skips ALL globals including Mocha DSL (Feature, Scenario, etc.)
- noGlobals: false (default for existing projects) prints deprecation warning
- New projects generated via `codeceptjs init` get noGlobals: true by default
- Store noGlobals flag in store so initMochaGlobals respects it

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve CI failures — circular deps, frozen workerMode, null paths

- Move tsFileMapping from container to store to break circular dep
  (step/base.js -> container.js -> step.js -> step/base.js)
- Use getters with global fallback for codeceptDir/outputDir so unit
  tests that set global.codecept_dir directly still work
- Stop freezing workerMode — it's set after initialize() by run-workers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve remaining CI failures

- Fix Map type annotation in store.js (TS error)
- getMaskConfig falls back to global.maskSensitiveData for unit tests
- Update ui_test.js to check store.featureOnly instead of process.env
- Remove Object.defineProperty freeze on paths — causes issues when
  unit tests share process and shard_test calls initialize() first
- Restore process.env.RUNS_WITH_WORKERS for cross-process communication
  (env vars cross process boundaries, store doesn't)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prefer global.codecept_dir over store._codeceptDir in getter

When unit tests set global.codecept_dir directly (without calling
store.initialize()), the getter must pick up the global value even
if _codeceptDir was set by a prior test in the same process.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: migrate utils_test to use store instead of globals

Revert getter priority to store-first (correct design). Fix the
test to set store.codeceptDir/outputDir instead of globals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: DavertMik <davert@testomat.io>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

3.7.8

Toggle 3.7.8's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
release 3.7.8 (#5511)