Replay Without Pretending the UI Is Deterministic
This chapter examines how tester-army/e2e reuses a verified agent flow without turning it into a blind macro. The focus is the replay lifecycle: what identifies a trace, how its targets are relocated against a fresh observation, how the end state proves that the action had an effect, and when control returns to the live agent. The practical judgment is when replay saves model calls and when a changed or uncertain UI should force a re-record or a human review.
The engine boundary makes replay possible because web and mobile surfaces produce a shared semantic model. But shared semantics do not make the UI static. A browser may re-render a row, a device may mint new observation references, a route may load more slowly, or a saved value may be different on the next run. e2e therefore treats a trace as a context-bound claim about an action and its visible consequence, not as a sequence of clicks that is valid forever.
A trace is a claim about context
A replay entry starts with more identity than the natural-language instruction. Its key includes the project and test, the target and platform, the engine name and compatibility version, the engine contract version, the application identity, the instruction and parameter digests, the occurrence of that call, the configured agent, its redacted context, the cache schema, and the policy version. The point is not to make cache keys intimidating. It is to prevent one recorded behavior from silently crossing a boundary that can change what the behavior means.
An instruction such as “open the settings” can be identical across two apps while referring to different controls. A route can be the same in two preview deployments while the application build differs. A configured agent context can explain that one product calls a plan “Pro” while another calls it “Professional.” An engine minor version can alter how a semantic node is resolved. If these contexts share a trace merely because the sentence is the same, the cache can replay a mechanically valid action against the wrong surface.
The key also separates repeated calls. Two identical agent.act instructions in one test may perform different steps at different times. The occurrence of the same request is part of the identity, so an optional dialog or a conditional branch does not automatically renumber unrelated traces. Parameters are digested after projection, and secrets contribute stable names rather than plaintext. A unique() value can be templated into a trace slot so a fresh run-unique name does not force the entire flow to miss, while a value derived from the live screen can be treated as a gap rather than replayed as if it were still valid.
This narrow identity is a cost. Changing an engine version, app identity, agent context, or policy can cold-start entries that might have worked mechanically. That is intentional. Replay is an optimization, so a miss is safer than a false match. The cache should spend a model call when the context that gives an action its meaning has changed.
The trace itself records committed grammar actions and the target descriptors needed to relocate them. It can carry a start and end path, viewport information, action summaries, and a delta describing what appeared or disappeared. It does not simply store the model’s entire conversation. It stores enough evidence to re-execute the action grammar against a current observation and enough postcondition information to check whether the recorded effect returned.
The key and the payload solve different problems. The key asks whether this is the same request in the same execution context. The payload asks whether the current app can still reproduce the route, targets, actions, and visible effect. A key hit is therefore only an invitation to attempt replay. It is not a verdict that the trace is still valid.