Skip to content

Commit e199c64

Browse files
authored
fix(ios): wait for transient fill mismatches to settle (#3953)
Co-authored-by: shamilovtim <[email protected]>
1 parent 097b0ae commit e199c64

4 files changed

Lines changed: 41 additions & 4 deletions

File tree

‎apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ extension RunnerTests {
7272
latest = observed
7373
let result = RunnerTests.replacementTextEntryResult(requested: requested, baseline: baseline, observed: observed)
7474
let settled = sampledAt.timeIntervalSince(stableSince) >= TextEntryTiming.verificationStabilityWindow
75-
let moved = baseline.map { !observed.isSettled(with: $0) } ?? true
76-
if settled && (moved || result.verified != false || sampledAt >= deadline) {
75+
if settled && (result.verified != false || sampledAt >= deadline) {
7776
return result
7877
}
7978
if sampledAt >= deadline {

‎apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/UnitTests/RunnerTests+TextEntryConfirmationTests.swift‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,44 @@ extension RunnerTests {
146146
XCTAssertFalse(result.repaired)
147147
}
148148

149+
func testReplacementMismatchWaitsForCorrectTextOrDeadline() throws {
150+
let startedAt = Date(timeIntervalSinceReferenceDate: 100)
151+
let unidentified = TextEntryElementIdentity(
152+
identifier: nil, elementType: "TextField", frame: Self.otpFieldIdentity.frame
153+
)
154+
let shifted = TextEntryElementIdentity(
155+
identifier: nil, elementType: "TextField", frame: unidentified.frame.offsetBy(dx: 0, dy: 60)
156+
)
157+
let cases: [(String, TextEntryObservation, TextEntryObservation)] = [
158+
("partial value", Self.otpObservation(""), Self.otpObservation("123")),
159+
(
160+
"empty moved field",
161+
TextEntryObservation(value: "", identity: unidentified, isFocused: true),
162+
TextEntryObservation(value: "", identity: shifted, isFocused: true)
163+
),
164+
]
165+
for (name, baseline, mismatch) in cases {
166+
var confirmation = ReplacementTextEntryConfirmation(requested: "123456", baseline: baseline, startedAt: startedAt)
167+
XCTAssertNil(confirmation.observe(mismatch, at: startedAt.addingTimeInterval(0.1)), name)
168+
XCTAssertNil(confirmation.observe(mismatch, at: startedAt.addingTimeInterval(0.31)), name)
169+
let correct = TextEntryObservation(value: "123456", identity: mismatch.identity, isFocused: mismatch.isFocused)
170+
XCTAssertNil(confirmation.observe(correct, at: startedAt.addingTimeInterval(0.5)), name)
171+
let success = try XCTUnwrap(confirmation.observe(correct, at: startedAt.addingTimeInterval(0.71)), name)
172+
XCTAssertEqual(success.verified, true, name)
173+
XCTAssertNil(success.failure, name)
174+
XCTAssertFalse(success.repaired, name)
175+
176+
var persistent = ReplacementTextEntryConfirmation(requested: "123456", baseline: baseline, startedAt: startedAt)
177+
XCTAssertNil(persistent.observe(mismatch, at: startedAt.addingTimeInterval(0.1)), name)
178+
XCTAssertNil(persistent.observe(mismatch, at: startedAt.addingTimeInterval(0.31)), name)
179+
let failure = try XCTUnwrap(persistent.observe(mismatch, at: startedAt.addingTimeInterval(1.1)), name)
180+
XCTAssertEqual(failure.verified, false, name)
181+
XCTAssertEqual(failure.observedText, mismatch.value, name)
182+
XCTAssertNil(failure.failure, name)
183+
XCTAssertFalse(failure.repaired, name)
184+
}
185+
}
186+
149187
func testReplacementSubmitDoesNotTreatMultilineContentAsASubmitKey() {
150188
for after in ["", "hllo", "submitted"] {
151189
let submitted = Self.replacementTextEntryResult(

‎src/commands/schema/cli-help-workflows.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ Stabilizers:
184184
Text-entry quirks:
185185
iOS Allow Paste cannot be exercised under XCUITest; prefill with clipboard write "some text" instead and test the system prompt manually.
186186
Android Gboard handwriting/stylus UI can capture text in an IME-owned input instead of the app field. If fill reports that input was captured by the keyboard/IME, use the diagnostic targetInput/actualInput details, inspect keyboard status/get if needed, and switch or disable handwriting outside the command plan before retrying. Do not keep retrying fill/type against the same field while the IME owns focus. If the exact target changes but app-owned formatting prevents raw equality, fill succeeds with verification: "unconfirmed" plus target-bound requested/before/after evidence; inspect that evidence instead of retrying the same mutation.
187-
iOS accessibility-driven fill types the replacement once and never clears and retypes a read-back mismatch. Exact settled text confirms the entry. A value containing only characters in order from the post-clear value plus the request fails with TEXT_ENTRY_MISMATCH, including plain dropped characters and unchanged values; legitimate trimming or masks can also hit this limit ("5" into "$0.00" displaying "$0.05"). Other settled values return verification: "unconfirmed" with requested/before/after/target evidence, including grouping ("00 062 91 77" for "000629177"), currency ("$10.00" for "1000"), and OTP summaries. Missing, reordered, duplicated or stale text can also be unconfirmed, so assert the expected value with a value selector, get/is, or the resulting screen before continuing. For single-line fills ending in a submit key, any non-matching value observed on the same target succeeds unverified, including partial or cleared text; this exception skips dropped-character checks because the submit may change the field. Assert the resulting screen. Multiline fields compare newlines as content. Unreadable or removed fields remain unverified. The coordinate-driven fill route still requires an exact match. After TEXT_ENTRY_MISMATCH, inspect the field and retry fill only if it needs correction, using the --delay-ms value from the error hint.
187+
iOS accessibility-driven fill types the replacement once and never clears and retypes a read-back mismatch. Exact settled text confirms the entry. Nonempty replacements wait until the confirmation deadline before reporting TEXT_ENTRY_MISMATCH. A value containing only characters in order from the post-clear value plus the request fails with TEXT_ENTRY_MISMATCH, including plain dropped characters and unchanged values; legitimate trimming or masks can also hit this limit ("5" into "$0.00" displaying "$0.05"). Other settled values return verification: "unconfirmed" with requested/before/after/target evidence, including grouping ("00 062 91 77" for "000629177"), currency ("$10.00" for "1000"), and OTP summaries. Missing, reordered, duplicated or stale text can also be unconfirmed, so assert the expected value with a value selector, get/is, or the resulting screen before continuing. For single-line fills ending in a submit key, any non-matching value observed on the same target succeeds unverified, including partial or cleared text; this exception skips dropped-character checks because the submit may change the field. Assert the resulting screen. Multiline fields compare newlines as content. Unreadable or removed fields remain unverified. The coordinate-driven fill route still requires an exact match. After TEXT_ENTRY_MISMATCH, inspect the field and retry fill only if it needs correction, using the --delay-ms value from the error hint.
188188
189189
React Native internals:
190190
If the question is about React component props, state, hooks, the component tree, performance, render causes, slow components, or rerenders, use help react-devtools instead of inferring from screenshots or logs.`,

‎website/docs/docs/commands.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ Text-entry errors and what to do:
502502
- `TEXT_INPUT_SYNTHESIS_BUDGET_EXCEEDED` (iOS `fill`): the text is too long to type in one runner command, and nothing was typed. Fill up to the character limit the hint names for your `--delay-ms`, then append the rest with `type`. `--delay-ms` lowers the limit; a longer timeout does not help.
503503
- `TEXT_ENTRY_MISMATCH` (iOS `fill`): the settled value contains only characters, in order, from the field's value after clearing plus your text. That covers dropped characters and an unchanged value. This can also reject legitimate filtering, trimming, or masks (`5` into `$0.00` may show `$0.05`). Inspect the field and retry `fill` only if it needs correction, using the `--delay-ms` value from the error hint.
504504
505-
Except for `TEXT_INPUT_SYNTHESIS_BUDGET_EXCEEDED`, where you append the rest with `type` after the shorter `fill`, recover from these with `fill`, never `type`, which appends to whatever the field already holds. iOS `fill` types the replacement once and does not clear and retype a mismatch on its own.
505+
Except for `TEXT_INPUT_SYNTHESIS_BUDGET_EXCEEDED`, where you append the rest with `type` after the shorter `fill`, recover from these with `fill`, never `type`, which appends to whatever the field already holds. iOS `fill` types the replacement once and does not clear and retype a mismatch on its own. On the iOS accessibility route, nonempty fills wait until the confirmation deadline before reporting `TEXT_ENTRY_MISMATCH`.
506506
507507
On iOS, a single-line `fill` ending in a submit key, or a field that became unreadable or disappeared after typing, succeeds without verification; assert the resulting screen.
508508

0 commit comments

Comments
 (0)