feat(ios): pair plain and honor-modal AX reads in the alert surface corpus

Merged
TH
4h ago
+3605-640

Summary

PR 1 of 3 for modal-scoped alert. No behavior change for alert or snapshot.

  • The runner reads one app three times: honor-modal (snapshotKeyHonorModalViews), plain, honor-modal again. Each node carries its AX identity and its parent's. Pairing is linear on (element, parent). Typed outcomes cover unsupported, failed, and unstable (truncated, unmatched, reparented, moved, screen changed).
  • Corpus format 2 records both trees, the read order, timings, and the toolchain. All fixtures are re-recorded. New: Settings page sheet, Settings without a modal. The sheet's expect pins today's dismissPopup output.
  • Finding: XCUIApplication.snapshot() equals the honor-modal tree on all 7 recordings, so expect replays on honorModal.
  • The capture runner has its own bundle id, so it runs beside a session runner.

20 files.

Validation

check:affected --run --head f7d69df7b9 passed. Host swift test 36/36. iOS and macOS runner builds pass.

iPhone Duo, iOS 27.1. Nodes plain/honor; ms honor/plain/confirm:

ScenarioNodesms
Settings, no modal237/237144¹/40/40
Settings sheet426/8621/104/14
SpringBoard alert, folded248/4114/44/12
SpringBoard alert, unfolded243/4013/38/10
SpringBoard, no alert337/33757/48/45
Safari prompt, folded85/6729/15/22
Safari prompt, unfolded93/7516/15/12

¹ Warm-up read.

Positive control: each prompt removes nodes; each screen without one removes none (asserted per recording).

Not verified: physical devices, iOS before 27.1, iPad popovers, React Native Modal, SFSafariViewController.

🤖 Generated with Claude Code