Commit Graph
20 Commits
Author SHA1 Message Date
pj b19595c5c8 test(replay-ui): dedup step/summary fixtures into shared module 2026-06-06 13:44:32 +05:30
pj 1a9f0430bd test(replay-ui): pin metrics formatters and path gap handling 2026-06-06 13:07:43 +05:30
pj ead5262b18 test(replay-ui): pin keyboard-nav ownership and key routing 2026-06-06 13:07:43 +05:30
pj f934581b58 test(replay-ui): pin clampIndex step boundaries 2026-06-06 13:07:43 +05:30
pj 2702b158d8 refactor(replay-ui): extract metrics formatters into lib module 2026-06-06 13:07:43 +05:30
pj 153f17c756 refactor(replay-ui): extract keyboard-nav dispatch into pure module 2026-06-06 13:07:43 +05:30
pj e03da83cca refactor(replay-ui): export clampIndex for testing 2026-06-06 13:07:43 +05:30
pj e26100acf5 test(replay-ui): pin run-history derivation alignment 2026-06-06 13:07:43 +05:30
pj ae5ca403fb test(replay-ui): pin shared statusFor precedence and ordering 2026-06-06 13:07:43 +05:30
pj 9219c529f3 refactor(replay-ui): extract run-history derivation into lib module 2026-06-06 13:07:43 +05:30
pj f0b687f40e refactor(replay-ui): share one statusFor between panels 2026-06-06 13:07:43 +05:30
pj 3fa52b3a65 test(replay-ui): pin action selector parse and row formatting 2026-06-06 13:07:43 +05:30
pj a4d6020663 refactor(replay-ui): extract action selector/format into lib module 2026-06-06 13:07:43 +05:30
pj 3541ab44d8 test(replay-ui): pin snapshot flatten/getAtPath path round-trip 2026-06-06 13:07:43 +05:30
pj 441537a7cb refactor(replay-ui): extract snapshot flatten/getAtPath into lib module 2026-06-06 13:07:43 +05:30
pj 8f817bd8ff test(replay-ui): exercise real URL encoding and non-ok throw in getJson 2026-06-06 13:07:43 +05:30
pj 5d1d083f57 test(replay-ui): add bun test script 2026-06-06 13:07:43 +05:30
pj 410602d2e1 Fix action-system audit findings (#60)
* fix(replay-ui): size overlay viewBox from hierarchy root bounds

Tap points are recorded in the hierarchy's coordinate space (iOS points,
Android pixels, web CSS px) while screenshots are device pixels, so the
overlay rendered at 1/3 position on iOS 3x screens. Derive the viewBox
from the root element bounds; natural image size stays the fallback.

* fix(runner): derive trace tap point from resolveCoordinates

stampSelectorTarget preferred possibly-stale action X/Y while dispatch
preferred the fresh tree-resolved center, so the trace could record a
different point than the one tapped. Both now share resolveCoordinates.

* fix(runner): settle after InputText focus tap before key events

The focus tap raises the keyboard; with no settle the keyboard
animation races the erase/type key events on iOS, landing them in the
wrong field or dropping them. Wait for idle after a successful focus
tap, bounded by the run's idle timeout.

* fix(driver): skip pre-erase for replace-on-input drivers

The web driver's InputText already replaces content via select-all, so
the runner's unconditional EraseText was a redundant round-trip on
every InputText. A new optional TextReplacer capability lets a driver
assert replace semantics; the runner skips the erase when asserted.

* fix(hierarchy): rank spatial-fallback matches by specificity

The bounds-containment fallback returned the first pre-order match, so
a screen-sized container could win over the intended small element.
Matches are now ordered smallest-area first; equal-area matches keep
pre-order, preserving the iOS-flat equal-bounds sibling pattern.

* fix(runner): treat an unchanging transitional tree as settled

A UI persistently showing two route-level Screen ids (overlay, both
route ids alive at rest) burned the full retry budget every step and
skipped the verifier forever. A tree byte-identical to the previous
attempt now breaks the retry loop as settled; genuine cross-fades
differ between attempts and keep the retry/skip behavior.

* fix(replay-ui): skip synthetic zero-bounds root in deviceSpaceOf

The iOS hierarchy prepends a zero-bounds node before the real root
window, so elements[0] returned undefined and the overlay fell back to
the screenshot's pixel size. Take the first element with positive
extent instead; pre-order puts the root window before any content.
Verified against a real iOS trace in the replay UI.

* fix(sidecar): never replay non-idempotent actions after reconnect

A dropped connection mid-action (e.g. a read timeout while the device
is still typing) re-ran the whole block after reconnecting, typing the
text twice and double-firing taps. Non-idempotent actions now reconnect
for the next RPC's benefit but surface UNAVAILABLE, which the runner
already treats as transient; idempotent reads keep the replay.

* fix(sidecar): land the second double-tap sequentially on gesture collision

The overlapped second tap can hit the XCTest runner while the first
gesture is still executing ('only one gesture can be performed at a
time'), failing the step. The second tap now waits the first out and
retries once, keeping the tight gap on the happy path.

* fix(sidecar): map non-Exception throwables to INTERNAL status

The vendored iOS client throws failures that do not extend Exception;
runRpc missed them, killing the RPC as a channel-level Unknown the
runner cannot classify. Catch Throwable instead.

* feat(sidecar): close the driver and app under test on shutdown

* test(sidecar): cover service shutdown paths

* fix(testrun): stop the sidecar with SIGTERM before killing

* fix(sidecar): reap orphaned XCTest runner sessions at iOS init

* fix(sidecar): probe channel liveness before restarting the XCTest runner

* test(sidecar): cover WdaRecovery restart and retry policy

* fix(sidecar): absorb first-leg double-tap collision sequentially

* fix(runner): scope WDA-drop detection and cap consecutive transient failures

* chore(sidecar): silence vendored loggers on expected failure paths

* fix(runner): absorb one-off apply errors; only an unbroken streak aborts

* fix(folio): install the current build before the Android fuzz run

* chore(sidecar): silence absorbed view-hierarchy poll noise in Android runs

The driver logs an ERROR for every on-device view-hierarchy fetch that the
device-side server cancels or times out while the UI animates. The stability
poll fetches the hierarchy on a sub-second cadence and swallows those throws
to keep polling, so each line is advisory with no effect on the run. Real
failures still reach the runner as gRPC status errors, so nothing is lost.
2026-06-06 12:21:11 +05:30
pj 9958b0ddc8 Attribute violations to the causing step and render witness evidence (#59)
* feat(ltl): attribute violations to the obligation origin step

* feat(verifier): label evaluator observations with the runner step index

* feat(trace): carry the causing step in violation witnesses and summary

* feat(replay): move the violation marker to the causing step

* feat(replay-ui): render witness evidence in the violations panel

* feat(replay-ui): wire witnesses and step jump into violation panels

* fix(ltl): treat next obligations as vacuous at run end

* fix(runner): give the finalize trace record its own step index
2026-06-05 23:46:36 +05:30
pj b44077afde replay ui fix (#56)
* refactor: rename inspect to replay across the codebase

Renames inspect-ui/ to replay-ui/, internal/inspect/ to internal/replay/,
the CLI subcommand from `sanderling inspect` to `sanderling replay`, and
updates all references in docs, Makefile, README, and Go comments.

* feat(replay-ui): show spec filename with full path on hover

RunList and RunDetail now render the basename of spec_path (e.g.
login.spec.ts) with the full path available as a title tooltip.
2026-06-03 16:17:26 +05:30