From 7cb8b7d2697ded294f9a9cd1f3808926aea0c4f1 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Fri, 1 May 2026 13:00:35 -0400 Subject: [PATCH 1/2] docs(ios-ui-tests): add Patterns and pitfalls section for terminate-after-navigation race Documents the launchd race that PR #319 fixed: terminating right after a navigating tap can hang the simulator with "Failed to terminate ... :0". Records the canonical "direct .tap() + explicit waitForExistence on root.currentView." pattern, and explains when the existing tap(_:thenWaitForRoot:in:) helper is appropriate vs when its tapByStableCenter gating silently skips the tap. Closes #320 Co-Authored-By: Claude Opus 4.7 --- ios/StillPointAppUITests/README.md | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ios/StillPointAppUITests/README.md b/ios/StillPointAppUITests/README.md index 9a80a242..2022cdf6 100644 --- a/ios/StillPointAppUITests/README.md +++ b/ios/StillPointAppUITests/README.md @@ -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: + +``` +Failed to terminate : +``` + +The fix is to wait for the destination's `root.currentView.` 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.`. + +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. + +**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`). From 811b19e1408d7f47f39643f25922e298af0afec8 Mon Sep 17 00:00:00 2001 From: auerbachb Date: Fri, 1 May 2026 14:04:20 -0400 Subject: [PATCH 2/2] docs(ios-ui-tests): address CodeRabbit nits on Patterns and pitfalls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add `text` language tag to the unlabeled fenced block showing the Failed-to-terminate output (markdownlint-friendly). - Cite the passing CI run alongside the failing one to document both ends of the d9e69aa → ddf7b69 transition. Co-Authored-By: Claude Opus 4.7 --- ios/StillPointAppUITests/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/StillPointAppUITests/README.md b/ios/StillPointAppUITests/README.md index 2022cdf6..700e916d 100644 --- a/ios/StillPointAppUITests/README.md +++ b/ios/StillPointAppUITests/README.md @@ -73,7 +73,7 @@ Planned lane: `ios-ui-smoke` 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 : ``` @@ -105,7 +105,7 @@ The `tap(_:thenWaitForRoot:in:)` helper in [`StillPointAppUITests.swift`](StillP 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.`. -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. +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:**