skip to content

Scrolling into View

Bringing an off-screen row into reach. One platform searches while it scrolls, the other scrolls at an element it already holds - and a tree that lists the element is no promise you can tap it.

on this pageshow

explore

questions

5

In Appium, which command scrolls an off-screen row into view on Android, and which on iOS?

level: juniorimportance: must knowfreq 72%

answer

  1. opposite inputs on the two platforms
  2. one searches while it scrolls
  3. the other needs the element first
  4. scrollIntoView versus mobile: scrollToElement

basics

~10 s

On Android the UiAutomator2 driver scrolls while it searches: an -android uiautomator selector running UiScrollable scrollIntoView, or mobile: scroll. On iOS, XCUITest scrolls to an element you have already found, with mobile: scrollToElement.

solid answer

~40 s

The two drivers take **opposite inputs**. On Android, the UiAutomator2 driver's `-android uiautomator` strategy takes a `UiSelector` expression such as `new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("Bella - Full Groom"))`; the on-device server swipes the container and rescans after each swipe, so the row need not be in the hierarchy when the call starts. The same driver's `mobile: scroll` execute method does the same job from a scrollable container plus a strategy and selector for the target. On iOS, XCUITest's `mobile: scrollToElement` is handed an element you have already located and scrolls its container until that element is `hittable`. So on iOS you find first and scroll second; on Android the find *is* the scroll.

code

python · 5 lines
python
row = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiScrollable(new UiSelector().scrollable(true))'
    '.scrollIntoView(new UiSelector().text("Bella - Full Groom"))')
row.click()

go deeper

for a junior

Be ready to name the command for each platform without hesitating: UiScrollable scrollIntoView through the -android uiautomator strategy on Android, mobile: scrollToElement on iOS. Getting the driver attribution right matters as much as the command name.

for a middle

Explain why the inputs differ. One takes a selector and does the searching on the device; the other takes an element handle and only moves the container. Say what each one needs to exist beforehand.

for a senior

Show how you keep a suite honest across both: a branch on platform, a bounded search on Android, and a reachability check after the iOS scroll rather than an immediate tap.

for a principal

Own the decision of where this branch lives. Argue whether teams write one bring-into-reach primitive owned centrally or let each screen improvise, and what that choice costs when a third driver joins the fleet.

