Skip to content

Stop the UI readiness helpers busy-waiting and cascading failures - #45

Merged
m96-chan merged 1 commit into
mainfrom
fix-ui-helper-busywait
Sep 15, 2026
Merged

m96-chan merged 1 commit into
mainfrom
fix-ui-helper-busywait

Conversation

@m96-chan

Copy link
Copy Markdown
Owner

Two defects in the helper #41 added, found reviewing it. #41 merged before this was pushed to the branch, so it comes as its own change.

waitUntilHittable was busy-waiting, not polling

_ = waitForExistence(timeout: 0.1)   // the 'throttle'

waitForExistence returns immediately when the element already exists — which is precisely the case this helper is written for: present but not yet touchable, a sheet still animating in. So the loop spun on back-to-back accessibility snapshot queries for the full twenty seconds instead of polling at 100ms.

Querying the app as hard as possible is a poor way to wait for it to settle, and one of the three failures this helper exists to prevent was Timed out while synthesizing event — the app failing to report itself idle. It sleeps now.

tapWhenReady recorded a failure and returned

continueAfterFailure is true by default, so the caller carried on. typeWhenReady would then type into nothing and raise a second, unrelated "no keyboard focus" error on top of the real one, and every later helper in the test would spend its own twenty seconds before doing the same — three times over, now that CI retries with -test-iterations 3.

Both helpers throw now, so a test stops at the first unmet precondition and the report is about what actually went wrong. Every call site takes try.

Same defect, one place older

TailscaleImportUITests.bringIntoView used waitForExistence(timeout: 0.2) as its throttle too, from before #41. Fixed alongside rather than left as the one place still doing it.

Verified

The whole UI suite on both idioms locally — iPhone 8 passing, iPad passing, simulators shut down after.

🤖 Generated with Claude Code

…ures

Both found reviewing the commit before this one.

`waitUntilHittable` used `waitForExistence(timeout: 0.1)` as its throttle. That
returns immediately when the element already exists, which is precisely the case
it is written for — present but not yet touchable, a sheet still animating in —
so the loop spun on back-to-back accessibility snapshots for the whole twenty
seconds instead of polling. Querying the app as hard as possible is a poor way
to wait for it to settle, and one of the failures this helper exists to prevent
was the app failing to report itself idle. It sleeps now.

`tapWhenReady` recorded a failure and returned. `continueAfterFailure` is true
by default, so the caller carried on: `typeWhenReady` would type into nothing
and raise a second, unrelated "no keyboard focus" error over the top of the real
one, and every later helper in the test would spend its own twenty seconds
before doing the same — three times over, now that CI retries. Both helpers
throw instead, so a test stops at the first unmet precondition and the report is
about the thing that actually went wrong.

`TailscaleImportUITests.bringIntoView` had the same non-throttle, from before
this branch. Fixed with it rather than left as the one place that still does it.

Verified: the UI suite on an iPhone, 8 passing, with the iPad leg running.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@m96-chan
m96-chan merged commit f71a059 into main Sep 15, 2026
6 checks passed
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