skip to content

An Appium session drives yesterday's build though the app capability names a new artifact. Why?

level: seniorimportance: must knowfreq 55%

answer

  1. the install is conditional, not automatic
  2. identity and version, not file bytes
  3. same capability name, two driver defaults
  4. enforceAppInstall overrides the judgement
  5. pin it on both platforms

basics

~20 s

Because the install is conditional. Both the Android drivers and the XCUITest driver compare the artifact against the build already installed under the same identity and skip the install when they judge it unnecessary. appium:enforceAppInstall forces it.

solid answer

~50 s

Neither platform reinstalls unconditionally. The Android drivers compare the artifact named by `appium:app` against the package already installed under that identity and skip the install when they judge the device already holds it; the XCUITest driver makes the equivalent decision against the installed bundle id. A rebuild that does not change the declared version is a *new file* with the *same identity and version*, so nothing is replaced and the session runs old code. `appium:enforceAppInstall` is the switch that overrides that judgement and installs regardless - it is declared by the Android drivers and by the XCUITest driver, and the default belongs to each driver rather than being shared, so pin it explicitly on both sides instead of inheriting it. If `appium:app` is a URL, also check that the URL really serves the new file.

go deeper

for a junior

Know that naming an artifact in app does not guarantee it is installed, and that appium:enforceAppInstall is the capability that makes the install happen regardless of what the device already holds.

for a middle

Explain the comparison each driver makes - identity and declared version against the installed copy - and why a rebuild with an unchanged version is treated as the same build on Android and on iOS.

for a senior

Diagnose it live: read the install decision in the session log, isolate it with one enforced re-run, and separate a skipped install from a build server serving a stale artifact.

for a principal

Decide the fleet-wide policy: where the install is enforced, how build provenance is recorded per run, and why a silent pass on unverified device contents is the failure this policy exists to prevent.

## Why a session can run stale code `appium:app` names an artifact, and it is tempting to read that as *deliver this file to the device*. Neither platform promises that. Both driver families treat the install as conditional: they look at what the device already holds under the same identity and decide whether delivering the artifact is necessary at all. When they decide it is not, the session opens against the build that was already there, the suite runs, and nothing in the result says it drove last week's code. The decision is made from the artifact's **identity and declared version**, not from the bytes of the file. That is exactly why a rebuild fools it: a pipeline that recompiles the same source produces a byte-different artifact whose package name or bundle id and whose declared version are unchanged, so the driver concludes it is looking at the same build. ## What each side compares | | Android drivers | XCUITest driver | |---|---|---| | what identifies the installed copy | the package name from the manifest | the bundle id from `Info.plist` | | what the driver weighs | the artifact's identity and version against the installed package | the artifact's identity and version against the installed bundle | | the override capability | `appium:enforceAppInstall` | `appium:enforceAppInstall` | | where the default comes from | that driver's own declaration | that driver's own declaration | The override has the same name on both sides, which makes it easy to assume the behaviour behind it is shared too. Treat that assumption as unsafe. The two drivers make their own install decisions in their own code and each declares its own default, so a cross-platform suite that relies on the default is relying on two separate agreements it never read. ## The switch `appium:enforceAppInstall` forces the artifact to be installed even when the driver would otherwise conclude the device already has it. Set to `true`, every session pays an install and is hermetic with respect to the app binary. Set to `false`, the session accepts whatever is installed under that identity. 1. **Pin it explicitly on both platforms.** One line in each capability set removes a per-driver default from your suite's behaviour. 2. **Enforce where the build changes.** The job that has just produced a build should install it, deliberately and once. 3. **Think before enforcing on every session of a large parallel run.** The install is paid per session, per worker. 4. **Never infer the running version from the capability set.** The set records which artifact was offered; only the device knows which build ran. ## Diagnosing it in a real run For a laundry pickup suite that suddenly shows the old pickup-scheduling screen: - **Read the session log first.** The install decision is taken at session start and is visible there; guessing from test symptoms wastes the run. - **Re-run once with `appium:enforceAppInstall` set to `true`.** If the new behaviour appears, the install was being skipped and your problem is the decision, not the artifact. - **If it still runs old code, suspect the artifact.** When `appium:app` is a URL, fetch that URL by hand and look at what it serves; a build server can hand out a stale file indefinitely. - **Check the capability set for a missing `app`.** A set carrying only `appium:appPackage` or `appium:bundleId` installs nothing by design, so the stale build is precisely what was requested. - **Check for identity drift.** If the artifact's package name or bundle id is not the one the device holds, the driver is comparing against a different application entirely and may install alongside it rather than over it. ## Two things this is not - It is not a **stored-data** problem. Forcing a reinstall is about replacing the binary; what happens to the app's saved state between runs is a separate capability question with its own answer on each platform. - It is not a **build-selection** problem. Deciding which build a suite ought to exercise is a test-design decision. This is only about whether the build you named actually reached the device. ## The habit worth forming Make the install decision explicit and make it observable. Write `appium:enforceAppInstall` into the Android and the iOS capability sets, keep the artifact's identity and declared version visible in the pipeline output, and treat a green run on a device you did not provision as unverified. This failure mode is uniquely nasty because it is silent: the suite passes, against the wrong code, and reports success.

  • How would you tell a skipped install apart from a stale artifact?
    Re-run once with `appium:enforceAppInstall` set to `true`. If the new behaviour appears, the driver had been skipping the install. If the run still shows old behaviour, the artifact itself is stale - fetch the URL or check the file the path resolves to on the server host.
  • Why is enforcing the install on every session not a free choice?
    The install is paid per session and per worker, so on a wide parallel run it can dominate session start. The usual compromise is to enforce once per device when the build changes, then run sessions that address the installed build by identity.
  • If the artifact's package name or bundle id differs from the installed one, what happens?
    The driver is no longer comparing like with like. It sees no build under the new identity, installs the artifact, and the device can end up carrying both - which is why a suite that also addresses the app by identity must keep that identity in step with the artifact it ships.

saying these in an interview costs you the question

  • Assumes a new artifact always replaces the installed build
  • Thinks enforceAppInstall exists only on Android
  • Relies on the default rather than pinning it per platform
  • Believes a rebuilt file is different because its bytes changed
  • Confuses forcing a reinstall with clearing stored data
  • Trusts the capability set to prove which version ran