## What a scroll-into-view command has to solve A mobile list shows a handful of rows and holds hundreds. In a pet-grooming appointment app, the row for the four o'clock **full groom for Bella** may sit forty appointments down the day's schedule. Two separate things have to happen before a test can tap it: the row has to become something the driver can address at all, and it has to end up somewhere on screen where a touch actually lands on it. Appium has no single command for that. Each driver ships its own, and the two are built around **opposite inputs**. ## Android: the search runs inside the scroll The UiAutomator2 driver's `-android uiautomator` strategy does not send a query for the driver to evaluate. It sends a **`UiSelector` expression as a string**, which the UiAutomator2 server parses and executes on the device: ```python driver.find_element( AppiumBy.ANDROID_UIAUTOMATOR, 'new UiScrollable(new UiSelector().scrollable(true))' '.scrollIntoView(new UiSelector().text("Bella - Full Groom"))') ``` `UiScrollable` wraps the scrollable container and `scrollIntoView` swipes it, rescanning after every swipe, until something matches or its search budget is spent. Three consequences follow: - The target **need not be in the hierarchy** when the command starts, so a recycled row that has never been laid out is fine. - The whole scan is **one request**, however many swipes it takes. - What comes back is an ordinary element handle you then click, so the find and the scroll are the same call. The same driver also declares the execute methods `mobile: scroll` and `mobile: scrollBackTo`. `mobile: scroll` does the same job from the other end: you name the scrollable container and give it a strategy and a selector for the target, and it swipes that container until the target turns up. Neither of them is `mobile: scrollGesture`, which is a blind directional drag over a region and searches for nothing. ## iOS: the find runs before the scroll XCUITest inverts the order. `mobile: scrollToElement` takes **an element you already hold**, and the only way to hold one is a find that has already succeeded. It then scrolls that element's container until the element is `hittable`. On iOS the sequence is: 1. Locate the row, typically with `-ios predicate string` or `accessibility id`. 2. Post `mobile: scrollToElement` for that element. 3. Tap it, now that it is in reach. That first step succeeds more often than Android intuition suggests, because WebDriverAgent's snapshot routinely lists elements that are laid out but off screen. The find works; the row is simply nowhere near the viewport. XCUITest also ships `mobile: scroll`, which can be given a direction rather than a destination, but `mobile: scrollToElement` is the "bring this exact element into reach" command. ## The divergence in one table | | Android, UiAutomator2 driver | iOS, XCUITest driver | |---|---|---| | Command | `-android uiautomator` with `UiScrollable.scrollIntoView`, or `mobile: scroll` | `mobile: scrollToElement` | | What you hand it | a selector for the target | an element handle for the target | | Target already in the tree? | not required | required | | Where the loop runs | the on-device UiAutomator2 server | XCUITest, on the element's container | | Stop condition | a node matches the selector | the element reports `hittable` | | A row that does not exist | fails after the swipe budget is spent | fails at the find, before any scrolling | ## What it means for a shared suite The commands are not interchangeable, and neither driver quietly accepts the other's: - Sending `-android uiautomator` to an XCUITest session is an **unknown locator strategy**, not a slow path. - Sending `mobile: scrollToElement` to a UiAutomator2 session is an **unknown execute method**. - A cross-platform helper therefore branches on `platformName` and shares only its *shape*: take a logical target, return something you can act on. ## The part that is easy to miss On Android, a successful `scrollIntoView` means a matching node exists and its container was scrolled to it. On iOS, a successful find means only that the element is in the snapshot. The row can be listed, addressed, and still refuse a tap, because XCUITest judges interaction by `hittable` rather than by presence. That is why the iOS branch scrolls after finding rather than before, and it is the single biggest reason a scroll step that behaves on one platform misbehaves on the other.

  • On Android, what does UiAutomator2's `mobile: scroll` give you that the `-android uiautomator` selector does not?
    It is an execute method rather than a locator, so the scrollable container and the target are separate arguments instead of one expression. That makes it easier to point at a specific list when a screen holds several scrollable areas, and it keeps the target in a supported strategy rather than forcing everything through `UiSelector` syntax.
  • Can XCUITest's `mobile: scrollToElement` reach a row that is not in the snapshot yet?
    No. It takes an element handle, and a handle only comes from a find that has already succeeded, so the row must be in WebDriverAgent's snapshot before you can ask for the scroll. If it has not been laid out, the find fails first and the scroll never runs.

Android's scrollIntoView is like sending someone into the stacks with a title to look for. iOS's mobile: scrollToElement is like handing them the shelf mark of a book the catalogue has already located.

saying these in an interview costs you the question

  • Says one scroll-into-view command covers Android and iOS
  • Calls scrollIntoView an XCUITest feature
  • Thinks a blind swipe gesture is the same as scrolling into view
  • Expects the -android uiautomator strategy to work in an iOS session
  • Assumes iOS can scroll to a row it has not found yet
open as a page

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

level: seniorimportance: must knowfreq 46%

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.

open as a page

In Appium on iOS, which attribute must a row report before XCUITest will tap it, and how do you get there?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Hittable. XCUITest acts on an iOS element only when a hit test at its activation point resolves to that element. mobile: scrollToElement scrolls its container until it is hittable; an overlay such as a sticky bar must be cleared instead.

open as a page

Why does Appium's UiScrollable scrollIntoView find an Android row that findElement cannot?

level: middleimportance: should knowfreq 52%

basics

~20 s

UiScrollable runs on the device: the UiAutomator2 server swipes the Android list and rescans after each swipe. A plain findElement is one query against the hierarchy as it exists now, and a recycled off-screen row is not in it.

open as a page

In Appium, where does a scroll-into-view step fail for a missing row on Android versus iOS?

level: seniorimportance: nice to knowfreq 34%

basics

~20 s

Android fails late: the UiAutomator2 server spends its whole search-swipe budget before the find reports nothing. iOS fails early, because mobile: scrollToElement needs an element handle, so the find fails first and the scroll never runs at all.

open as a page