How would you design one hardware-key helper for an Appium kayak-rental suite spanning Android and iOS?
answer
- unify the intersection, not the vocabularies
- a silent no-op is the worst outcome
- keep the raw execute call reachable
- Android has a back key, Apple does not
basics
~20 sUnify only the intents both platforms have — volume up, volume down, home — behind a small enum with one adapter per driver. Fail loudly where a platform lacks a key, and keep the raw execute call reachable.
solid answer
~50 sStart from the intersection, because it is tiny. Volume up, volume down and a home button exist on both platforms; Android's back key has no Apple chassis counterpart, and `metastate` and `isLongPress` have no Apple twin at all. So the honest shape is a small **intent** enum with one adapter per driver: the Android adapter issues `mobile: pressKey` with the right `KeyEvent` keycode, the Apple adapter issues `mobile: pressButton` with the matching name, and an intent a platform does not have raises an explicit unsupported error rather than returning quietly. Keep the raw `executeScript` path reachable so an unusual keycode or an `isLongPress` case does not force the abstraction wider. The failure to design against is the silent no-op: a helper that does nothing on one platform keeps the kayak-rental run green while nothing was pressed.
go deeper
You are not expected to design this, but know that the two platforms need two different calls, so any shared helper is hiding a branch rather than removing one.
Be able to say which keys exist on both platforms and which do not, because that intersection is the only honest surface a shared helper can expose.
Argue for failing loudly where a platform lacks a key, and show where the raw execute call stays reachable for the keycodes and flags the abstraction never covers.
Own the trade-off between one narrow shared vocabulary and a frankly per-platform surface, and be explicit about what the abstraction refuses to unify and why.
## What the question is really asking Every cross-platform mobile suite eventually reaches a call that has no shared form, and hardware keys are the cleanest example on the Appium surface: UiAutomator2 declares `mobile: pressKey` taking a numeric `KeyEvent` keycode, XCUITest declares `mobile: pressButton` taking a chassis button name, and neither driver answers the other's method. The interesting decision is not *how do I hide that* — it is **how much of it deserves to be hidden**, and what the wrapper owes a reader when it cannot hide the rest. ## The intersection is small, and that is the design input | Intent | Android, UiAutomator2 | Apple platforms, XCUITest | |---|---|---| | Volume up | `mobile: pressKey`, keycode `24` | `mobile: pressButton`, name `volumeup` | | Volume down | keycode `25` | name `volumedown` | | Home | keycode `3` | name `home` | | Back | keycode `4` | no chassis counterpart | | Enter, Tab | keycode `66`, `61` | `mobile: keys` sequence, different method | | Modifier held | `metastate` bitmask | not expressible | | Long press | `isLongPress` flag | not expressible | Three rows map cleanly. The rest either exist on one side only or live on a different method entirely. A shared surface built from the three honest rows is useful; one stretched over all seven is a translation table pretending two alphabets are one. ## A shape that survives contact with a real suite 1. **Define intents, not calls.** The kayak-rental suite asks for `HardwareKey.VOLUME_UP`, not for a keycode or a button name. The enum is deliberately short, and adding to it is a decision rather than a reflex. 2. **One adapter per driver.** An Android adapter maps the intent onto `mobile: pressKey` with the right integer; an Apple adapter maps it onto `mobile: pressButton` with the right name. The adapter is the only place a driver method name appears. 3. **Fail loudly on the gap.** `HardwareKey.BACK` on an Apple session raises an explicit unsupported-key error naming the platform. The run turns red at the call site, which is where the information is useful. 4. **Keep the raw path reachable.** A test that genuinely needs `isLongPress`, an unusual keycode, or an Apple key sequence calls `executeScript` with the driver's own method directly. The abstraction never becomes the only door. 5. **Keep the branch readable.** Whichever adapter is selected, the selection happens once at session setup from `platformName` and `appium:automationName`, not as an `if` scattered through the steps. ## The failure mode to design against The worst outcome is not an error — it is a **silent no-op**. A helper called `pressBack()` that issues the Android call and quietly returns on Apple platforms produces a kayak-rental run that stays green while the step it names never happened. Nobody reads the code again until something else breaks, and by then the suite has a step everyone believes runs on both platforms. A few properties keep that from happening: - Every adapter method either performs the action or throws; none of them returns without doing something. - The unsupported-key error names the intent and the platform, so the message alone explains the gap. - No adapter substitutes a different mechanism for a missing key — reaching for an on-screen control because a hardware key is absent quietly changes what the step exercises. ## What the abstraction should refuse to unify - **The parameter spaces.** `metastate` and `isLongPress` are Android-only. A shared signature that accepts them and ignores them on the other platform is the no-op problem wearing an argument list. - **The numeric space itself.** Android's integers address everything the system can report as a key; the Apple names describe the buttons on the case. There is no general mapping, only the three-row intersection. - **Single presses and key sequences.** `mobile: keys` sends a sequence to whatever the app has focused, which is a different target from a chassis button. Folding it into a `pressKey`-shaped helper hides the distinction that decides whether the call does anything. ## Where the helper's job ends The adapter's responsibility stops at issuing the right driver command for the platform it is on, and at making a missing key visible. It does not decide which flows deserve a hardware-key step, and it should not grow into a place where that gets decided — the moment a helper starts choosing between a hardware key and an on-screen equivalent, the suite has a step whose meaning changes with the device it lands on. The defensible answer to the interview question is therefore narrow on purpose: unify the intersection, name the divergence, fail loudly at the gap, and leave the escape hatch open.
- Which hardware keys genuinely exist on both platforms, and which do not?Volume up, volume down and a home button exist on both — `24`, `25` and `3` on Android against `volumeup`, `volumedown` and `home` on Apple platforms. Android's back key has no Apple chassis counterpart, and Apple's `mobile: keys` sequences have no single Android twin, since `mobile: pressKey` addresses one key per call.
- What should the helper do on a platform where the requested key does not exist?Fail loudly. An explicit unsupported-key error naming the intent and the platform keeps the divergence visible at the call site. A helper that quietly returns keeps the run green while nothing was pressed, and the platform gap then stays hidden until somebody reads the adapter.
saying these in an interview costs you the question
- Wrapping both methods in one call that no-ops on Apple platforms
- Building a keycode-to-button-name table as if the vocabularies matched
- Assuming every Android key has an Apple chassis counterpart
- Hiding the platform branch so a missing key still looks like a pass
- Accepting metastate in a shared signature and ignoring it on Apple
- Substituting an on-screen control when the hardware key is absent