The iOS app has unit tests (flyfun-formsTests) but no UI tests, and no iOS job runs in CI. This issue adds an XCUI suite built on the pattern flyfun-weather already uses (flyfun-weatherUITests), carrying over the lessons that suite learned the hard way.
Test mode (DEBUG only, launch environment)
FLYFUN_UITEST=1
- Skips the auth gate by using an in-memory
BearerTokenStore, never the keychain.
- Uses an in-memory
ModelContainer with no CloudKit, seeded from fixtures: people with multi-country and expired documents, aircraft, upcoming and past flights, and a two-leg trip. This is the one non-negotiable change: without it, tests would write fake passports into whatever iCloud account the simulator is signed into, and state would leak between runs.
- Resets the
UserDefaults the UI reads, such as emailLanguage and document overrides.
FLYFUN_MOCK=1
- Registers a
URLProtocol stub that serves canned responses for /airports, /airports/{icao}, /validate, /generate, /prefill, /email-text and the maps.flyfun.aero notifications.
- Every call in the app goes through
URLSession.shared, including RollingBearerSession, so the stub needs no refactor of FormService.
- Any URL without a stub fails loudly instead of reaching the network.
FLYFUN_UITEST_CAPTURE_DIR=<path>
- The stub writes each request body it receives to this directory, so a journey can check the request the app generated (the server's output is already covered by the Python snapshot tests).
Tier 1 journeys (iPhone; macOS out of scope for now)
- Launch smoke: the People, Aircraft and Flights tabs show the seeded data.
- New flight flow: route, then people with the "Same crew as…" suggestion, and the flight is created.
- Paste an ICAO flight plan: the fields fill, and an unknown registration creates a new aircraft.
- A schedule edit that crosses UTC midnight moves the day (guards the
departureDate/departureInstant dual-write).
- Generate a form: the captured
/generate request carries the flight, aircraft and people, and the preview appears.
- A stubbed 422 shows human-readable validation errors ("Crew 1 — ID Number").
- Person and document CRUD: add a person and a passport; the expiry state shows.
- Return flight, next leg and duplicate each open the new flight with the right route.
Out of scope: system UI (Apple/Google sign-in, contacts picker, camera/MRZ, mail composer) and CloudKit sync.
Lessons carried over from flyfun-weather
- Pin portrait: the CI simulator boots in landscape, and lazy
Form rows then drop out of the accessibility tree.
- Swipe rows into view before asserting on them.
- Never launch without mocks.
- Select pickers by identifier.
CI
- A shared scheme.
- A PR gate that runs the unit tests only.
- A nightly workflow that runs the UI suite, fails when zero tests ran, and uploads the
.xcresult.
The iOS app has unit tests (
flyfun-formsTests) but no UI tests, and no iOS job runs in CI. This issue adds an XCUI suite built on the pattern flyfun-weather already uses (flyfun-weatherUITests), carrying over the lessons that suite learned the hard way.Test mode (DEBUG only, launch environment)
FLYFUN_UITEST=1BearerTokenStore, never the keychain.ModelContainerwith no CloudKit, seeded from fixtures: people with multi-country and expired documents, aircraft, upcoming and past flights, and a two-leg trip. This is the one non-negotiable change: without it, tests would write fake passports into whatever iCloud account the simulator is signed into, and state would leak between runs.UserDefaultsthe UI reads, such asemailLanguageand document overrides.FLYFUN_MOCK=1URLProtocolstub that serves canned responses for/airports,/airports/{icao},/validate,/generate,/prefill,/email-textand the maps.flyfun.aero notifications.URLSession.shared, includingRollingBearerSession, so the stub needs no refactor ofFormService.FLYFUN_UITEST_CAPTURE_DIR=<path>Tier 1 journeys (iPhone; macOS out of scope for now)
departureDate/departureInstantdual-write)./generaterequest carries the flight, aircraft and people, and the preview appears.Out of scope: system UI (Apple/Google sign-in, contacts picker, camera/MRZ, mail composer) and CloudKit sync.
Lessons carried over from flyfun-weather
Formrows then drop out of the accessibility tree.CI
.xcresult.