Skip to content

fix(android): carry the content description beside the label on the snapshot node - #2912

Merged
thymikee merged 1 commit into
callstack:mainfrom
okwasniewski:oskar/android-content-description
Sep 24, 2026
Merged

thymikee merged 1 commit into
callstack:mainfrom
okwasniewski:oskar/android-content-description

Conversation

@okwasniewski

@okwasniewski okwasniewski commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

An Android node is labelled by its text and falls back to the content description only when it has none (ui-hierarchy.ts, label: text || desc), so an accessibility label set beside visible text never reached the wire: a labelled text view was named by its content, a filled field by its value, an empty field by its hint, a filled password field by its dots. TalkBack reads the content description in each case.

The parser already read content-desc; the hierarchy node now carries it as contentDescription whenever it is not already the label, through the raw snapshot node, the unchanged-snapshot comparison, and the find/get digest. label is unchanged, so nothing that keys on it moves. 7 files, +37/-1.

agent-device snapshot --json --platform android
# {"type":"android.widget.EditText","label":"Ada Lovelace","contentDescription":"Name field","identifier":"name-input"}

Validation

Tested at c6dbc8a; the test-only follow-up d5ddd39 passed the same gate.

  • pnpm check:quick and pnpm check:affected --run --base a363a0239: all runnable checks passed (format, lint, typecheck, layering, fallow, build, 852 test files).
  • src/__tests__/android-ui-hierarchy.test.ts: 39 tests, one new for text beside a description, a description equal to the label (not repeated), and a hinted empty field.
  • Live on an Android emulator (API 36, tester-army/e2e's mobile benchmark app, helper 0.21.13): the header and three text fields carry their accessibilityLabel as contentDescription; the status-bar clock carries its spoken time beside its label. Text presentation unchanged.
  • Risk: none identified; additive, absent without a distinct description. The text snapshot does not show it yet (follow-up).

Motivation: e2e-inc/e2e#500.

…napshot node

An Android node is labelled by its text and falls back to the content description only when it has none, so an accessibility label the app set beside visible text never reached the wire: a labelled text view was named by its content, a filled field by its value, an empty field by its hint, a filled password field by its dots. The parser already read `content-desc`; the hierarchy node now carries it as `contentDescription` whenever it is not already the label, through the raw snapshot node, the unchanged-snapshot comparison, and the selector digest. `label` is unchanged, so nothing that keys on it moves.
Copilot AI lite review requested due to automatic review settings September 24, 2026 14:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Add regression coverage for changed/equal descriptions and selector-digest propagation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 Low severity

Open (2)
What changed in this PR

Adds Android contentDescription alongside visible labels and carries it through snapshots and selector digests without changing label behavior.

Changes:

  • Preserves distinct Android content descriptions.
  • Includes them in snapshot-change detection and selector digests.
  • Adds parser coverage for description handling.
File Reviewed changes
src/​daemon/​response-views.ts Includes content descriptions in selector digests.
src/​commands/​capture/​runtime/​snapshot-unchanged.ts Compares content descriptions for snapshot freshness.
src/​__tests__/​android-ui-hierarchy.test.ts Tests content-description parsing.
packages/​platform-android/​src/​ui-hierarchy.ts Derives distinct descriptions.
packages/​platform-android/​src/​ui-hierarchy-node.ts Extends the Android node type.
packages/​platform-android/​src/​ui-hierarchy-builder.ts Publishes descriptions in snapshots.
packages/​kernel/​src/​snapshot.ts Extends the snapshot node contract.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

subrole: true,
label: true,
value: true,
contentDescription: true,

@okwasniewski okwasniewski Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in #2920 (this PR merged before the rows landed): contentDescription is in the "detects presentation changes" table, and a new case keeps a node whose description did not change as unchanged.

'subrole',
'label',
'value',
'contentDescription',

@okwasniewski okwasniewski Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in #2920 (this PR merged before the rows landed): MATCHED_NODE and COMPACT_NODE now carry contentDescription, so the find/get digest assertions cover its retention.

@thymikee

Copy link
Copy Markdown
Member

Reviewed at c6dbc8a. The fix looks correct and I found no blocking issues. Not blocking: the new unit test doesn't add a changed-description/equal-description case for areSnapshotNodesEquivalent even though contentDescription now participates in snapshot-reuse invalidation, and SELECTOR_DIGEST_NODE_FIELDS in https://github.com/callstack/agent-device/blob/c6dbc8a/src/daemon/response-views.ts#L113 has no test confirming contentDescription survives compactSelectorNode for the find/get digest, both flagged inline already, and either can be picked up later or left as is.

I checked the diff, the PR-head files, and the new unit test, which does exercise the real parseUiHierarchyTree/buildUiHierarchySnapshot pipeline, but I did not independently reproduce the live emulator run described in the PR body. Android Smoke Tests covers the changed snapshot route (snapshot.ts → ui-hierarchy.ts → ui-hierarchy-builder.ts) but both runs were still queued at review time, so I can't say whether they pass on this head. Nothing further is needed from this review while those results are pending.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Sep 24, 2026
@thymikee
thymikee merged commit d5c21f8 into callstack:main Sep 24, 2026
12 of 13 checks passed

@thymikee thymikee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at c6dbc8a. The source behavior looks sound: contentDescription belongs in the presentation-equivalence key because it is visible through snapshot JSON and get attrs even while text rendering is deferred, and the parser fallback preserves label without repeating a description that is already the label. I have no additional source-level findings beyond Copilot's test and fixture notes.

Two user-surface updates are missing:

  • website/docs/docs/snapshots.md should list contentDescription beside the Android node keys and explain that it is Android-only and appears only when the description is distinct from label.
  • CHANGELOG.md needs an Unreleased entry for the new user-visible Android snapshot/get attrs field.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants