Stabilize iOS E2E after quick minute rebase#278
Conversation
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@cursor review |
|
@CodeAnt-AI review |
|
CodeAnt AI is running the review. |
|
@graphite-app re-review |
Sequence DiagramThis diagram shows how the PR hardens iOS UI tests by adding a retrying tab navigation helper that waits for the destination screen, and by changing the sessions failure test to assert an auth-screen status message when launching offline. sequenceDiagram
participant Test as iOS UITest
participant App as iOS App
participant TabHelper as Tab navigation helper
Test->>TabHelper: Open progress tab and wait for history screen
loop Up to 3 attempts
TabHelper->>App: Tap progress tab button
App-->>TabHelper: Report if history screen visible
end
TabHelper-->>Test: History screen confirmed
Test->>App: Launch with offline and sessions failure flags
App-->>Test: Show auth root screen with status message
Test->>App: Read status message text
App-->>Test: Message indicates failure or connection issue
Generated by CodeAnt AI |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit a7d7f43. Configure here.
| let message = app.staticTexts["authView.launchAuthStatusMessage"] | ||
| XCTAssertTrue(message.waitForExistence(timeout: 8)) | ||
| XCTAssertTrue(message.label.localizedCaseInsensitiveContains("failed") | ||
| || message.label.localizedCaseInsensitiveContains("connection")) |
There was a problem hiding this comment.
Sessions failure test now tests offline auth instead
Medium Severity
Adding forceLaunchOffline: true to testSessionsFailureShowsVisibleRetryMessage causes the app to fail during the uiTestMe() auth check (throwing "No internet connection") before it ever reaches uiTestGetSessions(), where forceSessionsFailure is evaluated. The forceSessionsFailure: true flag is effectively dead code here. The test now checks the auth-screen offline error message — making it a near-duplicate of testLaunchOfflineShowsUserVisibleMessage — rather than verifying the sessions API failure UX it was designed for.
Reviewed by Cursor Bugbot for commit a7d7f43. Configure here.
|
|
||
| app.terminate() | ||
|
|
||
| let relaunch = makeApp( |
There was a problem hiding this comment.
Relaunch removed so persistence is no longer tested
Medium Severity
The terminate-and-relaunch cycle was removed from testLaunchLoginCompleteSessionAndHistoryPersistence, so the test no longer verifies that history data survives an app restart. It now just checks same-session tab navigation. The assertion message still says "after relaunch" and the test name still claims to cover "Persistence", but the persistence-across-launches scenario is gone. The README also still lists "relaunch history persistence" as covered.
Reviewed by Cursor Bugbot for commit a7d7f43. Configure here.
| let app = makeApp( | ||
| seedAuthenticated: true, | ||
| resetStore: false, | ||
| forceLaunchOffline: true, | ||
| forceSessionsFailure: true | ||
| ) | ||
| app.launch() | ||
|
|
||
| waitForRoot("home", in: app, failureMessage: "Home screen did not appear") | ||
| openTab(identifier: "tab.progress", in: app) | ||
|
|
||
| let errorLabel = app.staticTexts["history.errorMessage"] | ||
| XCTAssertTrue(errorLabel.waitForExistence(timeout: 8)) | ||
| XCTAssertTrue(errorLabel.label.localizedCaseInsensitiveContains("failed") | ||
| || errorLabel.label.localizedCaseInsensitiveContains("connection")) | ||
| waitForRoot("auth", in: app, failureMessage: "Auth screen did not appear") | ||
| let message = app.staticTexts["authView.launchAuthStatusMessage"] | ||
| XCTAssertTrue(message.waitForExistence(timeout: 8)) | ||
| XCTAssertTrue(message.label.localizedCaseInsensitiveContains("failed") | ||
| || message.label.localizedCaseInsensitiveContains("connection")) |
There was a problem hiding this comment.
🟠 Architect Review — HIGH
testSessionsFailureShowsVisibleRetryMessage now sets both forceLaunchOffline and forceSessionsFailure but only waits for the auth-screen launch message and never navigates to Progress/History, so the sessions-load failure path and HistoryView error UI are no longer exercised and SP_UI_TEST_FORCE_SESSIONS_FAILURE is effectively untested at the UI level.
Suggestion: Drop the launch-offline flag in this test and navigate to the Progress tab so that a forced sessions failure drives the HistoryView error state (history.errorMessage), or split launch-offline and sessions-failure into two separate focused tests.
Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 254:266
**Comment:**
*HIGH: `testSessionsFailureShowsVisibleRetryMessage` now sets both `forceLaunchOffline` and `forceSessionsFailure` but only waits for the auth-screen launch message and never navigates to Progress/History, so the sessions-load failure path and HistoryView error UI are no longer exercised and `SP_UI_TEST_FORCE_SESSIONS_FAILURE` is effectively untested at the UI level.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| let app = makeApp( | ||
| seedAuthenticated: true, | ||
| resetStore: false, | ||
| forceLaunchOffline: true, | ||
| forceSessionsFailure: true | ||
| ) |
There was a problem hiding this comment.
Suggestion: This configuration makes the sessions-failure path unreachable: forceLaunchOffline is checked first during auth bootstrap and throws immediately, so forceSessionsFailure is never exercised. Remove forceLaunchOffline in this test (or split into separate tests) so the code path that fails session loading can actually run. [logic error]
Severity Level: Major ⚠️
- ⚠️ Sessions-failure UI path never exercised in `testSessionsFailureShowsVisibleRetryMessage`.
- ⚠️ `SP_UI_TEST_FORCE_SESSIONS_FAILURE` only used by history loading.
- ⚠️ Duplicate coverage of launch-offline auth error with `testLaunchOfflineShowsUserVisibleMessage`.Steps of Reproduction ✅
1. Run the UI test `testSessionsFailureShowsVisibleRetryMessage` in
`ios/StillPointAppUITests/StillPointAppUITests.swift:253-267`.
2. `makeApp(...)` at `StillPointAppUITests.swift:254-259` sets
`SP_UI_TEST_FORCE_LAUNCH_OFFLINE=1` and `SP_UI_TEST_FORCE_SESSIONS_FAILURE=1` in the app
launch environment via `makeApp` at `StillPointAppUITests.swift:269-287`.
3. On app launch, `RootView`'s `.task` calls `AppViewModel.checkAuth()` at
`ios/StillPointApp/ViewModels/AppViewModel.swift:6,22`, which invokes
`APIClient.shared.me()`; in UI-test mode this calls `uiTestMe()` at
`ios/StillPointShared/Sources/StillPointShared/APIClient.swift:374-397`.
4. Inside `uiTestMe`, `uiTestConfig.forceLaunchOffline` is checked first at
`APIClient.swift:380-382` and throws `APIError(status:0,"No internet connection")`, which
`checkAuth()` maps to `authStatusMessage` at `AppViewModel.swift:37-42`. The
sessions-failure flag is only read in `uiTestGetSessions()` at `APIClient.swift:468-477`
(used by `getSessions()` at `APIClient.swift:174-179` and `HistoryViewModel.load()` at
`HistoryViewModel.swift:34-46`), but those methods are never called in this test, so the
sessions-failure path remains unexercised.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 254:259
**Comment:**
*Logic Error: This configuration makes the sessions-failure path unreachable: `forceLaunchOffline` is checked first during auth bootstrap and throws immediately, so `forceSessionsFailure` is never exercised. Remove `forceLaunchOffline` in this test (or split into separate tests) so the code path that fails session loading can actually run.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| waitForRoot("auth", in: app, failureMessage: "Auth screen did not appear") | ||
| let message = app.staticTexts["authView.launchAuthStatusMessage"] | ||
| XCTAssertTrue(message.waitForExistence(timeout: 8)) | ||
| XCTAssertTrue(message.label.localizedCaseInsensitiveContains("failed") | ||
| || message.label.localizedCaseInsensitiveContains("connection")) |
There was a problem hiding this comment.
Suggestion: This test is asserting the auth-screen launch message, but session-load failures are surfaced from HistoryViewModel in the history screen (history.errorMessage), not authView.launchAuthStatusMessage. As written, the test validates the wrong UI contract and can pass while never proving that the retry/error UI for sessions is visible. [logic error]
Severity Level: Critical 🚨
- ⚠️ Sessions failure retry UI in `HistoryView` never asserted.
- ⚠️ Test only verifies generic auth bootstrap error message.
- ⚠️ Regression in `history.errorMessage` could ship untested.Steps of Reproduction ✅
1. Run `testSessionsFailureShowsVisibleRetryMessage` in
`ios/StillPointAppUITests/StillPointAppUITests.swift:253-267`.
2. The test waits for the auth root and then queries `authView.launchAuthStatusMessage`
via `app.staticTexts["authView.launchAuthStatusMessage"]` at
`StillPointAppUITests.swift:262-263`, asserting it contains "failed" or "connection" at
`StillPointAppUITests.swift:265-266`.
3. In the app, the auth launch status message is driven by
`AppViewModel.authStatusMessage` (set in `checkAuth()` at
`ios/StillPointApp/ViewModels/AppViewModel.swift:22-43`) and displayed in `AuthView` at
`ios/StillPointApp/Views/AuthView.swift:40-46` with identifier
`authView.launchAuthStatusMessage`, used for auth/bootstrap failures like offline or
token-expired.
4. Session-load failures, including those caused by `SP_UI_TEST_FORCE_SESSIONS_FAILURE`,
surface through `HistoryViewModel.load()` at
`ios/StillPointApp/ViewModels/HistoryViewModel.swift:34-47`, which sets `errorMessage =
"Failed to load sessions. Check your connection."`, and `HistoryView` then renders this
via `Text(errorMessage)` with accessibility identifier `history.errorMessage` at
`ios/StillPointApp/Views/HistoryView.swift:29-36`. No code maps `getSessions()` failures
to `authStatusMessage`, so the current test asserts an auth error surface unrelated to the
history retry/error UI it is named to validate.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 262:266
**Comment:**
*Logic Error: This test is asserting the auth-screen launch message, but session-load failures are surfaced from `HistoryViewModel` in the history screen (`history.errorMessage`), not `authView.launchAuthStatusMessage`. As written, the test validates the wrong UI contract and can pass while never proving that the retry/error UI for sessions is visible.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| let tabButton = app.tabBars.buttons[identifier] | ||
| if tabButton.waitForExistence(timeout: 10) { | ||
| tapByStableCenter(tabButton, in: app) | ||
| return | ||
| return tapByStableCenter(tabButton, in: app, file: file, line: line) |
There was a problem hiding this comment.
Suggestion: This early return skips the index and fallback selectors whenever the identifier-based tab exists but tapByStableCenter returns false, which defeats the fallback strategy during flaky hit-point resolution. Continue to fallback attempts when the first tap attempt fails instead of returning immediately. [possible bug]
Severity Level: Major ⚠️
- ⚠️ Tab navigation tests may flake when primary tab button is unstable.
- ⚠️ Index-based and generic fallback selectors become unreachable on tap failure.
- ⚠️ Affects history/settings navigation coverage in `testHistoryAndSettingsNavigationSmoke`.Steps of Reproduction ✅
1. `openTab` calls `tapTab(identifier:in:file:line)` at
`ios/StillPointAppUITests/StillPointAppUITests.swift:299` when navigating to
`tab.progress` or `tab.settings` in tests like `testHistoryAndSettingsNavigationSmoke` at
`StillPointAppUITests.swift:133,135`.
2. Inside `tapTab` at `StillPointAppUITests.swift:308-329`, the first branch resolves
`tabButton = app.tabBars.buttons[identifier]` at line 314 and checks
`tabButton.waitForExistence(timeout: 10)` at line 315.
3. If the tab button exists but `tapByStableCenter(tabButton, ...)` at line 316 returns
`false` (e.g., because `stableFrame` cannot find a valid frame within timeout at
`StillPointAppUITests.swift:375-399`), `tapTab` returns immediately with `false`, and the
subsequent index-based (`tabBarIndex`) and generic `app.buttons[identifier]` fallbacks at
`StillPointAppUITests.swift:318-325` are never executed.
4. This means that in runs where the identifier-based tab button exists but is temporarily
unsuitable for `tapByStableCenter`, `tapTab` cannot benefit from the alternate selectors
that were added specifically to handle tab-bar quirks (see index mapping at
`StillPointAppUITests.swift:332-337` and identifiers defined in `MainTabView` at
`ios/StillPointApp/Navigation/MainTabView.swift:15-20`), making tab navigation more
brittle than intended.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 314:316
**Comment:**
*Possible Bug: This early `return` skips the index and fallback selectors whenever the identifier-based tab exists but `tapByStableCenter` returns `false`, which defeats the fallback strategy during flaky hit-point resolution. Continue to fallback attempts when the first tap attempt fails instead of returning immediately.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished running the review. |
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
|
CodeAnt AI is reviewing your PR. |
| for attempt in 1...3 { | ||
| if tapTab(identifier: identifier, in: app, file: file, line: line), | ||
| destination?.waitForExistence(timeout: 8) ?? true { | ||
| return | ||
| } | ||
| RunLoop.current.run(until: Date().addingTimeInterval(0.5)) | ||
| XCTAssertTrue(attempt < 3, "Expected tab \(identifier) to open destination", file: file, line: line) | ||
| } | ||
| } | ||
|
|
||
| private func tapTab( | ||
| identifier: String, | ||
| in app: XCUIApplication, | ||
| file: StaticString = #filePath, | ||
| line: UInt = #line | ||
| ) -> Bool { | ||
| let directButton = app.buttons[identifier] | ||
| if directButton.waitForExistence(timeout: 5) { | ||
| return tapByStableCenter(directButton, in: app, file: file, line: line) | ||
| } | ||
|
|
||
| let tabButton = app.tabBars.buttons[identifier] | ||
| if tabButton.waitForExistence(timeout: 5) { | ||
| return tapByStableCenter(tabButton, in: app, file: file, line: line) | ||
| } | ||
| if let index = tabBarIndex(for: identifier) { | ||
| let indexedButton = app.tabBars.buttons.element(boundBy: index) | ||
| if indexedButton.waitForExistence(timeout: 5) { | ||
| tapByStableCenter(indexedButton, in: app) | ||
| return | ||
| return tapByStableCenter(indexedButton, in: app, file: file, line: line) | ||
| } | ||
| } | ||
| let fallbackButton = app.buttons[identifier] | ||
| XCTAssertTrue(fallbackButton.waitForExistence(timeout: 5), "Expected tab button \(identifier) to exist") | ||
| tapByStableCenter(fallbackButton, in: app) | ||
| XCTFail("Expected tab button \(identifier) to exist", file: file, line: line) | ||
| return false |
There was a problem hiding this comment.
The retry logic in openTab() is undermined by tapTab() calling XCTFail() on line 334. When tapTab() cannot find the tab button, it immediately records a test failure on the first attempt, even though openTab() intends to retry up to 3 times. This means failed attempts will accumulate multiple XCTFail() calls (one per retry) before the final assertion at line 309, creating noisy and confusing test failure output.
// Fix: Make tapTab return false gracefully without XCTFail on non-final attempts
private func tapTab(
identifier: String,
in app: XCUIApplication,
shouldFailOnMissing: Bool = false, // Add parameter
file: StaticString = #filePath,
line: UInt = #line
) -> Bool {
// ... existing button search logic ...
if shouldFailOnMissing {
XCTFail("Expected tab button \(identifier) to exist", file: file, line: line)
}
return false
}
// Then in openTab, only pass shouldFailOnMissing: true on final attempt
if tapTab(identifier: identifier, in: app, shouldFailOnMissing: attempt == 3, file: file, line: line),
destination?.waitForExistence(timeout: 8) ?? true {
return
}| for attempt in 1...3 { | |
| if tapTab(identifier: identifier, in: app, file: file, line: line), | |
| destination?.waitForExistence(timeout: 8) ?? true { | |
| return | |
| } | |
| RunLoop.current.run(until: Date().addingTimeInterval(0.5)) | |
| XCTAssertTrue(attempt < 3, "Expected tab \(identifier) to open destination", file: file, line: line) | |
| } | |
| } | |
| private func tapTab( | |
| identifier: String, | |
| in app: XCUIApplication, | |
| file: StaticString = #filePath, | |
| line: UInt = #line | |
| ) -> Bool { | |
| let directButton = app.buttons[identifier] | |
| if directButton.waitForExistence(timeout: 5) { | |
| return tapByStableCenter(directButton, in: app, file: file, line: line) | |
| } | |
| let tabButton = app.tabBars.buttons[identifier] | |
| if tabButton.waitForExistence(timeout: 5) { | |
| return tapByStableCenter(tabButton, in: app, file: file, line: line) | |
| } | |
| if let index = tabBarIndex(for: identifier) { | |
| let indexedButton = app.tabBars.buttons.element(boundBy: index) | |
| if indexedButton.waitForExistence(timeout: 5) { | |
| tapByStableCenter(indexedButton, in: app) | |
| return | |
| return tapByStableCenter(indexedButton, in: app, file: file, line: line) | |
| } | |
| } | |
| let fallbackButton = app.buttons[identifier] | |
| XCTAssertTrue(fallbackButton.waitForExistence(timeout: 5), "Expected tab button \(identifier) to exist") | |
| tapByStableCenter(fallbackButton, in: app) | |
| XCTFail("Expected tab button \(identifier) to exist", file: file, line: line) | |
| return false | |
| for attempt in 1...3 { | |
| if tapTab(identifier: identifier, in: app, shouldFailOnMissing: attempt == 3, file: file, line: line), | |
| destination?.waitForExistence(timeout: 8) ?? true { | |
| return | |
| } | |
| RunLoop.current.run(until: Date().addingTimeInterval(0.5)) | |
| XCTAssertTrue(attempt < 3, "Expected tab \(identifier) to open destination", file: file, line: line) | |
| } | |
| } | |
| private func tapTab( | |
| identifier: String, | |
| in app: XCUIApplication, | |
| shouldFailOnMissing: Bool = false, | |
| file: StaticString = #filePath, | |
| line: UInt = #line | |
| ) -> Bool { | |
| let directButton = app.buttons[identifier] | |
| if directButton.waitForExistence(timeout: 5) { | |
| return tapByStableCenter(directButton, in: app, file: file, line: line) | |
| } | |
| let tabButton = app.tabBars.buttons[identifier] | |
| if tabButton.waitForExistence(timeout: 5) { | |
| return tapByStableCenter(tabButton, in: app, file: file, line: line) | |
| } | |
| if let index = tabBarIndex(for: identifier) { | |
| let indexedButton = app.tabBars.buttons.element(boundBy: index) | |
| if indexedButton.waitForExistence(timeout: 5) { | |
| return tapByStableCenter(indexedButton, in: app, file: file, line: line) | |
| } | |
| } | |
| if shouldFailOnMissing { | |
| XCTFail("Expected tab button \(identifier) to exist", file: file, line: line) | |
| } | |
| return false | |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
| if destination?.exists == true { | ||
| return | ||
| } |
There was a problem hiding this comment.
Suggestion: Returning early when destination.exists is already true can skip the tab tap entirely, which can produce false positives because destination elements may already be present in the accessibility tree before actual tab navigation. This means the helper can report success without verifying that the tab switch happened. Ensure at least one tap attempt (or verify selected tab state) before returning success. [logic error]
Severity Level: Major ⚠️
- ❌ Tab helper may report success without switching visible tab.
- ⚠️ History/settings navigation tests can miss regressions in tab routing.Steps of Reproduction ✅
1. Consider `testHistoryAndSettingsNavigationSmoke` in
`ios/StillPointAppUITests/StillPointAppUITests.swift:127-137`. After waiting for the home
root, it calls `openTab(identifier: "tab.progress", in: app, waitingFor:
app.staticTexts["history.title"])` at line 133 to navigate to the Progress tab.
2. `openTab(...)` is defined at `StillPointAppUITests.swift:292-311`. It receives
`destination` as the XCUIElement handle for `"history.title"`. Because SwiftUI `TabView`
implementations often instantiate off-screen tab content eagerly, the `history.title`
element can already exist in the accessibility tree even when the Progress tab is not
selected.
3. When `openTab` executes, it first evaluates `if destination?.exists == true { return }`
at lines 299-301. If `history.title` is already present anywhere in the accessibility
tree, this branch returns early without calling `tapTab(...)` at lines 303-307 and without
ever tapping the `"tab.progress"` button.
4. The remainder of `testHistoryAndSettingsNavigationSmoke` then asserts only that the
destination elements exist (`history.title` and later `settings.title` via a second
`openTab` call), not that the intended tab buttons were actually tapped or that those
screens are visible. This allows the test to pass even if tab navigation is broken (e.g.,
tab buttons not wired up), because the helper reports success based purely on `exists`
rather than verifying actual tab selection or visibility.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 299:301
**Comment:**
*Logic Error: Returning early when `destination.exists` is already true can skip the tab tap entirely, which can produce false positives because destination elements may already be present in the accessibility tree before actual tab navigation. This means the helper can report success without verifying that the tab switch happened. Ensure at least one tap attempt (or verify selected tab state) before returning success.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com>
* Add quick minute session option Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Fix iOS quick minute launch Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Stabilize iOS E2E waits Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Reduce iOS E2E timing sensitivity Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Validate session progression inputs Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Address quick session review findings Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Make streak require contiguous days Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Relax iOS session UI assertions Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Tighten iOS auth UI test waits Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Focus iOS E2E on durable session states Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Allow slower iOS completion transition Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Handle fast iOS session completion in E2E Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Use completion title for iOS golden path Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Avoid fragile iOS note editor tap Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Align test stats and iOS session controls Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Align iOS test clear average stats Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Stabilize iOS E2E after quick minute rebase (#278) * Stabilize iOS E2E after quick minute rebase Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Address iOS E2E review feedback Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Address remaining iOS E2E review feedback Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> * Avoid chrome dim assertion in UI test mode Co-authored-by: Bretton Auerbach <auerbachb@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR stabilizes iOS UI tests by allowing the app to launch directly into the progress tab via a test environment flag and by hardening tab navigation with a retry and destination-wait helper. Together these changes reduce flakiness around history and settings screens in end to end flows. sequenceDiagram
participant UITest
participant App
participant MainTabView
participant TabHelper
UITest->>App: Configure app environment for progress tab
UITest->>App: Launch app with progress tab forced
App->>MainTabView: Create main tab view
MainTabView->>MainTabView: Read environment and select progress tab
MainTabView-->>UITest: Show history screen on launch
UITest->>TabHelper: Open settings tab with destination wait
TabHelper->>App: Retry tab taps until destination visible
App-->>UITest: Present settings screen with logout button
Generated by CodeAnt AI |
| XCTAssertTrue( | ||
| relaunch.staticTexts["history.title"].waitForExistence(timeout: launchTimeout), | ||
| "History screen did not appear after relaunch" | ||
| ) |
There was a problem hiding this comment.
Suggestion: This assertion only waits for history.title, so the relaunch check can still fail when the history root is present but the title snapshot resolves late (the exact flake this PR is trying to harden). Accept either destination evidence (root marker or title) before failing so the test doesn't produce false negatives during slow accessibility tree updates. [logic error]
Severity Level: Critical 🚨
- ❌ History persistence E2E flakes on slow accessibility updates.
- ⚠️ ios-e2e-critical suite reliability degraded by false failures.Steps of Reproduction ✅
1. Run the iOS UI test `testLaunchLoginCompleteSessionAndHistoryPersistence` in
`ios/StillPointAppUITests/StillPointAppUITests.swift:45-134`, which drives login, runs a
full session, then terminates and relaunches the app with `forceProgressTab: true` at
lines 119-125.
2. On relaunch, `MainTabView.initialSelectedTab()` in
`ios/StillPointApp/Navigation/MainTabView.swift:16-20` reads
`SP_UI_TEST_FORCE_PROGRESS_TAB` and selects the Progress tab, while
`RootView.viewAccessibilitySlug` in `ios/StillPointApp/Views/RootView.swift:113-24` sets
the root marker `root.currentView.history` as soon as `appVM.currentView` becomes
`.history`.
3. The test currently only asserts that `relaunch.staticTexts["history.title"]` exists
within `launchTimeout` at `ios/StillPointAppUITests/StillPointAppUITests.swift:128-131`,
even though `HistoryView` defines this identifier on the "Progress" header at
`ios/StillPointApp/Views/HistoryView.swift:20-24` and its accessibility snapshot can
become available later than the root marker.
4. In runs where `root.currentView.history` is present but the `history.title` text
element materializes after `launchTimeout` (slow SwiftUI/accessibility tree update), the
assertion at line 128 fails with "History screen did not appear after relaunch" even
though the correct root is active, producing a false-negative flake for this E2E
history-persistence test.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 128:131
**Comment:**
*Logic Error: This assertion only waits for `history.title`, so the relaunch check can still fail when the history root is present but the title snapshot resolves late (the exact flake this PR is trying to harden). Accept either destination evidence (root marker or title) before failing so the test doesn't produce false negatives during slow accessibility tree updates.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| @MainActor | ||
| func testSessionsFailureShowsVisibleRetryMessage() throws { | ||
| let app = makeApp(seedAuthenticated: true, resetStore: false, forceSessionsFailure: true) | ||
| let app = makeApp(seedAuthenticated: true, resetStore: false, forceSessionsFailure: true, forceProgressTab: true) |
There was a problem hiding this comment.
Suggestion: Using resetStore: false makes this test depend on persisted auth state from prior tests, so it can nondeterministically boot to auth instead of home if a previous case left the UI-test store unauthenticated. Reset the store for this test setup to keep it isolated and deterministic. [possible bug]
Severity Level: Major ⚠️
- ❌ Sessions failure UI test depends on prior auth state.
- ⚠️ ios-e2e suites become order-dependent and intermittently flaky.Steps of Reproduction ✅
1. One UI test (or manual UI flow) logs out the fixture user by triggering the logout path
that calls `APIClient.uiTestLogout()` in
`ios/StillPointShared/Sources/StillPointShared/APIClient.swift:446-454`, which sets
`UITestStore.isAuthenticated = false` and persists it to `UserDefaults` under
`StillPoint.UITest.Store` at lines 38-48.
2. Without clearing this persisted UI-test store, run the UI test
`testSessionsFailureShowsVisibleRetryMessage` in
`ios/StillPointAppUITests/StillPointAppUITests.swift:277-286`, which creates the app with
`makeApp(seedAuthenticated: true, resetStore: false, forceSessionsFailure: true,
forceProgressTab: true)` at line 278.
3. During app startup, `StillPointApp.resetSwiftDataIfRequested` in
`ios/StillPointApp/StillPointApp.swift:32-40` sees `SP_UI_TEST_RESET_STORE=0` and does not
clear persisted state; `APIClient.UITestConfig.fromProcessInfo()` in
`ios/StillPointShared/Sources/StillPointShared/APIClient.swift:20-29` reads `resetStore =
false`, so `APIClient.init` at lines 18-49 reuses the stale `UITestStore` where
`isAuthenticated` is still false.
4. When the test waits for `root.currentView.home` via `waitForRoot("home", ...)` at
`ios/StillPointAppUITests/StillPointAppUITests.swift:281` and `HistoryView` later tries to
load sessions via `uiTestGetSessions()` at
`ios/StillPointShared/Sources/StillPointShared/APIClient.swift:468-480`,
`ensureUITestAuthenticated(store:)` throws due to `isAuthenticated == false`, so the app
routes to re-auth instead of home or fails differently, and `history.errorMessage`
(asserted at lines 282-286) never appears—making this test's outcome depend on prior
test/auth state.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointAppUITests/StillPointAppUITests.swift
**Line:** 278:278
**Comment:**
*Possible Bug: Using `resetStore: false` makes this test depend on persisted auth state from prior tests, so it can nondeterministically boot to auth instead of home if a previous case left the UI-test store unauthenticated. Reset the store for this test setup to keep it isolated and deterministic.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished running the review. |
|
CodeAnt AI is running the review. |
Sequence DiagramThis PR stabilizes iOS UI tests by letting the app start on the progress history tab via an environment flag and by adding a retrying tab navigation helper that waits for destination screens before asserting. sequenceDiagram
participant TestRunner
participant iOSApp
participant MainTabView
participant HistoryScreen
participant SettingsScreen
TestRunner->>iOSApp: Launch app and complete a session
iOSApp-->>TestRunner: Show completion then return to home
TestRunner->>iOSApp: Relaunch with force progress tab flag
iOSApp->>MainTabView: Initialize selected tab from environment
MainTabView-->>HistoryScreen: Open history tab on launch
HistoryScreen-->>TestRunner: Display history title and persisted session row
TestRunner->>iOSApp: Open settings tab with helper
iOSApp->>MainTabView: Retry tab taps until settings visible
MainTabView-->>SettingsScreen: Present settings screen with logout button
Generated by CodeAnt AI |
| private static func initialSelectedTab() -> Int { | ||
| if truthy(ProcessInfo.processInfo.environment["SP_UI_TEST_FORCE_PROGRESS_TAB"]) { | ||
| return 1 | ||
| } | ||
| return 0 |
There was a problem hiding this comment.
Suggestion: The new forced-tab override is applied whenever the environment variable is present, even outside UI-test mode, so non-test launches can be unintentionally redirected to the Progress tab. Gate this override behind the existing UI-test mode flag so production/debug behavior is not altered by stray environment configuration. [logic error]
Severity Level: Major ⚠️
- ⚠️ Non-test app launches can start on Progress tab.
- ⚠️ Test-only environment flag leaks into normal navigation behavior.Steps of Reproduction ✅
1. Configure the main Still Point app run scheme (normal app target, not UI tests) in
Xcode to include the environment variable `SP_UI_TEST_FORCE_PROGRESS_TAB=1`. This is the
same key the UI tests set in `makeApp` at
`ios/StillPointAppUITests/StillPointAppUITests.swift:289-312`, but here it is applied to a
non-test launch.
2. Launch the app normally. The SwiftUI root view builds `RootView`, which, for the
default case, renders `MainTabView(appVM: appVM)` as seen in
`ios/StillPointApp/Views/RootView.swift:49`.
3. `MainTabView`'s initializer at `ios/StillPointApp/Navigation/MainTabView.swift:7-10`
calls `Self.initialSelectedTab()` and stores the result in the `@State private var
selectedTab` binding used by the `TabView(selection: $selectedTab)` at
`MainTabView.swift:12-19`.
4. `initialSelectedTab()` at `MainTabView.swift:55-59` reads
`ProcessInfo.processInfo.environment["SP_UI_TEST_FORCE_PROGRESS_TAB"]` via the `truthy`
helper at `MainTabView.swift:62-65`. Because this lookup is not gated by
`SP_UI_TEST_MODE`, any non-test process with that environment variable set will get
`selectedTab = 1`, causing the app to open on the Progress tab (tag 1) instead of the Home
tab (tag 0), altering normal startup behavior based solely on a test-only environment
flag.Fix in Cursor | Fix in VSCode Claude
(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/StillPointApp/Navigation/MainTabView.swift
**Line:** 55:59
**Comment:**
*Logic Error: The new forced-tab override is applied whenever the environment variable is present, even outside UI-test mode, so non-test launches can be unintentionally redirected to the Progress tab. Gate this override behind the existing UI-test mode flag so production/debug behavior is not altered by stray environment configuration.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished running the review. |


User description
Summary
Follow-up for PR #274 after its rebase onto
mainexposed remaining iOS XCTest timing/snapshot flakes.This PR hardens the iOS UI test assertions by:
Test plan
ios-e2e-smokepassesios-e2e-criticalpassesNotes
The failing PR #274 branch already contains the final PR #277 stabilization commit. The new failures are later timing/snapshot issues around final relaunch and tab navigation, not the original auth-root flake.
Issue creation note: I could not create the GitHub issue from this environment because the available GitHub tooling here is read-only for issues; this PR is stacked directly onto PR #274’s branch as the actionable fix.
CodeAnt-AI Description
Stabilize iOS tab navigation and keep relaunch history visible in UI tests
What Changed
Impact
✅ Fewer iOS UI test navigation flakes✅ More reliable history and settings checks✅ Clearer UI test relaunch coverage🔄 Retrigger CodeAnt AI Review
Details
💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.