Mobile tests are mostly gestures: tapping, swiping through lists, long-pressing, scrolling until an element appears. In Appium 2 the old TouchAction API is gone, and gestures are written with W3C actions (PointerInput and Sequence) or driver-specific mobile: commands. This guide shows both, plus the waits and pop-up handling that make mobile tests stable. New to Appium? Start with the Appium tutorial.

Mobile Gestures

import io.appium.java_client.android.AndroidDriver;
import org.openqa.selenium.interactions.PointerInput;
import org.openqa.selenium.interactions.Sequence;
import java.time.Duration;
// ── Tap (simple click on coordinates) ──────────────────
PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
Sequence tap = new Sequence(finger, 1)
    .addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 500, 800))
    .addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
    .addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
driver.perform(Collections.singletonList(tap));
// ── Swipe up (scroll down) ─────────────────────────────
Sequence swipeUp = new Sequence(finger, 1)
    .addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 540, 1400))
    .addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
    .addAction(finger.createPointerMove(Duration.ofMillis(600), PointerInput.Origin.viewport(), 540, 400))
    .addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
driver.perform(Collections.singletonList(swipeUp));
// ── Long press ─────────────────────────────────────────
Sequence longPress = new Sequence(finger, 1)
    .addAction(finger.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), 500, 800))
    .addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
    .addAction(new Pause(finger, Duration.ofMillis(1500)))
    .addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
driver.perform(Collections.singletonList(longPress));

Reusable gesture utility class

public class GestureUtils {
    public static void swipeUp(AndroidDriver driver) {
        Dimension size = driver.manage().window().getSize();
        int startX = size.width / 2;
        int startY = (int)(size.height * 0.80);
        int endY   = (int)(size.height * 0.20);
        PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger");
        Sequence swipe = new Sequence(finger, 1)
            .addAction(finger.createPointerMove(Duration.ZERO,
                PointerInput.Origin.viewport(), startX, startY))
            .addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg()))
            .addAction(finger.createPointerMove(Duration.ofMillis(500),
                PointerInput.Origin.viewport(), startX, endY))
            .addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg()));
        driver.perform(Collections.singletonList(swipe));
    }
    public static void swipeDown(AndroidDriver driver) { /* reverse */ }
    public static void swipeLeft(AndroidDriver driver)  { /* horizontal */ }
    public static void swipeRight(AndroidDriver driver) { /* horizontal */ }
}
Advertisement

Waits & Synchronization

import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.support.ui.ExpectedConditions;
// Implicit wait (global for ALL findElement calls)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
// Explicit wait — wait for specific element condition
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
// Wait for element to be visible
wait.until(ExpectedConditions.visibilityOfElementLocated(
    By.id("com.example:id/progress_bar")));
// Wait for element to disappear (loading spinner)
wait.until(ExpectedConditions.invisibilityOfElementLocated(
    By.id("com.example:id/loading")));
// Wait for text
wait.until(ExpectedConditions.textToBePresentInElementLocated(
    By.id("com.example:id/status"), "Success"));
// Custom wait — wait for app activity
wait.until(d -> ((AndroidDriver)d).currentActivity()
                 .contains("HomeActivity"));
// Fluent wait with polling
Wait<WebDriver> fluent = new FluentWait<>((WebDriver)driver)
    .withTimeout(Duration.ofSeconds(30))
    .pollingEvery(Duration.ofSeconds(1))
    .ignoring(NoSuchElementException.class);
fluent.until(d -> d.findElement(By.id("result")).isDisplayed());

Note: Never use Thread.sleep() in Appium tests. Mobile apps are slower than web apps — explicit waits with proper conditions are essential for stable tests.

Handling Alerts, Permissions & Popups

// System permission dialog (Android 6+)
// Method 1: autoGrantPermissions=true in capabilities (automatic)
// Method 2: Handle manually
try {
    WebElement allowBtn = driver.findElement(
        By.id("com.android.permissioncontroller:id/permission_allow_button"));
    allowBtn.click();
} catch (NoSuchElementException e) {
    // Permission dialog did not appear, continue
}
// Method 3: Grant via ADB
// adb shell pm grant com.example.app android.permission.CAMERA
// Toast messages (read-only, disappears quickly)
WebElement toast = driver.findElement(
    By.xpath("//*[@class='android.widget.Toast']"));
String toastText = toast.getText();
// Alert dialogs
driver.findElement(By.id("com.example:id/ok_button")).click();
// Deep links (open specific screen directly)
driver.get("myapp://product/12345");

Scrolling to an Element

// Method 1: UIAutomator scroll to text (Android)
driver.findElement(AppiumBy.androidUIAutomator(
    "new UiScrollable(new UiSelector().scrollable(true))" +
    ".scrollIntoView(new UiSelector().text(\"Settings\"))"));
// Method 2: Scroll until element visible
public WebElement scrollToElement(By locator) {
    int maxScrolls = 10;
    while (maxScrolls-- > 0) {
        try {
            WebElement el = driver.findElement(locator);
            if (el.isDisplayed()) return el;
        } catch (NoSuchElementException e) {
            GestureUtils.swipeUp(driver);
        }
    }
    throw new NoSuchElementException("Element not found after scrolling");
}
// Method 3: JavascriptExecutor scroll (works for WebViews)
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].scrollIntoView(true)", element);

W3C Actions or mobile: Commands?

W3C actions (PointerInput)mobile: gesture commands
PortabilitySame code for Android and iOSDriver-specific (UiAutomator2 or XCUITest)
Code sizeVerbose: you describe each pointer moveOne call with a map of arguments
ReliabilityDepends on coordinates and timingUses the platform's own gesture implementation
Best forCustom gestures, cross-platform helpersCommon gestures: swipe, scroll, long-click, drag, pinch

Whichever you use, compute coordinates from the screen size (as in the utility class above) rather than hard-coding pixels, so gestures work on every device.

FAQs

What replaced TouchAction in Appium 2?

W3C actions through Selenium's PointerInput and Sequence classes, or the drivers' mobile: gesture commands such as mobile: swipeGesture in UiAutomator2.

How do you scroll to an element in Appium?

On Android, use a UiScrollable selector with scrollIntoView; on iOS, use mobile: scroll with a direction or predicate; or swipe in a loop until the element is found, with a maximum number of attempts.

How do you handle Android permission pop-ups?

Grant them automatically with the autoGrantPermissions capability, or tap the Allow button by its resource ID when you need to test the permission flow itself.