skip to content

In Appium, how do you design one permission-seeding layer for a vineyard harvest-log suite on Android and iOS?

level: principalimportance: should knowfreq 33%

answer

  1. intent in, command out
  2. three ceilings, not two platforms
  3. declare what a target cannot do
  4. capabilities belong to session profiles

basics

~20 s

Express the intent, not the command: the layer takes a permission and a wanted state, and each platform adapter reaches it its own way. Because Apple real devices can only clear a decision, it must refuse states it cannot reach, loudly.

solid answer

~50 s

Design it around **intent plus a declared capability ceiling**, not around a shared command. The public call is something like ensure camera is denied; behind it, an Android adapter uses `mobile: changePermissions`, an Apple Simulator adapter uses `mobile: setPermission`, and an Apple real-device adapter has only `mobile: resetPermission` and therefore cannot honour granted or denied at all. Three design consequences follow. The layer needs its **own permission vocabulary** with two translations, because Android names a platform permission string and the XCUITest driver names a service — there is no shared key. Each adapter must **declare which states it can reach**, so a case asking for an unreachable one is skipped with a reason rather than drifting. And `appium:autoGrantPermissions` stays out of the per-case path: it is a session capability, so it belongs to a session profile, not to an intent.

go deeper

for a junior

Focus first on knowing which command belongs to which platform. A design answer only makes sense once mobile: changePermissions, mobile: setPermission and mobile: resetPermission are firmly attached to their drivers.

for a middle

Be able to explain why a single wrapper leaks: the commands take different identifiers, and one target class can only clear a decision rather than set one, so the abstraction has an uneven floor.

for a senior

Show the operational habits — seed after install and before activation, verify where read-back exists, reset on shared hardware at teardown, and skip loudly when a state cannot be reached.

for a principal

Own the framing: a capability ceiling that varies by target class is fleet data that belongs in the open, so planning sees the limit up front instead of discovering it as an unexplained red run.

## Why a naive wrapper fails The obvious design is a helper called `grant(permission)` that switches on `platformName` and forwards to the right execute method. It works until it meets an Apple real device, at which point the whole abstraction is exposed: the two platforms do not offer the same power, and no amount of wrapping makes them. A layer that hides an inequality it cannot remove produces the worst possible failure — a case that runs, does not fail, and quietly tests something other than what it says. The honest starting point is that this suite has **three target classes with three capability ceilings**, not two platforms. | Target class | Set granted | Set denied | Clear | Read back | |---|---|---|---|---| | Android emulator or device | yes | yes | yes | yes | | Apple Simulator | yes | yes | yes | yes | | Apple real device | no | no | yes | no | The bottom row is the whole design problem. On real Apple hardware `mobile: setPermission` and `mobile: getPermission` are Simulator-only, and `mobile: resetPermission` — which proxies to WebDriverAgent's `/wda/resetAppAuth` — only returns a permission to undecided. There is no route to a chosen value. ## The shape that survives Express **intent**, and let each adapter answer whether it can serve it: - The public surface takes a permission from **your own enum** and a wanted state, not a driver command. For the **vineyard harvest-log app**, a case says camera denied and never names `mobile: setPermission`. - Each adapter declares the states it can reach. That declaration is data the runner can read before the case starts, not an exception it discovers halfway through. - An unreachable intent produces a **loud skip carrying the state that could not be reached**, so the report says camera cannot be seeded denied here rather than element not found. - Verification is part of the contract, not a caller's afterthought. Android reads back with `mobile: getPermissions`, an Apple Simulator with `mobile: getPermission`, and a real Apple device cannot — which the adapter also declares. - Teardown resets on shared hardware, because a device that has answered a prompt stays answered and the next tenant inherits it. ## Two vocabularies, no shared key A subtle trap is the argument itself. Android's command names a platform permission string on a package; the XCUITest command names a service on a bundle. They are not the same identifier space and there is no clean mapping to key a map on, so the layer needs its own enum plus two translation tables. Any design that passes a caller-supplied string straight through will read fine in review and then diverge the first time a permission has different granularity on the two sides. ## Where the session capability belongs `appium:autoGrantPermissions` is a capability. It is fixed at `POST /session`, applies at install time, and grants everything the application declares with no exclusion list. That makes it excellent as a **session-profile default** — the whole happy-path suite starts with prompts out of the way — and useless as a per-case mechanism. Keep it out of the layer's request path entirely: 1. Choose it once per session profile, alongside the reset policy, and record that choice with the profile. 2. Let the layer treat the resulting state as the starting point it corrects, never as a state it can request. 3. Remember the coupling to installation: if the session's reset policy skips reinstalling, the grant never happens, so the layer's starting assumption changes silently. Verifying in setup is what catches that. 4. Never model grant everything as an intent, because only one platform can express it and only at one moment. ## Lifecycle placement The layer also owns when, not only what. Seeding must happen after install or reset and before the application is brought to the foreground, because changing a permission a running app already holds can restart its process — a restart that reads as a crash in the failure output. Making the seeding step a named phase of the case lifecycle, rather than a call anyone can make at any point, removes a whole class of intermittent failures that otherwise look like flake. ## What this layer must not absorb Two boundaries keep it small. It does not decide **which permission branches are worth covering**, nor what a case should conclude once a capability is missing — that is test design, and pulling it in turns a mechanism into a framework nobody can reason about. And it does not answer prompts: when a real Apple device forces the reset-plus-prompt route, that is modal handling, a different mechanism with different commands, and the layer's job is to say clearly that it has handed off rather than to pretend it seeded the state. ## The judgment an interviewer is listening for Anyone can list the three commands. The lead-level answer is the recognition that a capability gap between target classes is a **property of the fleet that belongs in the open**, not an exception caught inside a helper. Once the ceiling is declared data, planning can see it: a case that needs a denied camera is visibly Simulator-and-Android work, and nobody discovers that in a red run at the end of a release week.

  • Where does appium:autoGrantPermissions belong in this design?
    In the session profile, never in the per-case path. It is a capability fixed at session creation, applied at install time, granting everything the app declares with no exclusion list. Treat the state it produces as the starting point the layer corrects with `mobile: changePermissions`, and verify it, because a reset policy that skips reinstalling silently skips the grant too.
  • Why not map a caller-supplied permission string straight to each driver's argument?
    Because the two identifier spaces are unrelated: Android names a platform permission string on a package, the XCUITest driver names a service on a bundle. There is no shared key, and granularity differs between them. The layer needs its own enum plus two translation tables, or the abstraction breaks the first time the mapping is not one to one.

saying these in an interview costs you the question

  • Assumes one wrapper can hide the platform gap entirely
  • Passes a raw permission string through to both drivers
  • Catches the real-device failure and continues silently
  • Models grant everything as a per-case intent
  • Puts session capabilities behind a per-case helper call
  • Lets the seeding layer decide what a case should assert