test: add forceFreshLoginPerTest opt-in flag to AuthenticatedUITestCase
Android UI Tests / ui-tests (push) Has been cancelled

Default is `false` (current session-reuse behaviour) so tests reuse the
existing logged-in session — fast, and resilient to suites where the
current screen lacks a logout affordance (`UITestHelpers.ensureLoggedOut`
times out → tests fail before their bodies run).

Override to `true` in suites that observe transient `Invalid token` 401s
on POST/PATCH while reads continue to work. Recipe added after a 2026-05
incident where the API container was rebuilt mid-suite and in-memory
JWT tokens went stale; the diagnostic value is having an explicit lever
to reach for next time, not flipping the default.

Net effect on a clean simulator + stable API: 244/253 → 244/253 (no
behaviour change in the default path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-05-03 13:14:37 -05:00
parent ec5d93efab
commit d5041492a9
@@ -34,6 +34,23 @@ class AuthenticatedUITestCase: BaseUITestCase {
}
}
/// When `true`, every test in the suite forces a logout login cycle
/// in `setUp`, guaranteeing a freshly-issued auth token on each run.
///
/// Default is `false`: tests reuse the existing logged-in session
/// from the previous test in the same suite much faster (one login
/// per suite, not one per test) and resilient to suites where the
/// current screen has no logout affordance (`UITestHelpers.ensureLoggedOut`
/// times out the test fails before its body runs).
///
/// Override to `true` in suites that have observed transient
/// `Invalid token` 401s on POST/PATCH while reads continue to work.
/// The recipe was added after a 2026-05 incident where the API
/// container was rebuilt mid-suite and in-memory tokens went stale.
/// In normal CI runs against a stable API + freshly-erased simulator,
/// session reuse is the correct default.
var forceFreshLoginPerTest: Bool { false }
override func setUpWithError() throws {
guard TestAccountAPIClient.isBackendReachable() else {
throw XCTSkip("Backend not reachable at \(TestAccountAPIClient.baseURL)")
@@ -41,27 +58,27 @@ class AuthenticatedUITestCase: BaseUITestCase {
try super.setUpWithError()
// If already logged in (tab bar visible), skip the login flow
let tabBar = app.tabBars.firstMatch
if tabBar.waitForExistence(timeout: defaultTimeout) {
// Already logged in just set up API session if needed
if needsAPISession {
guard let apiSession = TestAccountManager.loginSeededAccount(
username: apiCredentials.username,
password: apiCredentials.password
) else {
XCTFail("Could not login API account '\(apiCredentials.username)'")
return
}
session = apiSession
cleaner = TestDataCleaner(token: apiSession.token)
}
return
}
let alreadyLoggedIn = tabBar.waitForExistence(timeout: defaultTimeout)
// Not logged in do the full login flow
UITestHelpers.ensureLoggedOut(app: app)
loginToMainApp()
// Force-fresh path: log out (if needed) and re-authenticate per
// test so every test starts with a freshly-issued JWT. Catches
// server-side token invalidation that would otherwise surface
// mid-suite as opaque 401s on the first mutation call.
if forceFreshLoginPerTest {
if alreadyLoggedIn {
UITestHelpers.ensureLoggedOut(app: app)
} else {
UITestHelpers.ensureLoggedOut(app: app)
}
loginToMainApp()
} else if !alreadyLoggedIn {
// Legacy session-reuse path: only log in when not already in.
UITestHelpers.ensureLoggedOut(app: app)
loginToMainApp()
}
// (When `forceFreshLoginPerTest == false` AND we're already
// logged in, fall through with the existing session.)
if needsAPISession {
guard let apiSession = TestAccountManager.loginSeededAccount(