Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions ios/StillPointAppUITests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,51 @@ Planned lane: `ios-ui-smoke`
- **Cross-reference #155/#156:** reuse shared simulator boot, xcodegen generation, and cache priming steps from those issues so iOS lanes do not duplicate setup work unnecessarily.
- Keep auth/session fixture bootstrapping in app-side launch environment (this folder) rather than cloning separate backend setup in each lane.

## Patterns and pitfalls

### Wait for the destination root before terminating after navigation

If a test triggers navigation (taps a button that pushes a new screen, starts audio, kicks off any non-trivial transition) and then calls `app.terminate()`, the simulator's `launchd` on macos-26 / iOS 26 sometimes loses track of the process and hangs for tens of seconds before reporting:

```text
Failed to terminate <bundle>:<pid>
```

The fix is to wait for the destination's `root.currentView.<slug>` element to exist *before* terminating, so SIGTERM hits a quiescent process.

**Do this:**

```swift
beginButton.tap()
XCTAssertTrue(
app.otherElements["root.currentView.session"].waitForExistence(timeout: 20),
"Session screen did not appear after Begin tap"
)
app.launchEnvironment["SP_UI_TEST_SEED_AUTH"] = "1"
terminateAppReliably(app)
```

**Don't do this:**

```swift
beginButton.tap()
// terminate races launchd while audio engine + timer are still spinning up
terminateAppReliably(app)
```

### When `tap(_:thenWaitForRoot:in:)` is fine vs when it bites

The `tap(_:thenWaitForRoot:in:)` helper in [`StillPointAppUITests.swift`](StillPointAppUITests.swift) is a one-liner for "tap, then wait for the destination root". Tests like `testRotationDecisionSessionRemainsUsableInLandscape` use it successfully. Internally it composes `tapByStableCenter` (an 8s stable-frame check) with a 12s root-existence wait, retried up to 3 times.

The trap: in flows where the source element's frame is briefly unstable (e.g. immediately after the home screen mounts following a fresh login), `tapByStableCenter` returns `false` *without ever tapping* — the helper then waits 12s for a root that never appears, retries, and ultimately asserts `Expected tap to transition to root.currentView.<slug>`.

Concrete case: the v1 commit (`d9e69aa`) of [#319](https://github.com/auerbachb/still-point/pull/319) used the helper for the post-login `home → session` transition in `testLaunchLoginCompleteSessionAndHistoryPersistence` and failed CI on [run 25218186115](https://github.com/auerbachb/still-point/actions/runs/25218186115). The shipping fix (`ddf7b69`) kept the direct `.tap()` and added an explicit `waitForExistence` for the destination root — verified passing on [run 25218888601](https://github.com/auerbachb/still-point/actions/runs/25218888601).

**Rule of thumb:**

- **Already-quiescent flows** (authenticated boot, lighter session config, no fresh transition in flight): `tap(_:thenWaitForRoot:in:)` is fine.
- **Right after a transition** (fresh login → home → tap, layout/animation likely still settling): use direct `.tap()` + explicit `waitForExistence` for the destination root.

## VoiceOver smoke steps (manual fallback)

1. Enable VoiceOver in the simulator (`Settings -> Accessibility -> VoiceOver`).
Expand Down
Loading