Repository navigation
fix(ios): stop a fill's post-focus reads from recording an XCTest failure (#3238) - #3329
Conversation
…lure (#3238) #3237 settled this on the tap route; the type/fill focus path kept two reads of the element it resolved BEFORE its own focus tap. `textEntryRefreshPoint(for:)` read `element.frame` after that tap to build the target's refresh point, and `moveCaretToEnd(element:)` read it before the caret edge-tap reached from `clearTextInput` on a replacement fill. A field that stops answering the query that resolved it once it takes focus — the #3060 Flutter shape — turns either read into a recorded "No matches found for …", which the dispatch path converts into XCTEST_RECORDED_FAILURE plus an invalidated target for a focus the gesture had already delivered. The report's `fill @e6` row measured 4.5 s at this seam. Both frames now come from `textEntrySnapshotFrame`, which reads through `snapshot()` — the throwing channel that records nothing, already how `probeTextEntryInput` and `withElement` reach an element. Where the frame cannot be answered the call sites degrade per the route's own rules: the refresh point drops to NO point rather than the pre-focus one, because this route's first resolve is not yet bound to an identity and a field that went unqueryable on focus may have moved the layout so the old point names another input, so the fill fails closed with a typed reason instead of typing into that input; the caret edge-tap skips to the caller's point-free delete burst rather than dispatching a point from a frame the field may have moved. Runner-only, no CLI/daemon/API change. Reads that distinguish absence from failure stay on `probeTextEntryInput`. A fill whose field genuinely did not take focus still fails typed, and a genuinely recorded failure still invalidates and surfaces. The regression test drives `fill` through `execute(command:)` on the unqueryable-on-focus fixture: red before the change at the refresh-point read, green after with the app-side focus witness proving the gesture landed.
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
… the handle (#3329 review) A successful `snapshot()` proves the handle answered its query when the frame was read, not that it still answers when the tap dispatches — and an element-anchored `coordinate(withNormalizedOffset:).tap()` re-runs the resolving query to place its anchor when the action executes, the same #3060 recorder one call later. Both branches now leave the handle: the edge-tap point is computed from the snapshot frame and dispatched through `tapAt`, the app-relative coordinate route that resolves no element at all — the shape `waitForTextEntryReadinessAfterTap` settled on in #3237 — and the empty-frame `element.tap()` (how a departed handle reads) is folded into the same fail-closed guard, degrading to the point-free delete burst rather than tapping a handle that may not resolve. `clearTextInput` threads `app` to reach `tapAt`; its three call sites already hold it. No CLI/daemon/API change.
|
Gate record at 65a9dbf (caret edge-tap dispatched app-relative after the cubic-bot finding — see thread reply): |
|
This PR is ready for human review. At 65a9dbf the change looks correct, and CI is green: 19 checks, 0 failing, including the iOS runner and replay lanes that exercise this Swift code. There are no conflicts. Not blocking, and you can take or leave these: The cubic-dev-ai thread on the edge tap no longer applies, since the tap now goes through I did not run the regression test or the simulator batch. The red-before claim rests on reading the old code and the #3237 measurement. The live evidence is your 14-test batch on an iPhone 18 Pro simulator, and no CLI |
|
Summary
fill/typestill read the element they resolved before their own focus tap, after the tap:textEntryRefreshPoint(for:)readelement.frameto build the target's refresh point, andmoveCaretToEnd(element:)read it before the caret edge-tap reached fromclearTextInput. A field that stops answering its resolving query once focused (#3060's Flutter shape) turns either read into a recordedNo matches found for …, which the dispatch path converts toXCTEST_RECORDED_FAILUREplus an invalidated target — the report'sfill @e6row at 4.5 s.Both frames now come from
textEntrySnapshotFrame, which reads throughsnapshot()— the throwing channel that records nothing, already howprobeTextEntryInputandwithElementreach an element. Where no frame can be answered the call sites degrade instead of dispatching a stale point: the refresh point drops to no point (the target is not yet identity-bound, so the pre-focus point could name a field that moved into it — the fill fails closed with a typed reason), and the caret edge-tap skips to the point-free delete burst.Runner-only; 3 files. Absence-vs-failure reads stay on
probeTextEntryInput. Closes #3238.Validation
Tested at
2c9588332. New regression test on--agent-device-text-entry-unqueryable-on-focusdrivesfillthroughexecute(command:): red before the change at the refresh-point read (RunnerTests+TextEntryFocus.swift:169), green after withdidRecordXCTestFailure == falseand the app-side focus witness proving the gesture landed. Moves-on-focus, both #3237 tap tests, and the fill/synthesized-replacement suites pass on an iPhone 18 Pro simulator; iOS and macOSbuild-for-testingsucceed.pnpm check:xctest-selectionandcheck:packaged-runner-swiftpass; remainingcheck:affecteditems (swift-runner builds, replay lanes) are GitHub-authoritative. ThemoveCaretToEndhunk is defensive-only — everyclearTextInputcaller passes an element that already passed the snapshot-probing resolve — and is pinned by no new test.