fix(android): carry the field's hint text on the snapshot node as placeholder - #2927
Conversation
…ceholder The helper wrote hint-showing but never the hint itself, and text holds the hint only while the field is empty, so a filled field's placeholder was readable nowhere. The helper now writes hint (getHintText, API 26+), the node carries it as placeholder whether or not it is showing, and the fact joins the unchanged map and the find/get digest. Value and label are unchanged.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The advertised placeholder selector query remains unsupported.
Review effort: Lite
Findings: None
What changed in this PR
Adds Android field hint text as placeholder metadata through snapshots and attribute responses.
Changes:
- Serializes and parses Android hints.
- Propagates placeholders through snapshots, digests, and unchanged comparisons.
- Adds documentation and regression tests.
| File | Summary |
|---|---|
website/docs/docs/snapshots.md |
Documents Android placeholder metadata. |
src/daemon/response-views.ts |
Includes placeholders in selector digests. |
src/daemon/__tests__/response-views.test.ts |
Tests digest behavior. |
src/commands/capture/runtime/snapshot-unchanged.ts |
Compares placeholder changes. |
src/commands/capture/runtime/snapshot-unchanged.test.ts |
Tests comparison behavior. |
packages/platform-android/src/ui-hierarchy.ts |
Parses and maps hints. |
packages/platform-android/src/ui-hierarchy-node.ts |
Adds placeholder metadata. |
packages/platform-android/src/ui-hierarchy-builder.ts |
Publishes placeholder data. |
packages/platform-android/src/__tests__/ui-hierarchy-placeholder.test.ts |
Tests placeholder propagation. |
packages/kernel/src/snapshot.ts |
Defines the snapshot field. |
android/snapshot-helper/src/main/java/com/callstack/agentdevice/snapshothelper/AccessibilityTreeXml.java |
Serializes native hint text. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Reviewed at c6463bd. The code looks correct: the Android snapshot helper carries the field's hint text through as placeholder, and the TS side parses and plumbs the new attribute cleanly. Smoke Tests are still queued, so there's nothing to attribute yet; if a later Android failure shows up, it would land on the same route this PR touches. I didn't run the Android helper or an emulator myself, so the live-device evidence here is the name-input/passphrase-input/key-input triplet the author pasted, not something I reproduced. Nothing here blocks merge on the current evidence. |
* feat(ios): the snapshot node carries the field's placeholder Android nodes carry the field's hint as placeholder since #2927; iOS nodes carried nothing, so a consumer could neither query a field by its placeholder nor tell an empty field (XCTest reports the placeholder as its value) from one holding that text. 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: the XCTest tree and query sweeps read placeholderValue off the snapshot or element, the runner's private-AX reader asks for the keypath beside value, and the Simulator AX bridge requests the attribute (source v1.8.0, one recompile per host). An empty placeholder reads as none. The docs name the fact for both platforms. * fix(ios): count a placeholder as content, keep value and placeholder apart in the tests Review follow-ups: the collapsed-tab producer read the placeholder only after its content check, so a placeholder-only field inside a collapsed container was dropped; the placeholder now counts as content. The bridge test used one string for value and placeholder, so it could not tell the two attributes apart; it uses distinct strings and asserts both. A wire-shape test pins that a node without a placeholder encodes no key. The docs stop calling a value equal to the placeholder an empty field, since a user can type that text. * fix(ios): make the placeholder attribute optional in the bridge vocabulary, read it on text entry only Review follow-ups. The Simulator AX bridge asked for the placeholder attribute in its required set, so a runtime whose vocabulary lacks it would have failed every capture; a mismatch now retries the mapping without the attribute and the capture serves without placeholders. The bridge trims the placeholder the way the runner does, so whitespace reads as none. The element sweeps read a live XCUIElement attribute per element inside their deadline, so they read placeholderValue on text-entry types only; the snapshot producer keeps reading it off the snapshot it already holds.
Summary
The helper wrote
hint-showingbut never the hint itself, andtextholds the hint only while a field is empty, so a filled field's placeholder was readable nowhere: aplaceholderquery or attribute read had nothing to match on Android.The helper now writes
hint(getHintText(), API 26 or later) whenever the field has one, showing or not. The parser, the Android node, and the published snapshot node carry it asplaceholdertoget attrs, the unchanged-snapshot comparison, and the selector digest.label,value, andhintShowingare unchanged: an empty field still reports the hint as its text withhintShowing: true, and the new field is the one place that says what the hint is once the field is filled. A field without a hint omits it.10 files. No
HELPER_API_VERSIONbump, as withselected(#2515) andchecked(#2913): an older helper omits the attribute, which means unavailable.Validation
Tested at the pushed head.
pnpm check:affected --run --base dc9ab863apassed: 854 test files, 6,677 tests, fallow and oxfmt clean.Rebuilt helper on an API 36 emulator, React Native form,
snapshot --jsonandget attrs:The filled field's placeholder appears nowhere else in the tree. A WebView
<input>named by<label for>carries no hint (Chrome writes none), so it stays without one.Risk: an
unchangedcomparison now notices a placeholder change.