Teach the live sign-up test about the age gate

The gate landed on 2026-08-29 and `canSubmit` has required a year of birth
ever since. The test was never taught to pick one, so it failed on "submit
stayed disabled" — and the live sign-up path, the one thing that proves a
new person can actually get in, went unverified from then until now. The
app was fine the whole time; the check was not.

Two things it got wrong on the way, both worth keeping in the comments:
the picker's options are not buttons in every OS version, so the year is
matched by label across element types; and the list runs 1900…this year in
reverse, about 127 entries, of which a menu renders only what is on screen
— a year thirty rows down is not in the accessibility tree at all, and the
failure reads as "the list never opened".

It now picks the youngest year the gate accepts, which is near the top of
the list and is also the boundary worth exercising.

Verified against production: passes, creating a real account through the
real UI and ending signed in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-30 22:20:12 -04:00
co-authored by Claude Opus 4.8
parent 9534e273df
commit 237a0f6460
+33 -1
View File
@@ -110,8 +110,40 @@ final class LiveSignUpUITests: XCTestCase {
+ "the strong-password panel ate the input")
}
// 🔴 **The age gate, which this test did not know about.** It landed on
// 2026-08-29 and `canSubmit` has required a year ever since; the test
// was never taught to pick one, so it failed on "submit stayed
// disabled" and the live sign-up path went unverified from then until
// 2026-08-30. The app was fine the whole time — a person sees the
// picker and uses it. What was broken was the check.
//
// ⚠️ A menu picker, so this is tap-to-open then tap-the-option, not a
// wheel to spin. The option is titled with the bare year.
// ⚠️ **The youngest year the gate accepts, because it is near the top of
// the list.** The picker offers 1900…this year in reverse, which is
// about 127 entries, and a menu renders only the slice on screen — a
// year thirty rows down is simply not in the accessibility tree, and
// the failure reads as "the list never opened". This also puts the test
// exactly on the boundary the gate allows, which is the value worth
// exercising anyway.
let year = Calendar.current.component(.year, from: Date()) - 14
let picker = app.buttons["signup.birthyear"]
XCTAssertTrue(picker.waitForExistence(timeout: 10),
"no year-of-birth control — the age gate changed shape")
picker.tap()
// ⚠️ Matched by label across every element type, not as a button. A
// SwiftUI menu picker's options are not buttons in the accessibility
// tree on every OS version, and a query that assumes one type fails
// with "the year list never opened" when the list opened fine.
let option = app.descendants(matching: .any)
.matching(NSPredicate(format: "label == %@", String(year))).firstMatch
XCTAssertTrue(option.waitForExistence(timeout: 10),
"no option labelled \(year) after opening the picker. "
+ "Hierarchy:\n\(app.debugDescription)")
option.tap()
let submit = app.buttons["signup.submit"]
XCTAssertTrue(submit.isEnabled,
XCTAssertTrue(submit.waitForExistence(timeout: 5) && submit.isEnabled,
"submit stayed disabled — the form is still demanding a field it cannot use")
submit.tap()