Ask: you specify the shape of a UI-flow spec, and we drive it
The agent side keeps hand-writing imperative flows against your test server, and I keep getting them subtly wrong in ways that are your knowledge, not mine. The most recent (CIRISAgent#1149, gate run 34067627803) is a fair sample: I drove btn_menu and then waited for menu_logout. btn_menu opens the ADVANCED category; menu_logout lives under GOVERNANCE. The click succeeded — clicks always do — and the run then timed out on an element that was never going to render.
Nothing was broken in your app. I encoded an assumption about containment that only you can state, and there is no artifact for you to state it in. Since 0.5.203 ships guaranteed tags and fields for everything, the missing piece is not tags — it is a contract for composing them into flows, and you should own its shape.
What the agent side needs, stated as requirements rather than a design
- Deterministic. A flow either runs or fails for a named reason. No step may "succeed" without having had its effect — the
btn_menu failure above is the anti-pattern.
- Containment is explicit. If
menu_logout requires btn_governance_menu open, that is a fact about your UI. Today it is discoverable only by reading CIRISApp.kt.
- Version-legible. 0.5.203 moved
AiPreferenceSection to the top of YOU. Our driver had it fourth. A flow written against 0.5.202 should fail loudly on 0.5.203, not drive the wrong order and pass.
- Reviewable by you. A flow diff should be readable in your PR without reading Python.
- Executable by us, on all five platforms, through the existing
/tree /click /input /wait surface.
Prior art, since you asked whether there is a standard
There is no single standard, but there is a clear best-in-class and a clear protocol:
- Maestro (mobile.dev) is the closest thing to a de facto standard for declarative mobile UI flows: YAML files of
- tapOn: / - inputText: / - assertVisible:, one flow per file, composable via runFlow. It is the shape most people mean by "declarative UI test". Worth reading even if we do not adopt it, because its step vocabulary is well-worn.
- W3C WebDriver (and WebDriver BiDi) is the actual standard for the protocol underneath — your test server is already a small, purpose-built version of it. If you ever want off-the-shelf tooling, matching its verb names costs nothing now and buys optionality later.
- Testing Library's guidance is the standard for addressing: prefer role + accessible name, fall back to a stable
data-testid. You have deliberately gone all-in on stable tags, which is the pragmatic choice for Compose Multiplatform; the tradeoff is that tags carry no semantics, so containment and preconditions have to be published separately.
- Page Object / Screenplay are the standard patterns for reuse. Our current Python flows are an ad-hoc Page Object; the pain is that the object model lives in our repo and the truth lives in yours.
My read: Maestro's step vocabulary plus something that publishes containment is the sweet spot. But you own the UI, so you should pick.
Three shapes, roughly in order of how much you own
A. You publish a tag registry; we keep writing the driving code. A generated JSON artifact in the client wheel: every guaranteed tag, its screen, its container and what must be open/true for it to be present. We validate our flows against it at lint time and fail CI when a flow names a tag that moved. Smallest change for you, and it would have caught the menu_logout bug at lint rather than in a live run.
B. You publish the registry AND own a flow DSL. YAML flows live in your repo next to the screens they drive; we execute them. A flow diff appears in the PR that changes the screen. Highest determinism, most work for you, and it means a screen reorder cannot land without its flow moving in the same commit.
C. We define the DSL, you review it. We write the runner and the format, you approve the vocabulary and own the registry. Middle ground, but the contract still lives in our repo, which is the thing that keeps going wrong.
I would take B if you have the appetite, A if you do not — and I would rather implement whichever you pick than have you conform to something I invented this afternoon.
One concrete thing regardless of shape
Whatever we land on, /tree failures should carry what is on the screen. When a wait times out we already have the element list; printing "you asked for menu_logout; this screen exposes btn_governance_menu, btn_menu, …" turns my class of bug from a 20-minute log dig into a line of output. That is cheap and useful even if nothing else changes.
Agent side for context: tools/qa_runner/modules/web_ui/__main__.py holds the current flows; the five-platform gate drives setup → login → chat per platform, and now a run-without-AI pass before it.
🤖 Generated with Claude Code
Ask: you specify the shape of a UI-flow spec, and we drive it
The agent side keeps hand-writing imperative flows against your test server, and I keep getting them subtly wrong in ways that are your knowledge, not mine. The most recent (CIRISAgent#1149, gate run 34067627803) is a fair sample: I drove
btn_menuand then waited formenu_logout.btn_menuopens the ADVANCED category;menu_logoutlives under GOVERNANCE. The click succeeded — clicks always do — and the run then timed out on an element that was never going to render.Nothing was broken in your app. I encoded an assumption about containment that only you can state, and there is no artifact for you to state it in. Since 0.5.203 ships guaranteed tags and fields for everything, the missing piece is not tags — it is a contract for composing them into flows, and you should own its shape.
What the agent side needs, stated as requirements rather than a design
btn_menufailure above is the anti-pattern.menu_logoutrequiresbtn_governance_menuopen, that is a fact about your UI. Today it is discoverable only by readingCIRISApp.kt.AiPreferenceSectionto the top of YOU. Our driver had it fourth. A flow written against 0.5.202 should fail loudly on 0.5.203, not drive the wrong order and pass./tree/click/input/waitsurface.Prior art, since you asked whether there is a standard
There is no single standard, but there is a clear best-in-class and a clear protocol:
- tapOn:/- inputText:/- assertVisible:, one flow per file, composable viarunFlow. It is the shape most people mean by "declarative UI test". Worth reading even if we do not adopt it, because its step vocabulary is well-worn.data-testid. You have deliberately gone all-in on stable tags, which is the pragmatic choice for Compose Multiplatform; the tradeoff is that tags carry no semantics, so containment and preconditions have to be published separately.My read: Maestro's step vocabulary plus something that publishes containment is the sweet spot. But you own the UI, so you should pick.
Three shapes, roughly in order of how much you own
A. You publish a tag registry; we keep writing the driving code. A generated JSON artifact in the client wheel: every guaranteed tag, its screen, its container and what must be open/true for it to be present. We validate our flows against it at lint time and fail CI when a flow names a tag that moved. Smallest change for you, and it would have caught the
menu_logoutbug at lint rather than in a live run.B. You publish the registry AND own a flow DSL. YAML flows live in your repo next to the screens they drive; we execute them. A flow diff appears in the PR that changes the screen. Highest determinism, most work for you, and it means a screen reorder cannot land without its flow moving in the same commit.
C. We define the DSL, you review it. We write the runner and the format, you approve the vocabulary and own the registry. Middle ground, but the contract still lives in our repo, which is the thing that keeps going wrong.
I would take B if you have the appetite, A if you do not — and I would rather implement whichever you pick than have you conform to something I invented this afternoon.
One concrete thing regardless of shape
Whatever we land on,
/treefailures should carry what is on the screen. When a wait times out we already have the element list; printing "you asked formenu_logout; this screen exposesbtn_governance_menu,btn_menu, …" turns my class of bug from a 20-minute log dig into a line of output. That is cheap and useful even if nothing else changes.Agent side for context:
tools/qa_runner/modules/web_ui/__main__.pyholds the current flows; the five-platform gate drives setup → login → chat per platform, and now a run-without-AI pass before it.🤖 Generated with Claude Code