Repository navigation
journey-runner.js cannot express a hand-over journey, a toggle set to a known state, an expected ERROR span, or a row-scoped click (harvest from a field-run copy) #148
Description
What happens
The toolkit runner (project-tests/e2e/journey-runner.js) signs in once and has no vocabulary for several things a real multi-role journey needs. A field build patched its installed copy (a ~214-line diff) and ran its journeys green with it. This issue is the harvest request: port the vocabulary, not the project file.
The gaps the toolkit runner still has today:
| Gap | What goes wrong without it | Local extension that closed it |
|---|---|---|
| Hand a walk to another role | "Admin arms a fault, a second role looks, a third decides" cannot be one journey | signInAs {role}: H.logout, then H.loginAs with project.config.js roles[role], or "persona" to return to TEST_USER. Credentials are never read from the journey file. |
| Set a toggle to a known state | A pluggable switch is a toggle, so click depends on the start state. A dialog that reopens with the last run's settings made a second run turn a switch off by "turning it on". |
switch {widget, on}: click only if aria-checked differs, then read it back and FAIL if it still differs |
| A step that injects an error on purpose | Every step after an armed fault FAILs rung 2's "no ERROR at any level" by design | spans.expectErrors: [regex]: each regex must match at least one ERROR span over <op> | <error text> | http <status>. Every other ERROR still fails. |
| A grid repeats one widget name per row | First-match .mx-name-<w> clicks whichever row renders first |
click / switch gain row (text of one row, innermost match last()), in (scope to one grid) and target (child selector), via one scoped() helper |
| Radio option | No act | radio: choose an option by caption, then read the checked option back |
| Mendix confirmation dialog | No act | confirm {button, says?}: says goes through subst() like button. The first cut did not do that and took {{run}} literally. |
| System-to-system call | No act | request {method, path, body, auth:{userEnv,passEnv}, expectStatus, expectText}: missing env credentials or an unreachable URL is an InstrumentFault |
| A step that deletes rows on purpose (fixture reset) | Every later delta is measured from a baseline count that no longer exists | data.rebase: [entity] re-takes the baseline after the step |
| Effects that land on the next engine/scheduler pass | A single read FAILs a claim that is true 5 s later | withinMs / everyMs on OQL checks (checkOqlWithin), and spans.withinMs (stretch capture retries). Mutants delete withinMs, because a floor that is never reached is not reached by waiting. |
| Separate findings file per run | Controls overwrite the real walk's findings | --out=<file.json> |
| Faults vs findings | A missing credential read as FAIL | class InstrumentFault → the step's action row is INVALID, never FAIL |
The numeric mustPointAt sentinel (-424242) from the same patch set is already in the toolkit runner.
Evidence (card-disbursement requirements-driven build, 2026-09-26/27)
- Two hand-over journeys ran 97/97 and 80/80 (92/92 after
sayswent throughsubst()). - A mutant
expectErrorsregex that matches nothing fails its step. - The
switchread-back caught the second-run toggle inversion described above. The journey had gone red on its second run only. - Admin-console journeys with
radio/confirm/request/rowran 57/0/0 and 52/0/0.
Suggested fix
- Port the acts, the
scoped()helper,InstrumentFault,data.rebase,withinMs,spans.expectErrorsand--out=intoproject-tests/e2e/journey-runner.js. - Add
loginAstohelpers.js; it haslogin/logout/loginOncetoday. - Document the schema in
skills/journey-proof.md. - Extend
--positive-controlso each new claim kind has a mutant:expectErrors→ a regex that matches nothing;switch→ inverton;confirm.says→ a string the dialog does not say.
The field-proof bar needs a real journey run on a scratch app. That is why this is filed rather than ported directly. bin/harvest-learnings.sh will also pick up the locally patched installed script.
Files
project-tests/e2e/journey-runner.jsproject-tests/e2e/helpers.jsproject-tests/e2e/project.config.template.js(roles)project-tests/e2e/journey-runner.selftest.jsskills/journey-proof.md
Related: the persona/TEST_USER refusal and negative-only span claims are filed separately.
Activity
process(contrib/inbox): delete seven triaged 2026-09-26 notes
Field harvest from a requirements-driven build: 26 instrument fixes, …
new(journeys): row-scoped click — `row` and `in` on click (#148, one …
fix(journeys): a row-scoped click matches whole words and refuses an …
Bug sweep 2026-10-26: gates, runners, doctor, lint receipt, git hooks…
MendixMau commentedon Oct 26, 2026
One slice shipped in #578 (squash a088b4e):
clicktakesrowandin. The row must show the text as whole words, and an ambiguous match throws. The rest (signInAs,switch,expectErrors, radio, confirm,request,rebase,withinMs) needs control mutants and a scratch-app field run each. It's on the roadmap (#579); next isswitchwith its read-back.Generated by Claude Code