skip to content

In Appium on Android, why is a Jetpack Compose test tag unmatchable until you set `mapTestTagToResourceId`?

level: middleimportance: should knowfreq 38%

answer

  1. two descriptions, driver reads only one
  2. the tag is absent from the page source
  3. a setting reports it as resource-id
  4. pair it with the autocompletion setting
  5. Android only, no iOS twin

basics

~20 s

A Compose test tag lives in Compose's own semantics description, and Appium's UiAutomator2 driver reads Android's accessibility node tree instead. The UiAutomator2 setting mapTestTagToResourceId is what reports that tag in the resource-id slot so an id locator can reach it.

solid answer

~40 s

`Modifier.testTag` labels a composable for Compose's own test framework, which matches against Compose's semantics description. Appium does not read that. The UiAutomator2 driver builds its page source from Android's accessibility node tree, and a test tag is not published there as an addressable id by default, so the driver has nothing to match and the string never appears in the page source. UiAutomator2's `mapTestTagToResourceId` setting tells the driver to report the tag in the `resource-id` slot, after which the `id` strategy can find it. Because the tag arrives as a bare string with no package prefix, it is normally paired with `disableIdLocatorAutocompletion`. Both are Android-only: the iOS half of the same app has to set `accessibilityIdentifier` itself.

go deeper

for a junior

Know that a Compose test tag is not automatically something Appium can match on Android, and that a driver setting is what brings it into reach.

for a middle

Explain that UiAutomator2 builds its page source from Android's accessibility node tree rather than from Compose's semantics, and say what the setting reports and where.

for a senior

Work out why a tag is still missing from an Android page source, and say what the iOS half of the same screen has to set instead.

for a principal

Decide whether Compose screens are addressed by mapped test tag at all, given the setting is Android-only while the other platform still needs an identifier written in code.

## Two descriptions of the same screen A Compose screen is described twice. Compose keeps a semantics description of its own, which is what Compose's test framework matches `Modifier.testTag` against and what the platform's accessibility services are fed from. Appium's UiAutomator2 driver reads the second description: **Android's accessibility node tree**, the same one an assistive technology consumes, which the driver serialises into the page source a locator is matched against. Those two are not the same thing, and a test tag is a Compose-side label. Unless something publishes it into a node field the driver reports, the tag exists and is completely unmatchable from Appium. The string is not merely hard to find; it is absent from the page source, which is why the usual first reaction — write a better XPath — gets nowhere. ## What the setting does UiAutomator2's `mapTestTagToResourceId` setting tells the driver to report a Compose test tag in the `resource-id` slot of the page source. Once it does: - The tag becomes visible in a page-source dump, which is the first thing to check. - Appium's `id` strategy can match it, because `id` is the strategy that matches `resource-id` on Android. - The value arrives as the **raw tag**, with no package and no `:id/`, unlike an id declared in Android resources. - Nothing about the app changes: this is a driver-side reporting decision made per session. - Compose also has its own app-side opt-in for publishing test tags into the accessibility tree; either route ends the same way, with the value reaching a field the driver reports. ## The setting that almost always travels with it Because the mapped value has no package prefix, a bare `id` locator hits the other Android default: UiAutomator2 completes an unprefixed `id` value with the app package before matching. A locator of `badge-row-first-aid` becomes `com.example.scouts:id/badge-row-first-aid` and matches nothing, even though the string is now in the page source. Turning on `disableIdLocatorAutocompletion` makes the driver match the tag exactly as written. The two settings solve different halves of one problem, and confusing them is the common stumble: 1. `mapTestTagToResourceId` decides **whether the value is reported at all**. 2. `disableIdLocatorAutocompletion` decides **how the locator you send is spelled before matching**. 3. A scouting badge-tracker built on Compose usually needs both on for its Android sessions. ## There is no iOS half of this | | Android | iOS | |---|---|---| | Driver | UiAutomator2 | XCUITest | | Tree the driver reads | Android accessibility node tree | the XCUITest element hierarchy | | Where a test tag can be reported | `resource-id`, via `mapTestTagToResourceId` | nowhere — there is no `resource-id` | | What the app must set instead | nothing extra once mapped | `accessibilityIdentifier` on the view | | Attribute a locator then matches | `resource-id`, via the `id` strategy | `name`, via `id` or `accessibility id` | The divergence is total: the Android side can be fixed with a session setting and the iOS side cannot. There, the identifier is an app change — a view has to carry `accessibilityIdentifier`, which XCUITest resolves behind the `name` attribute. No driver setting invents one. A team that plans the Android half as a settings change and forgets that the other half is a code change discovers it late. ## Working out why a tag is still missing - Dump the Android page source and search for the tag string rather than assuming where it should sit — a tag can be reported on a different node than the one you pictured. - If the string is absent entirely, the mapping is the suspect: check that the setting is on for this session, not just for the one you ran yesterday. - If the string is present but the find fails, the completion is the suspect, and prefixing the locator by hand once will confirm it. - If it works in one session and not the next, look at where the settings live: a setting sent per test rather than in the session profile makes the same locator mean two things in one run. The answer worth giving is short: Appium reads Android's accessibility node tree, a Compose test tag is not in it as an addressable id by default, `mapTestTagToResourceId` puts it there under `resource-id`, and the iOS side has to set its own identifier because none of this applies there.

  • On the iOS half of the same scouting badge-tracker, what plays the role of the mapped test tag?
    Nothing maps it for you. The view has to set `accessibilityIdentifier` itself, and XCUITest reads it behind the `name` attribute, where both the `id` and `accessibility id` strategies find it. There is no `resource-id` on that side and no driver setting that invents one, so it is an app change rather than a session setting.
  • Once the tag is mapped on Android, why might a bare `id` locator still find nothing?
    Because the mapped value arrives as the raw tag with no package prefix, while UiAutomator2 completes a bare `id` locator with the app package before matching. Turning on `disableIdLocatorAutocompletion` makes the driver match the string exactly as written, which is why the two settings are usually enabled together.

saying these in an interview costs you the question

  • Assumes Appium reads Compose's own semantics description
  • Thinks a test tag is matchable with no driver setting
  • Expects the same setting to exist on iOS
  • Confuses mapTestTagToResourceId with disableIdLocatorAutocompletion
  • Believes a mapped tag arrives package-qualified