skip to content

Native Id and Type

The `id` and `class name` strategies look portable and are not: one platform matches a resource identifier and a widget class, the other an accessibility attribute and an element type.

on this pageshow

explore

questions

5

In Appium, what does the `id` locator strategy match on Android, and what on iOS?

level: juniorimportance: must knowfreq 76%

answer

  1. same keyword, two different attributes
  2. one platform reads a build resource
  3. the other reads an accessibility attribute
  4. resource-id versus name
  5. no prefix on Apple platforms

basics

~20 s

On Android the id strategy matches an element's resource-id, the package-qualified identifier fixed in the build. On iOS it is an alias for the name attribute, which XCUITest fills from the accessibility identifier. Same keyword, two unrelated attributes.

solid answer

~40 s

`id` is a locator strategy both native drivers accept, but each resolves it against a different attribute. On **Android**, the UiAutomator2 driver matches the element's `resource-id` — the identifier fixed in the app's build and reported in the page source in package-qualified form such as `com.example.app:id/login_button`; a bare name is prefixed with the package under test before matching. On **Apple platforms**, the XCUITest driver aliases `id` onto its `name` attribute, which it fills from the element's `accessibilityIdentifier`. So the same string is a build resource on one platform and an accessibility attribute on the other. They are written by different app-side code and are equal only when someone deliberately made them equal, so an `id` locator is platform-specific until you prove otherwise.

code

bash · 11 lines
bash
SESSION="$1"

# Android (UiAutomator2): 'id' compares against the resource-id attribute
curl -s -X POST "http://127.0.0.1:4723/session/$SESSION/element" \
  -H 'Content-Type: application/json' \
  -d '{"using": "id", "value": "com.example.app:id/login_button"}'

# Apple platforms (XCUITest): 'id' is an alias for the name attribute
curl -s -X POST "http://127.0.0.1:4723/session/$SESSION/element" \
  -H 'Content-Type: application/json' \
  -d '{"using": "id", "value": "loginButton"}'

go deeper

for a junior

Be ready to say plainly which attribute id matches on each platform: resource-id on Android, the name attribute on iOS. Knowing the two are different is the whole ask at this level.

for a middle

Explain the mechanics: the package-qualified form on Android and the automatic prefixing that hides it, and the alias from id onto name on Apple platforms with its accessibility-identifier source.

for a senior

Show how you diagnose an id locator that resolves on one platform and returns nothing on the other, using the live page source per device rather than reasoning from the app's source code.

for a principal

Own the tradeoff of letting a shared locator constant imply that two unrelated app-side fields agree, and say what you would require of a codebase before allowing that assumption.

