skip to content

How would you build one Appium scroll-into-view step for a pet-grooming app on Android and iOS?

level: seniorimportance: must knowfreq 46%

answer

  1. share the shape, not the command
  2. branch on the platform inside
  3. selector in on Android, element in on iOS
  4. bound both search budgets

basics

~10 s

Share 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 s

There 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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