Measured: ~half of every authenticated test was fixed setup, dominated by the UI login (typing email+password, keyboard/SecureField dance, ~8-12s). The test already creates the account via API and holds its real Kratos session token — so instead of typing credentials, pass the token as a launch arg and boot the app already authenticated. - App (UITestRuntime + iOSApp): reads --ui-test-session-token; after the --reset-state clear, calls DataManager.setAuthToken(token) and replicates the post-login init the UI login path runs (getCurrentUser + initializeLookups + getMyResidences + getTasks) so owner-gated/data-gated screens (residence detail delete + manage-users, pickers, lists) work on boot. Guarded by UITestRuntime.isEnabled — no effect on production. - AuthenticatedUITestCase: in fresh-account mode, create the account + seed its preconditions BEFORE launch, expose the token via additionalLaunchArguments, and drop the UI login. Legacy (usesFreshAccount=false) suites still UI-login. Measured per-test medians: Contractor 34s -> 25s; Task (uses lookups) ~34s -> 16s. TESTING.md updated. All affected suites pass; 0 leaked accounts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.9 KiB
HoneyDue iOS Tests — How it works
Two test targets:
| Target | Kind | What it covers | Speed |
|---|---|---|---|
| HoneyDueUITests | XCUITest | Drives the real app via accessibility IDs. Organized by domain. | slow (per-test app launch + account) |
| HoneyDueAPITests | standalone unit-test | Pure-API/contract tests (no UI, no app launch). | seconds |
Running
cd iosApp
# Full run: Smoke gate -> Seed -> API -> Parallel UI (8 workers) -> Sweep
./run_ui_tests.sh
# Variants
./run_ui_tests.sh "iPhone Air" 6 # device + worker count
./run_ui_tests.sh --smoke # just the smoke gate
./run_ui_tests.sh --only-api # just the fast API target
./run_ui_tests.sh --only-parallel # just the UI suites
The parallel phase runs the whole UI target minus the four phase-managed
suites (SmokeUITests, AppLaunchUITests, AAA_SeedTests,
SuiteZZ_CleanupTests) via -skip-testing. New suites are picked up
automatically — there is no hand-maintained include list to forget to update.
Per-test account isolation (the core idea)
Every UI test that needs to be logged in subclasses AuthenticatedUITestCase,
which in setUp:
- mints a unique, pre-verified Kratos identity —
uit_<domain>_<uuid>@test.honeydue.local(seeCore/Fixtures/TestAccount.swift), - boots the app already authenticated by passing the account's real Kratos
token as
--ui-test-session-token(the app reads it inUITestRuntimeand callsDataManager.setAuthToken). This skips the slow, flaky UI login (~8–12s/test) — the app lands straight on the main tabs. Logged-OUT suites (login/registration/onboarding) still drive the real login UI, since that's what they test, - exposes
session/cleaner/accountfor seeding under its own token,
and in tearDown calls account.delete(), which cascades all of the
account's data and clears the Kratos identity in one call.
No test shares state with any other, so suites run in parallel at high worker
counts with zero data races. (This replaced the old shared-testuser model
that capped the suite at 2 workers and forced Suite6 to run isolated.)
Seeding data the UI must SEE
A fresh account is empty at login. Anything you seed via API after login is invisible to the app until a manual refresh. So:
-
Creating a thing through the UI → no setup needed (but note: task and document creation are gated on a residence existing — set
override var requiresResidence: Bool { true }, which seeds one before login and exposes it asseededResidence). -
Viewing / editing / deleting an existing thing → seed it before login:
override func seedAccountPreconditions(_ account: TestAccount) { super.seedAccountPreconditions(account) // seeds seededResidence if requiresResidence let r = seededResidence ?? account.seedResidence() myTask = account.seedTask(residenceId: r.id, title: "Edit me") }
Adding a suite
- Pick the domain folder (
Task/,Residence/, …). The folder is an Xcode synchronized group, so a new.swiftfile is compiled into the target automatically — no.pbxprojediting. - Name it
<Domain><Aspect>UITests; subclassBaseUITestCase(logged-out surface: login/registration/onboarding) orAuthenticatedUITestCase(logged-in feature work). - Use accessibility IDs from
iosApp/Helpers/AccessibilityIdentifiers.swift(shared with the app — the single source of truth). - It runs automatically in the parallel phase. Done.
For a pure-API test (no UI): add it to HoneyDueAPITests/ instead — it'll
run in the fast API phase.
Known trade-off
Per-test account creation costs ~1–2s of setup (Kratos create + login). For a big suite that adds up; if a full run drags, the planned mitigation is a recycled account pool keyed by worker id. Correctness-first today.