Skip to content

Commit a407a75

Browse files
committed
fix(ios): keep a clear's retries on the input it started on
The clear's handle can be index-bound, so an input that replaced the cleared one, such as the next screen's after the delete navigated, read through it and got the next select-all and delete.
1 parent f49bba3 commit a407a75

3 files changed

Lines changed: 39 additions & 11 deletions

File tree

‎apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,16 @@ - (void)agentDeviceTextEntryDidChange:(UITextField *)textField {
539539
}
540540
// An auto-submitting one-time-code field: the last digit navigates away, to a screen whose own
541541
// input takes the code field's place or to one without an input. `replace-after-input` is the
542-
// same navigation landing before the code is complete.
542+
// same navigation landing before the code is complete, and `populated-successor` the same on any
543+
// edit, clearing included, to a screen whose input already holds a value.
543544
NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
544545
BOOL codeComplete = textField.text.length >= AgentDeviceTextEntryAutoSubmitLength;
545546
if (textField.superview == nil) {
546547
return;
547548
}
548549
if ((codeComplete && [arguments containsObject:@"--agent-device-text-entry-auto-submit"]) ||
549-
(textField.text.length > 0 && [arguments containsObject:@"--agent-device-text-entry-replace-after-input"])) {
550+
(textField.text.length > 0 && [arguments containsObject:@"--agent-device-text-entry-replace-after-input"]) ||
551+
[arguments containsObject:@"--agent-device-text-entry-populated-successor"]) {
550552
[self replaceTextEntryFieldWithNextScreenInput:textField];
551553
} else if (codeComplete &&
552554
[arguments containsObject:@"--agent-device-text-entry-auto-submit-without-successor"]) {
@@ -559,6 +561,9 @@ - (void)replaceTextEntryFieldWithNextScreenInput:(UITextField *)textField {
559561
if (![NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
560562
nextScreenField.accessibilityIdentifier = @"agent-device-auto-submit-next-screen-input";
561563
}
564+
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-populated-successor"]) {
565+
nextScreenField.text = @"next screen value";
566+
}
562567
nextScreenField.borderStyle = UITextBorderStyleRoundedRect;
563568
[textField.superview addSubview:nextScreenField];
564569
[textField removeFromSuperview];

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,11 @@ extension RunnerTests {
349349
#if !os(tvOS)
350350
moveCaretToEnd(app: app, input: input)
351351
#endif
352+
// The handle can be index-bound, so an input that took this one's place, such as the next
353+
// screen's after a clear navigated, reads through it too. Only `input` itself is cleared further.
354+
func readInput() -> ResolvedTextEntryInput? {
355+
ResolvedTextEntryInput(reading: input.element).flatMap { $0.identity == input.identity ? $0 : nil }
356+
}
352357
#if os(iOS)
353358
// The tap aims at the end of what the field shows, which a long value has scrolled past, so
354359
// bursts from there reach only what is before the caret (#3851). Select all reaches all of it, but
@@ -364,17 +369,14 @@ extension RunnerTests {
364369
return .posted
365370
},
366371
read: {
367-
textValueRead(
368-
for: ResolvedTextEntryInput(reading: input.element)?.attributes,
369-
treatingPlaceholderAsEmpty: true
370-
)
372+
textValueRead(for: readInput()?.attributes, treatingPlaceholderAsEmpty: true)
371373
}
372374
)
373375
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_CLEAR route=element passes=%d outcome=%@", passes, String(describing: outcome))
374-
// Text left after every pass goes to the bursts; a field that stopped resolving is not typed into.
376+
// Text left after every pass goes to the bursts; an input that stopped resolving, or that another
377+
// input replaced, is not typed into.
375378
guard outcome == .notCleared,
376-
let remaining = ResolvedTextEntryInput(reading: input.element)
377-
.flatMap({ editableTextValue(for: $0.attributes, treatingPlaceholderAsEmpty: true) })
379+
let remaining = readInput().flatMap({ editableTextValue(for: $0.attributes, treatingPlaceholderAsEmpty: true) })
378380
else {
379381
return
380382
}
@@ -386,8 +388,7 @@ extension RunnerTests {
386388
input.element.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: burst))
387389
// A burst that covers the value needs no read.
388390
guard burst < valueLength + Self.textClearSlack,
389-
let remaining = ResolvedTextEntryInput(reading: input.element)
390-
.flatMap({ editableTextValue(for: $0.attributes, treatingPlaceholderAsEmpty: true) }),
391+
let remaining = readInput().flatMap({ editableTextValue(for: $0.attributes, treatingPlaceholderAsEmpty: true) }),
391392
Self.textClearContinues(clearedLength: valueLength, remainingLength: remaining.count)
392393
else {
393394
return

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,28 @@ extension RunnerTests {
205205
XCTAssertEqual(editableTextValue(for: textField, treatingPlaceholderAsEmpty: true), "")
206206
}
207207

208+
// The clear's first delete navigates to a screen whose input, already holding a value, takes the
209+
// cleared one's place under the same handle. The clear keeps going only on the input it started on.
210+
@MainActor
211+
func testFillClearDoesNotEraseTheInputThatReplacedItsTarget() throws {
212+
let textField = try launchRemovableInputFixture(
213+
"--agent-device-text-entry-long-prefill",
214+
"--agent-device-text-entry-populated-successor"
215+
)
216+
217+
let failureCountBefore = currentXCTestFailureCount()
218+
_ = executeTypeCommand(
219+
activeApp: app,
220+
command: try fillCommandFixture(commandId: "fill-clear-replaced-input", text: "", at: textField)
221+
)
222+
223+
XCTAssertFalse(didRecordXCTestFailure(since: failureCountBefore))
224+
XCTAssertFalse(textField.exists)
225+
let nextScreenField = app.textFields["agent-device-auto-submit-next-screen-input"]
226+
XCTAssertTrue(nextScreenField.exists)
227+
XCTAssertEqual(editableTextValue(for: nextScreenField), "next screen value")
228+
}
229+
208230
// The reported shape: the last digit navigates to a screen without an input.
209231
@MainActor
210232
func testFillSucceedsWhenAppRemovesInputAfterLastCharacter() throws {

0 commit comments

Comments
 (0)