How would you build one Appium scroll-into-view step for a pet-grooming app on Android and iOS?
answer
- share the shape, not the command
- branch on the platform inside
- selector in on Android, element in on iOS
- bound both search budgets
basics
~10 sShare the shape, not the command. One helper takes a logical target and returns something reachable, branching inside: Android builds a UiScrollable scrollIntoView selector, iOS finds the row and then posts mobile: scrollToElement.
solid answer
~40 sThere is no shared command to reach for, so share the **contract** instead: one bring-into-reach helper that returns an element you may act on, or fails with a message naming the platform and what it tried. Inside, the branches are genuinely different. The Android branch composes an `-android uiautomator` expression - a `UiScrollable` for the list plus a `UiSelector` for the row - or calls the UiAutomator2 driver's `mobile: scroll` with the container and target as arguments; the search happens on the device. The iOS branch finds the row first, then posts XCUITest's `mobile: scrollToElement` for that handle and confirms it is `hittable`. Bound the Android search budget so a missing row fails fast, and keep both branches behind the one call sites use.
go deeper
Know that the two platforms need different commands here, and that a suite covering both usually hides that behind one helper rather than asking each test to choose.
Explain what each branch does differently: Android composes a selector that searches while it scrolls, iOS finds the row first and then scrolls to the handle. Say why neither command works in the other session.
Design the contract out loud: one entry point, a bounded search on Android, a reachability check on iOS, and failure messages that name the branch that ran and what it tried.
Own where the divergence lives. Argue for a single primitive maintained centrally, define what it guarantees callers, and be explicit about the maintenance bill when a third driver or a new list widget arrives.
## What can be shared and what cannot A pet-grooming appointment suite that runs on both platforms wants one line at the call site: bring the row for Bella's full groom into reach, then tap it. What cannot be shared is the command. The UiAutomator2 driver and the XCUITest driver have no scroll-into-view command in common, and sending one driver's to the other is not a slow path but an outright error: - `-android uiautomator` in an XCUITest session is an **unknown locator strategy**. - `mobile: scrollToElement` in a UiAutomator2 session is an **unknown execute method**. So the branch is mandatory. What you share is the *shape*: a single function with one contract, one failure vocabulary, and one place where platform knowledge lives. ## The contract worth writing down Define the helper by what callers may assume, not by what it does internally: 1. **In**: a logical target - the appointment row whose title is X - plus, optionally, which list it lives in. 2. **Out**: an element the caller may act on immediately, already brought into reach. 3. **On failure**: an error that says which platform branch ran, what it looked for, and how much searching it did. 4. **Bounded**: the step has a ceiling on how long it will hunt, chosen by you rather than inherited. With that contract, page objects call one thing and never learn a driver's vocabulary. ## The Android branch The Android branch can search while it scrolls, so it needs no prior find: - Compose an `-android uiautomator` expression: a `UiScrollable` matching the schedule list, with `scrollIntoView` carrying a `UiSelector` for the row. - Narrow the outer selector to the intended list - by resource id, for instance - so a screen with several scrollable areas cannot scroll the wrong one. - Or call the UiAutomator2 driver's `mobile: scroll`, passing the scrollable container and a strategy plus selector for the target, which keeps the two halves as arguments rather than one string. - Set the search budget explicitly, so a missing row costs a known number of swipes rather than an inherited default. The practical constraint is vocabulary: only what `UiSelector` can express - text, content description, resource id, class, index - can be a scroll target, so the helper needs the logical target in a form it can translate. ## The iOS branch The iOS branch runs in the opposite order: - Find the row first, typically by `accessibility id` or `-ios predicate string`. - Post `mobile: scrollToElement` with that element handle, which scrolls the enclosing container until the element is `hittable`. - Confirm reachability before returning, so the caller's tap is not the thing that discovers a covered row. - If the find itself fails because the row has not been laid out, fall back to a **bounded** loop of an XCUITest scroll or swipe followed by a re-find, never an unbounded one. ## The two branches side by side | Responsibility | Android branch | iOS branch | |---|---|---| | Find before scrolling | not needed | required | | Where the searching happens | the on-device UiAutomator2 server | your fallback loop, if the find failed | | Command | `-android uiautomator` selector, or `mobile: scroll` | `mobile: scrollToElement` | | Where the budget lives | inside the selector expression | in your fallback loop | | Post-condition to check | the element is present and clickable | the element reports `hittable` | ## Where suites get this wrong Four recurring mistakes, all of which show up as flakiness rather than as clean failures: - Letting the Android `UiSelector` string leak into page objects, so screens become platform-specific and the branch spreads. - Leaving the Android search unbounded, so a missing row turns into a multi-minute step that eventually looks like a hang. - Returning from the iOS branch without checking reachability, which converts an overlay problem into an unexplained tap failure. - Assuming the two branches fail the same way, so one error message has to describe both and ends up describing neither. ## Proving the helper works Exercise it against the cases that actually differ, not just the happy path: a row near the top that needs no scrolling, a row far down a long schedule, a row that does not exist at all, and a row that ends up under a pinned action bar. The first two should pass identically on both platforms; the last two should fail in a bounded time with a message that names the branch. When those four behave, the call site can stay one line and the platform divergence stays where it belongs - inside one function that says which platform it is speaking for.
- Why not simply expose two helpers and let each test pick the right one?Because the choice is not a test's business, and letting it leak means every screen learns a driver's vocabulary. One entry point with one contract keeps platform knowledge in a single function, so adding a device or changing a command touches one place instead of every page object.
- What is the honest fallback when an iOS find fails because the row has not been laid out yet?A bounded loop: scroll the container a step with an XCUITest scroll or swipe, re-run the find, and stop after a fixed number of attempts. It mimics what the Android side gets for free, and the bound is what keeps a missing row from becoming an open-ended hunt.
saying these in an interview costs you the question
- Sends one driver's scroll command to both platforms
- Writes the Android UiSelector string into shared page objects
- Leaves the Android search budget unbounded
- Returns from the iOS branch without checking reachability
- Believes a shared helper implies a shared command