fix(ios): stop a tap's post-gesture lookup from recording an XCTest failure (#3060) - #3237
Conversation
Size Report
Startup median (7 runs, lower is better):
|
|
The runner change looks reasonable, but the reported failure was not reproduced on the Flutter shape that triggered it, so I can't call it ready at b9a59ea. The fix classifies the tapped element from its snapshot type before the gesture. The fixture hides a real UITextField, so its snapshot type stays .textField and that check passes trivially. On Flutter, TextInputSemanticsObject reports TextField through the legacy attributes and Other through the modern one. If Could you run this on an iOS Simulator with a Flutter login screen (an email TextField and a password TextField with obscureText)? Run CI is green: 19 checks, 0 failing at b9a59ea, and the swift-runner lanes that exercise the changed files pass. There are no conflicts. I did not run the XCTest cases, so the red/green claim on reverted source is unverified. I also had no Flutter app, so I could not check what |
ea437cb to
a6f6af1
Compare
|
You were right about The witness can no longer be dropped by a type read. Your question about what The live run does not reproduce the failure, and that is worth saying plainly. I installed Flutter and What the run does establish on the Flutter shape: I won't over-read the timings either: So the red side is the fixture characterisation tests, which force the same failing read by taking the field |
|
Correction to my previous comment: it does reproduce, and I was looking at the wrong route. I ran your sequence again driving the selector route (
That is the report's error text, at the line this change removes, at 7.5–9.0 s against its measured 7–14 s This also moves your open question forward. Base's On the concern that removing the classification would add work: neither side performed a focus repeat on this |
|
Second correction — my "selector route" wording was wrong. Here is what I actually measured. Same fresh-open layout on both runners, varying only how the field is addressed (each cell starts from
So it is not selector-versus-coordinate — label addressing reproduces, coordinates do not, which is why my Base's recorded error is the report's, down to the attributes: preceded by The fix returns Withdrawing one number from my last comment: 2.79 s for the fixed |
…ailure (#3060) A tap holds a handle bound to the query that resolved its element, so reading that handle re-runs the query. Flutter's TextInputSemanticsObject reports TextField through the legacy accessibility attributes and Other through the modern ones once the field is involved, so a focused password field stops answering the query that found it, XCTest records "No matches found for Element at index 1 from input {(TextField)}", and didRecordXCTestFailure turns that into XCTEST_RECORDED_FAILURE plus an invalidated target. The tap had already landed: the report measured 7-14s per tap, all of it runner restart. rememberTextEntryTap re-derived isTextEntryElement after the gesture even though both tap routes had already classified the element before dispatching, so the check could only agree or die. Its parameter is now the caller's classification, which for the coordinate route comes from queryTextInputs -- it enumerates only text-entry element types, so the classification does not depend on the snapshot's modern type. The post-tap readiness wait read element.elementType and element.frame, the two reads that record; the frame now comes from snapshot(), which answers the query through the throwing channel and records nothing, which is already how probeTextEntryInput and withElement reach an element. Readiness no longer classifies at all: the caller decided, and re-deriving it post-dispatch would classify differently from the caller because elementType answers through the legacy attributes while snapshot().elementType answers through the modern ones. The frame is still read after the wait, so the focus repeat aims at where the field is now rather than where the tap started. Runner-only, no CLI/daemon/API change. A tap that did not land still fails and a genuinely recorded failure still invalidates and surfaces.
a6f6af1 to
3998583
Compare
|
This PR is ready on the code. I re-read 3998583 against the earlier findings, and they are now fixed: a tap's post-gesture lookup no longer records an XCTest failure, and the two XCTest cases cover that path. Not blocking: the new doc blocks on waitForTextEntryReadinessAfterTap and rememberTextEntryTap (TextEntryFocus.swift:9) read as long incident narratives, and each could be one sentence of contract (the caller classifies before dispatch, and the handle is not read through a recording channel after the gesture). You can take or leave this. No conflicts. The checks do not show a result yet: Smoke Tests, Analyze (java-kotlin) and Repo Guards were cancelled with no logs, so nothing points at this diff. Analyze (java-kotlin) and Repo Guards do not touch the Swift runner, but Smoke Tests can reach the iOS tap path this PR changes. Please re-run all three on 3998583 and get them green before merge. For the record, I did not run the two XCTest cases. That they fail on base comes from reading the base code path. The live Flutter numbers (iOS 27.1, label=Password reproduces, coordinates and text= do not) are your report, and I did not re-run them. We still do not know why only label addressing reproduces. Cubic has not reviewed this head, and there are no open review threads. |
|
* origin/main: (77 commits) fix(apple-runner): fence prep spawns behind a start-owned admission (callstack#3239) 0.21.22 test(apple): own the simctl settings plan tests in simctl-settings.test.ts (callstack#3244) fix(limrun): report the session device id in iOS settings refusals (callstack#3243) 0.21.21 feat(remote): add a host-allocated macos-app lease backend (callstack#3236) test(android): bound the screenshot write wait by wall time, not event-loop turns (callstack#3250) feat(recording): cap the touch overlay frame rate at the caller's --fps (callstack#3241) fix(ad-script): let .ad scripts carry scroll --until and wait capture flags (callstack#3197) (callstack#3234) feat(provider-webdriver): keyboard enter, dismiss, and status over WebDriver (callstack#3233) feat(selectors): match role= against snapshot kind with a node-scoped alias window (callstack#3232) fix(provider-webdriver): read field values, placeholders, secure fields, and checked state from page source (callstack#3231) feat(replay): accept --test-ime on test and replay so flow-owned Android opens opt into the test IME (callstack#3235) refactor(daemon): route daemon-level diagnostics through one scope helper (callstack#3242) docs(adr): correct ADR 0031 pointer event delivery evidence (callstack#3245) fix(ios): stop a tap's post-gesture lookup from recording an XCTest failure (callstack#3060) (callstack#3237) fix(recording): render the touch overlay at most 30 fps and inside the record request (callstack#3219) fix(daemon): keep an idle daemon alive only for retained leases (callstack#3227) fix(provider-webdriver): send an empty JSON object on bodyless POSTs (callstack#3230) Feat/maestro repeat while (callstack#3214) ...
Summary
Closes #3060.
A tap holds a handle bound to the query that resolved its element, so reading that handle re-runs the query.
Flutter reports a password field as
TextFieldby one accessibility channel andOtherby another, so oncefocused it stops answering the query that found it; XCTest records a no-matches failure and
didRecordXCTestFailurefails the command and invalidates the target — for a tap that already landed. ADR 0005keeps a tap's outcome independent of this bookkeeping; three post-gesture reads did not follow it.
rememberTextEntryTapre-derivedisTextEntryElementafter the gesture though both tap routes classify beforedispatching, so it could only agree or die. Its parameter is now the caller's classification; the coordinate
route's comes from
queryTextInputs, which enumerates only text-entry types. Readiness no longer classifies,and its frame comes from
snapshot()instead — same answer, throwing channel, records nothing, asprobeTextEntryInputandwithElementalready do. Still read after the wait, so the focus repeat aims atwhere the field is then. Runner-only, 4 files; a tap that did not land still fails.
Validation
Head 3998583 (comment-only amendment over a6f6af1, which carried the live A/B).
pnpm check:affected --rungreen; iOS PR lane 97/97 locally on iOS 27.1, and CI runs these caseson iOS 26.2.
Reproduced and fixed live on a Flutter login screen, same app and daemon, only the runner binary differing.
click label=Passwordon base failed at 7.34 s withXCTEST_RECORDED_FAILURE, 2 invalidations, and a runnerrestart per failing tap; its
runner.logcarries the report's error verbatim — no-matches onElement at index 1 from input {(TextField)}, legacy-TextField-vs-modern-Other, traits146029150208— recorded atTextEntry.swift:268, the read removed here. The fix returnsTappedat 0.92 s with none of those, and afollowing
typelands six masked characters.pressandclick text=are clean on both sides; the full matrixand caveats are in my review comments.
Both new tests fail on base with that signature and pass on the fix. Unclaimed: the speedup is the avoided
restart, not a re-timed tap. One real behavior delta — where base's post-gesture type read answered a non-text
type it skipped readiness entirely, and readiness now runs its wait there; on the measured Flutter shape both
sides performed no repeat (2 synthesized dispatches per 2 taps in both).
fill @e6istype's own reads —#3238.