Tags: codeceptjs/CodeceptJS
Tags
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>
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>
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>
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>
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>
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>
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>
PreviousNext