In Appium, what does the app capability accept, and which identity key does each platform read from it?
answer
- one artifact, two identity keys
- path or URL, resolved server-side
- Android manifest, iOS Info.plist
- appPackage on Android, bundleId on iOS
- no app means the installed copy
basics
~20 sThe app capability takes a local path or http URL to one build artifact: an APK for Android, an IPA or .app bundle for iOS. Each driver reads the app identity from it - the Android package name, the iOS bundle id.
solid answer
~50 s`app` names the build under test. It accepts an absolute path on the host running the **Appium server** - not the host running your test - or an `http(s)` URL the server fetches before the session, and the artifact must suit the platform: `.apk` or `.apks` for the Android drivers, a signed `.ipa` or a `.app` bundle for the XCUITest driver on Apple platforms. Each driver then reads the app's identity out of that artifact instead of making you repeat it: on Android the package name, the value you would otherwise pass as `appium:appPackage`, taken from the manifest inside the package; on iOS the bundle id, `appium:bundleId`, taken from the bundle's `Info.plist`. Pass the identity capability *without* `app` and you are telling the driver to work with the copy already on the device. `app` is not a W3C standard capability name, so it travels prefixed, as `appium:app`.
code
json · 22 lines{
"androidShipsTheArtifact": {
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:app": "/srv/builds/laundry-pickup-release.apk"
},
"iosShipsTheArtifact": {
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:app": "https://builds.internal/LaundryPickup.ipa"
},
"androidAddressesInstalledBuild": {
"platformName": "Android",
"appium:automationName": "UiAutomator2",
"appium:appPackage": "com.fluffcycle.pickup"
},
"iosAddressesInstalledBuild": {
"platformName": "iOS",
"appium:automationName": "XCUITest",
"appium:bundleId": "com.fluffcycle.pickup"
}
}go deeper
Recall that one capability, app, names the build file, and that the Android package name and the iOS bundle id are separate capabilities the driver can usually derive from that file.
Explain the mechanics: which artifact types each platform accepts, that the path is resolved on the Appium server host, and where each driver reads identity from - the Android manifest and the iOS Info.plist.
Show judgement about delivery: when a suite ships the artifact every session versus addresses an installed build, and how a missing app quietly turns a run into a test of whatever the device already holds.
Own the contract across a fleet: make the capability set state artifact and identity explicitly on both platforms so provenance of the build under test is recorded rather than inferred from a device's contents.
## What `app` actually points at `app` is the capability that tells an Appium session which build it is driving. Appium's own base constraint set declares it alongside `platformName`, `automationName` and `udid`, so every mobile driver understands it. It is **not** one of the twelve W3C standard capability names, so it is sent with the vendor prefix as `appium:app`, or nested inside `appium:options`. What it takes is an **artifact**, never a name: - an absolute filesystem path, resolved on the machine running the **Appium server** - not the machine running your test code; - an `http(s)` URL, which the server fetches before the session starts; - nothing at all, which is a deliberate choice covered further down. The first of those bites a suite the first time it points at a server on another host: `/Users/me/out/laundry-pickup.apk` means nothing to a server running elsewhere, and the fix is a URL the server can reach, not a longer path. ## The two platforms, side by side | | Android drivers (UiAutomator2, Espresso) | XCUITest driver (Apple platforms) | |---|---|---| | artifact `app` accepts | `.apk`, `.apks` | `.ipa`, or a `.app` bundle | | identity capability | `appium:appPackage` | `appium:bundleId` | | where that identity lives | the manifest inside the package | `Info.plist` inside the bundle | | example value | `com.fluffcycle.pickup` | `com.fluffcycle.pickup` | | extra condition on the artifact | a signature the device will accept | the build must be signed for the target device | The two values usually look identical, because teams keep the Android package name and the iOS bundle id in step. They are still different fields, read out of different files, by different drivers, under different capability names. Sending `appium:appPackage` to an iOS session or `appium:bundleId` to an Android one does not cross over; each is simply an unknown key to the other driver. ## Identity is derived, not asserted The reason `app` alone is usually enough is that each driver **extracts** the identity from the artifact you handed it: - the Android drivers inspect the package's manifest and learn the package name (and the launchable activity, which is a launch-behaviour concern rather than an identity one); - the XCUITest driver reads the bundle's `Info.plist` and learns the bundle id. So for a laundry pickup app, an Android capability set carrying `appium:app` set to `/srv/builds/laundry-pickup-release.apk` is complete: the package name is derived. The iOS twin carrying `appium:app` set to `https://builds.internal/LaundryPickup.ipa` is equally complete: the bundle id is derived. You still set the identity capability explicitly in two situations: 1. **There is no artifact.** You want the copy already installed, so there is nothing to read the identity out of. 2. **The artifact's identity is not the one you want to address.** A build flavour that appends a suffix to the package name is the usual case; the explicit capability then wins over what the file declares. ## Giving identity without an artifact Omit `app` and pass only `appium:appPackage` on Android or `appium:bundleId` on iOS, and you have said something specific: deliver nothing, work with the copy already installed. The session opens against whatever build the device happens to hold. That is fast - no upload, no install - and it is how a warmed device is usually driven. It also means the build is no longer described by the capability set: nothing in the session records which version ran, and if the device is stale, the run is stale and green. ## Failure shapes worth recognising - **A path that only exists on your laptop.** The server resolves it, so a remote server reports the artifact as missing. - **The wrong artifact for the platform.** An `.apk` offered to the XCUITest driver, or an `.ipa` offered to an Android driver, fails at install time, not at capability validation. - **An unprefixed `app`.** It is not a W3C standard name, so an unprefixed key is rejected by the server as an invalid argument. - **Identity crossed over.** `appium:bundleId` on Android does not become the package name; it is just an unrecognised capability there. - **Assuming identity implies delivery.** Naming a package or a bundle id never installs anything. ## Why the split matters Separating *what to deliver* from *what to address* is what lets one suite run three ways without touching test code: ship the artifact every session, ship it once and address it by identity afterwards, or never ship it at all against a fleet somebody else provisions. The capability set is the only place that decision is written down, so write it out explicitly for Android and for iOS rather than letting each driver's defaults decide for you.
- If you send only appium:bundleId on iOS and no app, what has to be true of the device?The build must already be installed. The XCUITest driver delivers nothing in that configuration; it addresses the installed copy under that bundle id and the session fails if nothing matches. Android behaves the same way with `appium:appPackage` and no `appium:app`.
- The Android drivers read the package name out of the artifact - when would you still set appium:appPackage explicitly?When there is no artifact at all, or when the artifact declares a package name you do not want to drive - a flavour that appends a suffix is the common case. The explicit capability overrides what was read from the file.
- Why does an absolute path in app sometimes work locally and fail against a shared server?`appium:app` is resolved by the process running the Appium server, not by your test process. On a local server both are the same machine; against a shared server the path is meaningless, so the artifact has to travel as a URL the server can fetch.
The artifact is the sealed box you hand the courier; the package name and bundle id are the address on the label. Appium can read the address off the box, or take the address alone and work with the box already on the doorstep.
saying these in an interview costs you the question
- Thinks app can hold a package name or a bundle id
- Assumes one artifact installs on both Android and iOS
- Sends appium:appPackage to iOS or appium:bundleId to Android
- Believes app must always be a local file path
- Thinks naming a package or bundle id installs the build
- Sends app unprefixed as a standard W3C capability