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 */ }
}
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 | |
|---|---|---|
| Portability | Same code for Android and iOS | Driver-specific (UiAutomator2 or XCUITest) |
| Code size | Verbose: you describe each pointer move | One call with a map of arguments |
| Reliability | Depends on coordinates and timing | Uses the platform's own gesture implementation |
| Best for | Custom gestures, cross-platform helpers | Common 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.