Swipe or Scroll Helper with Appium for iOS-Android Applications

ScrollSwipe

     We know that swipe or scroll actions are common interaction in mobile applications. One common requirement for testing is to scroll a list until an element is found or some other condition is reached. Scrolling can be achieved in a variety of ways, but the most low-level way of doing so is using the Actions API. In this article, I would like to share some convenience methods for accomplishing swipe or scroll actions. These methods will satisfy,

  • To scroll a list view.
  • To swipe in an arbitrary fashion, for an arbitrary amount of time.
  • To define a swipe in screen-relative terms.
  • Actions should do the same thing on both iOS and Android.

Below, we will start to build out our convenience methods,

Low-Level Swipe

     Everything we want to do (scrolling, swiping) can be conceived of at the root as an action with basically four steps:

  • Move the finger to a location above the screen.
  • Touch the finger to the screen.
  • While touching the screen, move the finger to another location, taking a certain amount of time to do so.
  • Lift the finger off the screen

     Encoding these steps will be our first task, and will result in the method we build on top of for everything else. Below is the implementation,

protected void swipe(Point start, Point end, Duration duration)

     We will call this method swipe, and allow the user to define the start point, end point, and duration.

AppiumDriver<MobileElement> driver = getDriver();
boolean isAndroid = driver instanceof AndroidDriver<?>;

     To perform the swipe, we need the driver instance, which we get, and also give a handy way of referring to whether the driver is iOS or Android. getDriver method you can implement in your Automation base class where you instantiating your driver.

     Next, we begin to construct the action sequence. First of all, we define the pointer input and the sequence object which will represent the swipe. The next two actions are common to both iOS and Android. We initially move the pointer to the start point (taking no explicit time duration to do so) and then lower the pointer to touch the screen,

PointerInput input = new PointerInput(Kind.TOUCH, “finger1”);
Sequence swipe = new Sequence(input, 0);
swipe.addAction(input.createPointerMove(Duration.ZERO, Origin.viewport(), start.x, start.y));
swipe.addAction(input.createPointerDown(MouseButton.LEFT.asArg()));

Below code helps to changes a bit depending on platform,

if (isAndroid) {
duration = duration.dividedBy(ANDROID_SCROLL_DIVISOR);
} else {
swipe.addAction(new Pause(input, duration));
duration = Duration.ZERO;
}
swipe.addAction(input.createPointerMove(duration, Origin.viewport(), end.x, end.y));

     For Android, the actual time taken by the action is always much greater than the amount of time actually specify. This may not be true across the board, but here defined a static variable ANDROID_SCROLL_DIVISOR  to represent this. On iOS, the duration of the pointer move is not actually encoded on the move action at all, it is encoded on a pause action which is inserted before the move action, which is why we are adding that action here if we are using iOS (before then setting the duration to zero for the actual move).

    Finally, lift the finger from the screen, and tell the driver to actually perform the sequence we have encoded. Below is the code snippet to do that action,

swipe.addAction(input.createPointerUp(MouseButton.LEFT.asArg()));
driver.perform(ImmutableList.of(swipe));

Swipe Relatively

The above swipe method is great, but it requires absolute screen coordinates. What if we are running the same test on different devices with different screen dimensions? It would be much better if we could specify our swipe in terms relative to the height and width of the screen. We will see now,

private Dimension getWindowSize() {
if (windowSize == null) {
windowSize = getDriver().manage().window().getSize();
}
return windowSize;
}

protected void swipe(double startXPct, double startYPct, double endXPct, double endYPct, Duration duration) {
Dimension size = getWindowSize();
Point start = new Point((int)(size.width * startXPct), (int)(size.height * startYPct));
Point end = new Point((int)(size.width * endXPct), (int)(size.height * endYPct));
swipe(start, end, duration);
}

     Here we have defined a new version of swipe, that takes start and end values in percentages, not absolute terms. To make this work, we need another helper function that retrieves (and caches) the window size. Using the window size (height and width), we are able to calculate absolute coordinates for the swipe.

Scrolling

     Now we are in a good position to build up the scroll-related methods. The scroll is technically a swipe where we don’t care about one of the dimensions. If scrolling a list down, it means performing a slow upwards swipe action in reality, where the only change in motion that matters is a change in the y-axis. We have 4 directions we can scroll, so we can create an enum to help us define that,

public enum ScrollDirection {
   UP, DOWN, LEFT, RIGHT
}

     We can construct a scroll method that takes one of these directions. It is also helpful to make the scroll amount configurable. We can allow the user to define a short scroll or long scroll in terms relative to the screen height or width. So If we want a scroll amount of 1, that means we should scroll the equivalent of a full screen. 0.5 would mean the equivalent of a half screen. We will see the implementation below,

protected void scroll(ScrollDirection dir, double distance) {
if (distance < 0 || distance > 1) {
throw new Error(“Scroll distance must be between 0 and 1”);
}
Dimension size = getWindowSize();
Point midPoint = new Point((int)(size.width * 0.5), (int)(size.height * 0.5));
int top = midPoint.y – (int)((size.height * distance) * 0.5);
int bottom = midPoint.y + (int)((size.height * distance) * 0.5);
int left = midPoint.x – (int)((size.width * distance) * 0.5);
int right = midPoint.x + (int)((size.width * distance) * 0.5);
if (dir == ScrollDirection.UP) {
swipe(new Point(midPoint.x, top), new Point(midPoint.x, bottom), SCROLL_DUR);
} else if (dir == ScrollDirection.DOWN) {
swipe(new Point(midPoint.x, bottom), new Point(midPoint.x, top), SCROLL_DUR);
} else if (dir == ScrollDirection.LEFT) {
swipe(new Point(left, midPoint.y), new Point(right, midPoint.y), SCROLL_DUR);
} else {
swipe(new Point(right, midPoint.y), new Point(left, midPoint.y), SCROLL_DUR);
}
}

     In the above logic, the start point of a scroll-swipe is going to be half the distance from the mid-point of the screen, along the appropriate axis. With all of these points defined, we can then simply start and end at the appropriate point corresponding to the direction that we want to scroll.

     You can use the above convenient methods with an iOS or Android test automation. Try to use the above helpers in your real automation world and enjoy swipe and scroll actions

Reference: Appium Pro

make it perfect!