fix(ios): treat a bullet-only field value as unreadable during fill

Merged
OK
7d ago
+338-59

Summary

Fixes tester-army/e2e#872.

A React Native secureTextEntry input reaches XCTest as a plain .textField whose value is the masked rendering (••••••••), not a .secureTextField. The runner compared the typed text against the bullets, so every password fill on iOS failed: TEXT_ENTRY_MISMATCH on the element route (and a repair tap that flipped the overlaid show/hide toggle), TEXT_INPUT_COMMIT_NOT_OBSERVED on the channel-penalized synthesized route.

One rule now owns "this field's value is unreadable": textValueRead(for:) classifies a field as .text, .unreadable (secure type, or a value made only of password bullets U+2022 / U+25CF), or .unavailable. Every route reads through it: editableTextValue, the synthesized clear's clearedFieldRead, and the commit wait's fieldIsUnreadable and observe. A masked field is typed into and reported unverified, the same as a .secureTextField, and no repair runs.

Tests

Host lane (pure):

  • testMaskedTextValueIsBulletsOnly, testTextValueReadOwnsTheUnreadableRule
  • testSynthesizedReplacementCommitLeavesAnUnreadableFieldUnverified, testSynthesizedReplacementCommitEndsWhenTheFieldTurnsUnreadableMidWait
  • testSynthesizedClearLeavesAMaskedFieldUnverified

iOS lane, host app gains --agent-device-text-entry-masked-value (a UITextField whose accessibility value is bullets, with a "Show password" button over its right edge) and --agent-device-text-entry-prefilled:

  • testFillIntoMaskedTextFieldIsUnverifiedRatherThanAMismatch: element-route replace, must report typed with zero toggle presses.
  • testSynthesizedReplacementLeavesAMaskedFieldUnverified: penalized-route replace into an empty and a pre-filled masked field, must report typed on the synthesized route with zero toggle presses. Fails with TEXT_INPUT_COMMIT_NOT_OBSERVED for both without the rule.

Verified on an iPhone 17 Pro simulator: 72 tests across the text-entry suites pass, as do check:xctest-selection, check:packaged-runner-swift and the commit-wait log-redaction test.

Not covered: a refill of a pre-filled masked field on the element route still runs the clear's caret edge-tap, the same tap a native secure field with an overlaid toggle gets today.