Claude Code UI Testing: Closing the Verification Gap
Claude Code can write the UI, but it cannot see whether the UI works. The verification gap is why agentic frontend changes regress — and how CUIT closes the loop.
When you ask Claude Code to change a button, a form, or a drag-and-drop interaction, it does something remarkable: it edits the right files, wires the handlers, and updates the types. Then it tells you it is done. And it has no idea whether the thing it built actually works.
This is the verification gap, and it is the single biggest reason agentic frontend changes feel unreliable. The model is excellent at *producing* a diff and nearly blind to the *effect* of that diff in a running browser.
Why Claude Code UI testing is harder than backend testing
For backend code, the loop is already closed. The agent writes a function, runs the unit test, reads the failure, and iterates. The feedback is text, the runner is deterministic, and the result lands back in the context window in seconds.
UI is different in three ways:
- The output is visual and stateful. "The modal opens, the focus traps, the overlay dims" is not something a type checker or a Jest assertion captures.
- The failure modes are interaction-shaped. A drag that snaps to the wrong segment, a debounce that fires twice, a hydration mismatch that only appears after the second click — none of these show up in the source.
- There is no native runner in the loop. Claude Code has no eyes on the DOM unless you give it some.
So the agent falls back on the only signal it has: the diff looks plausible, therefore the task is complete. That is reasoning by inspection, not by verification.
What "closing the loop" actually means
A closed loop has four phases, and the agent must traverse all four without a human in the middle:
- Act — make the change.
- Observe — run the UI and capture what happened (DOM state, console errors, network, the actual interaction trace).
- Judge — compare observed behavior against the intended outcome.
- Iterate — if the judgment is "not yet," go back to step one with the new evidence in context.
Most "AI testing" tooling stops at step one and hand-waves step two. The agent generates a Playwright spec, the spec is never run inside the same conversation, and the green checkmark you imagine never actually executes. The loop stays open.
How CUIT closes it
CUIT (complex-ui-tester) gives Claude Code the missing observe and judge phases as native MCP tools:
# Inside a Claude Code conversation, after a UI change:
/cuit-loop session.jsonThe recorder captures a real interaction session — clicks, drags, form input, plus console logs and network requests. @cuit/spec-gen turns that session into a Playwright spec grounded in the recorded events. CUIT then *runs* the spec against your app and feeds the result — pass, fail, the failing assertion, the console error — back into the conversation.
Now the agent has evidence. If the drag snapped to the wrong segment, the agent sees the assertion failure and the DOM state, not a vibe. It iterates against reality.
The loop is the product. Models will churn; the durable layer is the feedback mechanism that lets any model see whether its UI change worked.
The pragmatic takeaway
You do not need Claude Code to be clairvoyant. You need to stop asking it to verify UI by reading source. Give it a runner, give it the recorded session, and let it judge against the executed result. That is the difference between "the diff looks right" and "the regression gate is green."
If your agent is shipping frontend changes today with no executed UI check in the loop, the verification gap is already costing you regressions — you just are not seeing them until a user does.