## What `id` is at the wire level A find in Appium is an HTTP request: `POST /session/:sessionId/element`, carrying a body of `{"using": "id", "value": "..."}`. `id` is one of the strategy names both native drivers declare — it is on the UiAutomator2 driver's list for Android and on the XCUITest driver's list for iOS, iPadOS and tvOS. **Appium's core does not define what `id` means.** The driver that owns the session decides which attribute of the on-screen element the string is compared against, and the two mobile drivers picked attributes that have nothing to do with each other. That shared strategy name is exactly what makes the trap convincing. The request is well formed on both platforms, the server answers on both, and one page object compiles and runs everywhere. Nothing in the protocol tells you that the two runs consulted different data. ## Android: `id` reads the `resource-id` attribute On Android the UiAutomator2 driver matches the element's **`resource-id`** attribute — the same value the page source and the inspector display for that node. - The full form is package-qualified, for example `com.example.app:id/login_button`. - Given a bare `login_button`, the driver prepends the package under test before matching, which is why most suites get away with writing the short name. - The UiAutomator2 setting `disableIdLocatorAutocompletion` turns that prefixing off, which is what you need when the identifier was authored without a package. - The value is fixed when the app is built, so it is **not localised** — the same string in every language and every locale. - It carries **no uniqueness guarantee**. A list can render the same `resource-id` on every row, so a single-element find returns the first match and tells you nothing about the other twenty. - It is not part of the accessibility tree. A control can be perfectly addressable by `id` and still be unlabelled for a screen reader. ## Apple platforms: `id` is an alias for `name` The XCUITest driver resolves `id` against its **`name`** attribute, which it fills from the element's **`accessibilityIdentifier`**. Two consequences follow, and both surprise people arriving from Android: 1. `id`, `name` and `accessibility id` are **three strategy names for one attribute** here. Swapping between them changes nothing about which element comes back. 2. The value lives in the **accessibility layer**, so it exists only where app code put it there. A button with no identifier has no `id` to match, however obvious its caption looks on screen. There is one further wrinkle worth carrying: when the identifier is empty, XCUITest falls back to the element's accessibility label when it computes `name`, so an `id` find on Apple platforms can quietly match a **visible, translated** string and pass in English while failing in German. Nothing equivalent happens on Android, where `resource-id` is never a caption. ## The two side by side | | Android (UiAutomator2) | Apple platforms (XCUITest) | |---|---|---| | attribute matched | `resource-id` | `name` | | source of the value | the app's build resources | `accessibilityIdentifier` | | written form | `com.example.app:id/login_button` | a bare string, no prefix | | automatic prefixing | yes, switched off by `disableIdLocatorAutocompletion` | none | | relation to `accessibility id` | a different matcher on a different attribute | the same matcher — both alias `name` | | can match a translated caption | no | yes, when the identifier is unset | ## Writing the locator honestly Because the meaning is per-platform, the locator has to be per-platform too, and the discipline is small: 1. **Name the platform wherever the locator is defined.** A constant called `LOGIN_ID` used by both runs is a claim that the two attributes agree; make that claim explicit or split the constant. 2. **Read the value out of the live source, not out of the app's code.** `GET /session/:sessionId/source` shows exactly what the driver will compare against on that device. 3. **Expect the Android value to be long and the Apple value to be short.** If your iOS locator contains a colon and a slash, someone pasted an Android identifier into it. 4. **Do not assume one find equals one element** on either platform; check how many matches a multi-element find returns before trusting the first. ## The failure modes to expect - An `id` that resolves on Android and returns nothing on iOS, because app code set the resource identifier and never set `accessibilityIdentifier`. - An `id` that resolves on iOS and returns nothing on Android, because the identifier exists only in the accessibility layer. - An `id` find on iOS that passes in one language and fails in another, because it landed on the label fallback rather than a real identifier. - An `id` find on Android that returns the wrong row, because every row in the list shares the identifier and the driver handed back the first. The leaf-level thesis is short: `id` is portable as a **keyword** and not as a **meaning**. Treat it as two strategies that happen to share a spelling, and the surprises stop.

  • On Android, why can an `id` locator match more than one element?
    Because `resource-id` is not required to be unique on screen. A list, a tab bar or a repeated row template can render the same identifier many times, and a single-element find simply returns the first match in tree order. Ask for all matches when you need to know, and scope the search to a container element rather than trusting position.
  • On iOS, what happens to an `id` lookup when the control has no accessibility identifier?
    XCUITest computes `name` from the identifier, and when that is empty it falls back to the element's accessibility label. So the find either misses entirely or silently matches the visible caption. The first case is a clean failure; the second is worse, because the test passes until the app is translated or the copy is reworded.

saying these in an interview costs you the question

  • Claims id means the same attribute on Android and iOS
  • Thinks an Android resource-id also exists on an iOS build
  • Assumes an id match is always unique on screen
  • Believes iOS id and accessibility id are different matchers
  • Treats a shared id constant as automatically portable
  • Expects the package prefix to be required on Apple platforms
open as a page

In Appium, what does the `class name` strategy match on Android compared with iOS?

level: middleimportance: must knowfreq 61%

basics

~20 s

On Android the class name strategy matches a fully qualified widget class such as android.widget.Button. On iOS it matches an XCUIElementType value such as XCUIElementTypeButton. Both are exact string comparisons over two vocabularies that share nothing.

open as a page

In Appium on iOS, what changes when you swap the `id` strategy for `accessibility id`?

level: middleimportance: should knowfreq 47%

basics

~20 s

Nothing changes on iOS. XCUITest aliases id, name and accessibility id onto the same name attribute, so all three return the same element. On Android the swap changes the matched attribute entirely, and the results diverge.

open as a page

In Appium, why does a `class name` locator that works on Android hit the wrong control on iOS?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Because the two vocabularies have different resolution. An Android class string such as android.widget.EditText is often narrow enough to hit one control, while the iOS equivalent XCUIElementTypeTextField is a coarse role shared by many, so the find returns the first of several.

open as a page

In Appium, why do shared `id` and `class name` locators make one suite two different tests?

level: seniorimportance: should knowfreq 53%

basics

~20 s

Because only the strategy name is shared. On Android the driver compares against resource-id and a Java widget class; on iOS against the name attribute and an XCUIElementType. One locator constant therefore exercises two unrelated app-side contracts.

open as a page