fix(ios): treat a bullet-only field value as unreadable during fill
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,testTextValueReadOwnsTheUnreadableRuletestSynthesizedReplacementCommitLeavesAnUnreadableFieldUnverified,testSynthesizedReplacementCommitEndsWhenTheFieldTurnsUnreadableMidWaittestSynthesizedClearLeavesAMaskedFieldUnverified
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-routereplace, must reporttypedwith zero toggle presses.testSynthesizedReplacementLeavesAMaskedFieldUnverified: penalized-routereplaceinto an empty and a pre-filled masked field, must reporttypedon the synthesized route with zero toggle presses. Fails withTEXT_INPUT_COMMIT_NOT_OBSERVEDfor 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.