d11cc82fec
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>
91 lines
3.9 KiB
Markdown
91 lines
3.9 KiB
Markdown
# 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
|
||
|
||
```bash
|
||
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`:
|
||
|
||
1. mints a **unique, pre-verified Kratos identity** —
|
||
`uit_<domain>_<uuid>@test.honeydue.local` (see `Core/Fixtures/TestAccount.swift`),
|
||
2. boots the app **already authenticated** by passing the account's real Kratos
|
||
token as `--ui-test-session-token` (the app reads it in `UITestRuntime` and
|
||
calls `DataManager.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,
|
||
3. exposes `session` / `cleaner` / `account` for 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 as `seededResidence`).
|
||
- **Viewing / editing / deleting an existing** thing → seed it **before login**:
|
||
|
||
```swift
|
||
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
|
||
|
||
1. Pick the domain folder (`Task/`, `Residence/`, …). The folder is an Xcode
|
||
synchronized group, so a new `.swift` file is compiled into the target
|
||
automatically — no `.pbxproj` editing.
|
||
2. Name it `<Domain><Aspect>UITests`; subclass `BaseUITestCase` (logged-out
|
||
surface: login/registration/onboarding) or `AuthenticatedUITestCase`
|
||
(logged-in feature work).
|
||
3. Use accessibility IDs from `iosApp/Helpers/AccessibilityIdentifiers.swift`
|
||
(shared with the app — the single source of truth).
|
||
4. 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.
|