feat(cache): scope first and last replay to a test id
- Let a changing list replay by an explicitly requested first/last match instead of the recorded row's content or count.
- Scope the option to one test id, retaining its edge across re-rendered handles. Validate matching node actions and drag endpoints before input; refuse incomplete observations.
- Separate positional cache keys, occurrence counts and provenance. Ordinary keys, numeric positions and end-state verification keep their existing behavior.
await agent.act('open the first inbox item', {
position: { testId: 'inbox-item', at: 'first' },
});
flowchart LR
K[Position-scoped cache key] --> R{Recording?}
R -- yes --> T[Resolve test id, role and edge]
T --> V[Existing end-state verification]
R -- no --> E[Live executor]
Closes #1225.
Maintainer decision: is position: { testId, at } the preferred API? Naming the test id keeps the option scoped when a step also acts on other controls. Order covers observed matches, so separate lists need distinct test ids and unloaded rows do not participate.
Verified
Ran it locally: yes
Built CLI against a local Chromium inbox fixture:
e2e run --config e2e.config.ts --reporter list,markdown
first/last cold recordings 2 passed
changed, single, reordered, retained-old-row 8 replayed
ordinary/first/last, cold and warm in one cache 6 passed
first/last re-rendered immediately before click 2 passed
The eight changing-list replays opened the expected row with zero executor invocations. These recordings were produced through the public API, without editing cache files. Cold runs use a scripted executor to count fallbacks.
| Case | main | this branch |
|---|---|---|
| Changing rows, ordinary call | target-not-found, executor runs | Same conservative handoff |
| Explicit positional option | INVALID_ARGUMENT, unsupported option | 16 CLI checks passed |
| Unchanged ordinary recording | Replays | Replays with the same key |
Chromium integration tests also cover mixed-action steps, key isolation under strict cache, empty lists, contradictory choices, truncated observations, both drag endpoints, re-renders during input and between batched actions, and content-dependent end-state handoff. Testbed and deterministic web benchmark pass. The reference page was rendered locally.
Committed control-inventory recordings, --strict-cache, six paired runs:
| Metric | main | this branch | Paired change |
|---|---|---|---|
| Suite attempts | 7.49 s | 7.42 s | -0.9%, 95% CI -1.4% to -0.3%; same within ±5% |
All step outcomes and counters matched; no performance claim.
The original Android flow was not run locally. An uncached model run stopped at AI Gateway authentication before any tool call because credentials are unavailable. The docs screenshot remains local because the installed GitHub CLI 2.92 cannot attach it.