skip to content

In Appium, why does a test id kept in Android's content-desc break on a copy change when an iOS accessibilityIdentifier does not?

level: seniorimportance: should knowfreq 45%

answer

  1. one description field, two customers
  2. announced text and locator share it
  3. iOS splits identity from label
  4. move the machine id to resource-id
  5. an empty identifier revives the same bug

basics

~20 s

Android publishes one description field, so content-desc is both what an accessibility id locator matches and what the platform announces, and rewriting the copy rewrites the locator. iOS splits the two: accessibilityIdentifier holds the machine id and the label holds the text.

solid answer

~40 s

On **Android** the view publishes a single description, reported as `content-desc`. That one field is simultaneously what Appium's `accessibility id` strategy matches and what the platform announces to a user, so a copy team rewording it also moves the locator — and nobody involved in the reword touched a test. On **iOS** the two jobs live in two fields: `accessibilityIdentifier` for the machine identity and the label for the announced text. XCUITest resolves the `name` attribute onto the identifier when it is set, so rewording the label leaves the locator untouched. The Android remedy is to stop using the shared field for identity: put the machine id in `resource-id`, match it with the `id` strategy and `disableIdLocatorAutocompletion`, and leave `content-desc` to the humans.

go deeper

for a junior

Know that Android publishes one description field while iOS publishes a separate identifier and label, so a test id belongs in different places on the two platforms.

for a middle

Explain that content-desc is both what accessibility id matches and what the platform announces, while XCUITest resolves name onto the identifier when one is set.

for a senior

Show the migration end to end: machine ids into resource-id with the matching driver setting, human text left in content-desc, one screen at a time with verification.

for a principal

Decide which field pair the whole app fills, and settle who is accountable when a copy change breaks a locator that no test author ever touched.

## One field on Android, two on iOS This is the clearest divergence in the whole subject of app-side test ids, and it is a durability question rather than a syntax one. Android gives a view **one** description, which the page source reports as `content-desc` and which Appium's `accessibility id` strategy matches. That same field is what the platform reads out to a user. It has two customers with incompatible needs: automation wants a value that never changes, and a person wants a phrase that describes what the control does, in their language. iOS gives a view **two** fields. `accessibilityIdentifier` is a machine identity that is never announced; the label carries the announced text. XCUITest resolves the `name` attribute onto the identifier when the identifier is set, so a locator addresses the machine field while the copy team owns the other one independently. | | Android | iOS | |---|---|---| | Field a machine id can occupy | `content-desc` (shared) or `resource-id` | `accessibilityIdentifier` (dedicated) | | Field that carries announced text | `content-desc` — the same one | the label | | What `accessibility id` matches | `content-desc` | `name` | | What `name` resolves to | not applicable | the identifier, or the label when the identifier is empty | | Effect of a copy reword | moves the locator if the id lives there | none, while an identifier is set | ## How the failure actually arrives A scouting badge-tracker ships a badge row whose Android `content-desc` reads `First aid badge, 3 of 5 requirements complete`. The suite matches it with an `accessibility id` locator, and it works. Two sprints later the copy is reworded for clarity, or the app ships in a second language. Nothing in the test repository changed, no developer touched a locator, and the Android suite goes red on a screen that works perfectly. The reverse choice fails the other way. Putting `badge-row-first-aid` in `content-desc` to make the locator stable makes that string the announced description, which is a user-facing regression introduced for the benefit of a test. ## Why the iOS side looks fine, and where it is not With a real `accessibilityIdentifier` set, the same reword changes nothing: `name` resolves to the identifier and the locator is untouched. The trap on that side is the fallback. When the identifier is **empty**, XCUITest's `name` resolves to the element's label instead, and an `accessibility id` locator quietly matches announced text. The find still succeeds, so the missing identifier is invisible until the reword or the second locale arrives — at which point iOS fails exactly like Android, only later and with less warning. ## The remedy, in the order you would actually do it 1. Choose the field pair the app will fill: a machine id in Android's `resource-id` and in the iOS `accessibilityIdentifier`, with `content-desc` and the label left for people. 2. Because a generated id carries no package prefix, turn on UiAutomator2's `disableIdLocatorAutocompletion` for Android sessions so the `id` strategy matches the value verbatim. 3. Move one screen at a time: add the id, confirm it appears in the page source on both platforms, re-point the locators, then delete the old ones. 4. Keep the old `accessibility id` locators working until each screen is verified, so the migration never leaves the suite red for a reason unrelated to the product. ## What this buys, and what it costs - The locator stops depending on user-facing copy, which is the single largest source of avoidable Android locator churn. - The announced description goes back to being written for people rather than for a matcher. - The two platforms end up in the same shape — a stable machine field plus a human field — even though the field names differ. - The cost is that Android and iOS now use different strategies, `id` against `resource-id` and `accessibility id` or `id` against `name`, so a shared locator layer has to branch by platform. - The cost is also a session setting the Android lane must carry, which belongs in the capability profile rather than in a test. ## What a strong answer sounds like Name the asymmetry first: one field on Android doing two jobs, two fields on iOS. Then explain the consequence in production terms — a copy change is a locator change on Android and is not on iOS. Then give the remedy and its price. A candidate who says only that locators break when text changes has described a symptom shared by every tool; the platform-specific reason is the answer.

  • Your Android badge rows already ship a human `content-desc` that `accessibility id` locators match. What is the migration?
    Move the machine id into `resource-id`, re-point the Android locators at the `id` strategy, and leave `content-desc` for the announced text. Because generated ids are not package-qualified, the Android session also needs `disableIdLocatorAutocompletion`. Do it screen by screen, confirming the value appears in the page source before the old locator is deleted.
  • Does the same reasoning apply on a screen written with a cross-platform toolkit?
    Yes, because the field belongs to the platform rather than to the toolkit. Whatever writes the Android view's description ends up in `content-desc` and inherits the same one-field problem. The remedy is unchanged: keep the machine id in a field the suite matches, and the human text in the field the platform announces.

saying these in an interview costs you the question

  • Thinks Android has a separate identifier field like iOS
  • Puts machine ids into content-desc and calls it solved
  • Assumes a copy reword cannot break a native locator
  • Treats content-desc and accessibilityIdentifier as the same field
  • Believes resource-id changes when visible copy changes