Native Alerts and Permissions Dialogs

7 min read

System alerts and permission dialogs are some of the most common sources of test failures for mobile automation beginners. They appear outside your app's process, often at unpredictable moments, and block every subsequent interaction until they are dismissed. This lesson shows you how to handle them correctly — both reactively (when they appear mid-test) and proactively (preventing them from appearing at all).

Why these dialogs are different

A normal app element lives in your app's UI process. When you call findElement, Appium asks the automation engine to search the app's accessibility tree.

System dialogs — permission prompts, location requests, push notification requests, camera access requests — render in a separate system UI process. They are not part of your app's tree. On Android, UIAutomator2 has system-level access and can interact with them. On iOS, WebDriverAgent runs at a lower level and can interact with XCUITest's system alert API.

Android: Handling permission dialogs

Proactive approach — pre-grant with ADB

The cleanest solution is to grant permissions before the test runs so the dialog never appears:

// In @BeforeClass or a test setup helper
Runtime.getRuntime().exec(new String[]{
    "adb", "shell", "pm", "grant",
    "com.example.myapp",
    "android.permission.ACCESS_FINE_LOCATION"
});

Or from a Maven Surefire pre-test shell step, or a @BeforeSuite method that runs the ADB command.

Common permissions:

adb shell pm grant com.example.myapp android.permission.ACCESS_FINE_LOCATION
adb shell pm grant com.example.myapp android.permission.CAMERA
adb shell pm grant com.example.myapp android.permission.READ_CONTACTS
adb shell pm grant com.example.myapp android.permission.RECORD_AUDIO
adb shell pm grant com.example.myapp android.permission.POST_NOTIFICATIONS

This approach is reliable and requires no additional test code.

Reactive approach — tap the dialog

When pre-granting is not possible, interact with the dialog using its text:

private void allowPermissionIfPresent() {
    try {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
        WebElement allowButton = wait.until(
            ExpectedConditions.presenceOfElementLocated(
                AppiumBy.androidUIAutomator(
                    "new UiSelector().textMatches(\"(?i)(allow|ok|permit)\")"
                )
            )
        );
        allowButton.click();
    } catch (TimeoutException ignored) {
        // Dialog didn't appear — proceed
    }
}

The (?i) makes the regex case-insensitive. Different Android versions and OEM skins use different button labels ("Allow", "ALLOW", "OK", "Permit"). The regex covers all common variants.

Using UIAutomator2 to click system dialogs

UIAutomator2 can see outside the app's context:

// Android 12+ — "While using the app" button
driver.findElement(
    AppiumBy.androidUIAutomator("new UiSelector().text(\"While using the app\")")
).click();
 
// Older Android — "Allow" button
driver.findElement(
    AppiumBy.androidUIAutomator("new UiSelector().text(\"Allow\")")
).click();

iOS: Handling system alerts

Proactive approach — simctl on Simulator

Pre-grant permissions on iOS Simulator before the test:

// Grant location permission
Runtime.getRuntime().exec(new String[]{
    "xcrun", "simctl", "privacy", "booted",
    "grant", "location", "com.example.myapp"
});
 
// Grant camera
Runtime.getRuntime().exec(new String[]{
    "xcrun", "simctl", "privacy", "booted",
    "grant", "camera", "com.example.myapp"
});
 
// Reset all permissions (for a clean state before the test run)
Runtime.getRuntime().exec(new String[]{
    "xcrun", "simctl", "privacy", "booted",
    "reset", "all", "com.example.myapp"
});

Available services: location, locationAlwaysAuthorization, contacts, photos, camera, microphone, motion, health, homekit, medialibrary.

Reactive approach — Appium alert API

For simple OK/Cancel alerts:

// Accept (tap the positive button)
driver.switchTo().alert().accept();
 
// Dismiss (tap the negative button)
driver.switchTo().alert().dismiss();
 
// Get the alert text
String alertText = driver.switchTo().alert().getText();

For iOS alerts with multiple buttons, use the mobile: alert command:

// Accept (first button — usually "Allow" or "OK")
driver.executeScript("mobile: alert", Map.of("action", "accept"));
 
// Dismiss
driver.executeScript("mobile: alert", Map.of("action", "dismiss"));
 
// Click a specific button by label
driver.executeScript("mobile: alert", Map.of(
    "action", "accept",
    "buttonLabel", "Allow While Using App"
));

Using mobile: setPermission (iOS 15+)

Appium 2.x with XCUITest driver 4.x+ supports mobile: setPermission to grant or deny permissions programmatically:

driver.executeScript("mobile: setPermission", Map.of(
    "bundleId", "com.example.myapp",
    "permission", "camera",
    "value", "yes"   // "yes", "no", or "limited"
));

This is the cleanest iOS approach for tests that must test the post-permission state without relying on simctl.

Handling in-app alerts

In-app alerts (not system alerts) are just normal elements. Find and tap them like any other element:

// Dismiss an in-app error dialog
driver.findElement(AppiumBy.accessibilityId("error_dialog_close_button")).click();
 
// Or use Appium's alert API if the app uses a native UIAlertController
driver.switchTo().alert().dismiss();

Alert helper pattern

A reusable helper in BaseTest:

protected void dismissAlertIfPresent() {
    try {
        new WebDriverWait(driver, Duration.ofSeconds(3))
            .until(ExpectedConditions.alertIsPresent());
        driver.switchTo().alert().accept();
    } catch (TimeoutException ignored) {
        // No alert present
    }
}

Call dismissAlertIfPresent() at the start of any test that might encounter a permission dialog, or wrap it in a @BeforeMethod to guard every test automatically.

// tip to track lessons you complete and pick up where you left off across devices.