Skip to content

Fix the fourth NavigationUITests signature: Activation point invalid - #31

Merged
PrernaWeb merged 1 commit into
mainfrom
fix/uitest-activation-point
Jul 30, 2026
Merged

Fix the fourth NavigationUITests signature: Activation point invalid#31
PrernaWeb merged 1 commit into
mainfrom
fix/uitest-activation-point

Conversation

@PrernaWeb

Copy link
Copy Markdown
Owner

Follow-up to #30, which fixed three of the four recorded failure signatures and grouped the fourth with the wrong mechanism. It recurred the same day with #30 in place — run 30502645944, testSwitchingToProgressTabSelectsIt, on a docs-only SHA whose paired pull_request run passed 6/6.

Why #30 missed it

#30 filed these two together as "a lost tap". They are not the same mechanism:

Signature What it actually is Can a retry catch it?
"Tapped Coach but it never became selected" predicate returning false ✅ yes
"Activation point invalid and no suggested hit points based on element frame" XCUITest raising a failure during the query no

The raise is recorded against the test case the moment isHittable is evaluated — before any Bool comes back — so guard waitFor(…) else { continue } has nothing to absorb.

Evidence the retry never ran in that failure: exactly one error line rather than three, no "Tapped Progress N time(s)" message, and 53.1s consistent with the initial 30s hittability wait erroring then timing out on top of launch.

The message is also not about obstruction, which is how it got misfiled. It says the accessibility frame yields no hit points — the element is mid-layout with an empty or invalid frame.

The fix

Three changes, all following from never ask a question that can raise:

  1. waitForValidFrame gates every isHittable query — a block NSPredicate on exists && frame.width > 0 && frame.height > 0. Reading .frame is safe where isHittable isn't: it does no hit-point computation. exists is checked first because .frame on a non-existent element is unsafe.
  2. Taps go through coordinate(withNormalizedOffset:), not tap(). tap() resolves an activation point and raises when it can't; a normalized coordinate is derived from the frame just validated and skips that machinery.
  3. isHittable is queried once per switch, not once per retry attempt. Every query is another chance at the raise, and the retry doesn't need it now that the tap is coordinate-based.

The guard is intact — the reason it survives gating

A covered app still has a valid frame. It's laid out, just obscured. So waitForValidFrame passes and isHittable still returns false, exactly as it did when it caught the 2026-07-28 founding-sheet regression.

Frame validity and obstruction are orthogonal properties. Conflating them is precisely what cost the round trip, so it's worth being explicit: this change does not let an obstruction through, and testNothingIsPresentedOverTheAppOnLaunch still guards it by name.

Diagnostics now name the mechanism rather than defaulting to "something is likely covering the app" — an invalid frame reports a layout problem and states that a covered tab would still have a valid frame.

Evidence — stated carefully, because #30 had more of it and was wrong

Local 6/6, 169s (down from 196s; coordinate taps are cheaper than activation-point resolution).

That is deliberately not the argument. #30 had 6/6 local plus an agreeing same-SHA CI pair and was still incomplete. Green runs can't prove a rare flake gone — the same-SHA rule tells you a failure is flake; it can't tell you an absence of failure means anything.

The argument is that the raise has a named causeisHittable evaluated against an invalid frame — and the code no longer performs that evaluation. If signature 4 recurs anyway, the conclusion should be that the diagnosis was wrong, not that the timeouts need raising again. The new message text will say which of the remaining mechanisms fired.

🤖 Generated with Claude Code

PR #30 addressed three of the four recorded failure signatures and
grouped the fourth with "never became selected" as one lost-tap bucket.
It is not the same mechanism, and it recurred the same day with #30 in
place (run 30502645944, testSwitchingToProgressTabSelectsIt).

"never became selected" is a predicate returning false, which a retry
catches. "Activation point invalid and no suggested hit points based on
element frame" is XCUITest raising a failure during the isHittable
query itself — recorded against the test case before any Bool comes
back, so no retry can absorb it. The message is also not about
obstruction: it says the accessibility frame yields no hit points, i.e.
the element is mid-layout.

Three changes, all following from "never ask a question that can raise":

- waitForValidFrame gates every isHittable query — a block NSPredicate
  on exists && frame.width > 0 && frame.height > 0. Reading .frame does
  no hit-point computation; exists is checked first because .frame on a
  non-existent element is not safe.
- Taps go through coordinate(withNormalizedOffset:) rather than tap(),
  which resolves an activation point and raises when it cannot.
- isHittable is queried once per switch, not once per retry attempt.

The guard is intact: a covered app still has a VALID frame — laid out,
just obscured — so waitForValidFrame passes and isHittable still
returns false, exactly as when it caught the founding sheet. Frame
validity and obstruction are orthogonal.

Diagnostics now name the mechanism instead of defaulting to "something
is likely covering the app".

Local 6/6, 169s (was 196s). Deliberately not resting on that: #30 had
more green runs than this and was wrong. The argument is that the raise
has a named cause and the code no longer performs that evaluation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
coachsage-site Ready Ready Preview Jul 30, 2026 1:02am
run-coach Ready Ready Preview Jul 30, 2026 1:02am

@PrernaWeb
PrernaWeb merged commit 352a754 into main Jul 30, 2026
7 checks passed
@PrernaWeb
PrernaWeb deleted the fix/uitest-activation-point branch July 30, 2026 01:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant