feat(ios): the snapshot node carries the field's placeholder
Summary
Android nodes have carried the field's hint as placeholder since #2927; iOS nodes carried nothing. A consumer could neither find a field by its placeholder nor tell an empty field from one holding that text, since XCTest reports an empty field's placeholder as its value. XCTest exposes placeholderValue on every element and snapshot, and the AX server exposes XC_kAXXCAttributePlaceholderValue.
Every iOS producer now publishes placeholder on RawAXNode and PresentedNode, absent when empty:
- the XCTest tree and the query sweeps read
placeholderValueoff the snapshot or element (SnapshotEvaluationcarries it); - the runner's private-AX reader asks for the
placeholderValuekeypath besidevalue; - the Simulator AX bridge requests the attribute as optional: a runtime whose vocabulary lacks it serves the capture without placeholders instead of failing it; the source version moves to
v1.8.0, so each host recompiles the bridge once.
The element sweeps read placeholderValue on text-entry types only (a live XCUIElement attribute is one lookup per element inside the sweep's deadline); the snapshot producer reads it off the snapshot it already holds.
agent-device snapshot --raw --json | jq '.data.nodes[] | select(.identifier=="name-input") | {value, placeholder}'
# {"value":"Ada Lovelace","placeholder":"Type your name"}
The kernel field, the unchanged-digest fields, and the response views already knew placeholder from #2927, so nothing on the wire changes shape. 17 files: 9 Swift/ObjC, 5 TypeScript and fixtures, 3 tests and docs.
Validation
Tested at 9ef008834f (third commit: optional bridge attribute, trimmed placeholder, text-entry-only sweep reads; pnpm check:affected --run --base c31cc49f6 passed on it, and the three host-lane XCTests pass on the rebuilt runner):
pnpm check:affected --run --base c31cc49f6: all runnable checks passed,check:xctest-selectionandcheck:packaged-runner-swiftamong them.- Host-lane XCTest on an iOS 26.5 simulator (Xcode 26.6 Build version 17F113 ):
testSnapshotPresentationPreservesCurrentWireShape(the wire shape now namesplaceholder) and the newtestPrivateAXAcquisitionCarriesTheFieldPlaceholderpass;pnpm test:ios-snapshot-differentialpasses. tree.test.tspins the bridge mapping and that an empty attribute is omitted;protocol.test.tspins the v1.8.0 vocabulary in the native source.- Live, dev daemon,
dev.e2e.benchmarkControl Inventory, on the rebuilt runner:snapshot --actions --jsonpins the private-AX tier (snapshotQuality.backend: private-ax,reasonCode: requested-backend); the runner log showsAGENT_DEVICE_RUNNER_PRIVATE_AX_SNAPSHOT_USED nodes=58 depth=64for that capture, andname-inputcarriesvalue: "Ada Lovelace"besideplaceholder: "Type your name", withpassphrase-inputandkey-inputcarrying theirs. The XCTest tree producer on an iPhone 17 Pro reportsplaceholderon the three fields (Type your name,Passphrase,Key echo), a filled field included; the v1.8.0 bridge on an iPhone 17 Pro Max reports the same field withplaceholder: "Type your name",role: RCTUITextField,value: "Ada Lovelace". - Risk: a runtime whose AX vocabulary lacks
XC_kAXXCAttributePlaceholderValuefails the bridge's existing vocabulary check and falls back to the runner, as any unknown attribute does today.