Both iOS and Android show system-level dialogs outside the app's UI hierarchy. Permission requests, push notification prompts, and location access dialogs block test execution if unhandled. This lesson covers how to accept, deny, or auto-dismiss them in Python.
Android permission dialogs
Option 1: Auto-grant at capability level
from appium.options import UiAutomator2Options
options = UiAutomator2Options()
options.auto_grant_permissions = TrueThis grants all permissions before any tests run. Use this when permission flows aren't under test.
Option 2: Handle via UIAutomator
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
def grant_permission(driver, timeout=4):
"""Taps 'Allow' on an Android permission dialog if present."""
try:
allow_btn = WebDriverWait(driver, timeout).until(
lambda d: d.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().textContains("Allow")'
)
)
allow_btn.click()
except TimeoutException:
pass # No dialog shown
def deny_permission(driver, timeout=4):
try:
deny_btn = WebDriverWait(driver, timeout).until(
lambda d: d.find_element(
AppiumBy.ANDROID_UIAUTOMATOR,
'new UiSelector().textContains("Don\'t allow")'
)
)
deny_btn.click()
except TimeoutException:
passOption 3: Grant via ADB shell
def grant_permission_adb(driver, package: str, permission: str):
"""Grants a permission without triggering the dialog UI."""
driver.execute_script("mobile: shell", {
"command": f"pm grant {package} {permission}"
})
# Usage
grant_permission_adb(
driver,
"com.example.myapp",
"android.permission.ACCESS_FINE_LOCATION"
)iOS permission alerts
iOS permissions appear as native alerts, accessible via driver.switch_to.alert:
from selenium.common.exceptions import NoAlertPresentException, TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
def accept_alert(driver, timeout=4):
"""Accept an iOS system alert if present."""
try:
WebDriverWait(driver, timeout).until(
lambda d: _try_accept(d)
)
except TimeoutException:
pass
def dismiss_alert(driver, timeout=4):
"""Dismiss an iOS system alert if present."""
try:
WebDriverWait(driver, timeout).until(
lambda d: _try_dismiss(d)
)
except TimeoutException:
pass
def _try_accept(driver) -> bool:
try:
driver.switch_to.alert.accept()
return True
except NoAlertPresentException:
return False
def _try_dismiss(driver) -> bool:
try:
driver.switch_to.alert.dismiss()
return True
except NoAlertPresentException:
return FalseAuto-accept all iOS alerts:
from appium.options import XCUITestOptions
options = XCUITestOptions()
options.auto_accept_alerts = TrueiOS permission buttons by label
Different iOS permission dialogs have different button labels. Tap by label directly:
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
def tap_permission_button(driver, label: str, timeout=5):
"""Taps an iOS permission dialog button by its exact label."""
try:
WebDriverWait(driver, timeout).until(
lambda d: d.find_element(
AppiumBy.IOS_PREDICATE,
f"type == 'XCUIElementTypeButton' AND label == '{label}'"
)
).click()
except TimeoutException:
pass # Button not shown
# Usage
tap_permission_button(driver, "Allow While Using App") # Location
tap_permission_button(driver, "Allow") # Camera/notifications
tap_permission_button(driver, "Don't Allow") # Deny
tap_permission_button(driver, "Allow Once") # iOS 14+ one-time locationPush notification prompt (iOS)
Push prompts appear on real devices on first launch. Simulators before iOS 16 don't show them.
def dismiss_notification_prompt(driver):
"""Dismiss iOS push notification permission dialog if shown."""
# Try "Don't Allow" first — tests should not rely on push delivery
tap_permission_button(driver, "Don't Allow", timeout=3)
if not tap_permission_button.__doc__: # if nothing was tapped
# Some versions show "Disallow"
tap_permission_button(driver, "Disallow", timeout=1)AlertHandler class
Centralise alert handling in a reusable class:
class AlertHandler:
def __init__(self, driver):
self.driver = driver
def accept_if_present(self, timeout=3):
accept_alert(self.driver, timeout)
return self
def dismiss_if_present(self, timeout=3):
dismiss_alert(self.driver, timeout)
return self
def grant_android_permission(self, timeout=4):
grant_permission(self.driver, timeout)
return self
def deny_android_permission(self, timeout=4):
deny_permission(self.driver, timeout)
return self
def tap_ios_button(self, label: str, timeout=4):
tap_permission_button(self.driver, label, timeout)
return selfUsage in page objects:
class LocationPage(BasePage):
ENABLE_LOCATION = (AppiumBy.ACCESSIBILITY_ID, "enableLocation")
def enable_location(self):
self.tap(self.ENABLE_LOCATION)
# Handle the system permission dialog
alerts = AlertHandler(self.driver)
alerts.tap_ios_button("Allow While Using App").grant_android_permission()
return selfFixture: handle startup dialogs
For suites where every test starts fresh, handle startup dialogs in a fixture rather than in individual page objects:
@pytest.fixture
def clean_driver(driver):
"""Returns a driver with startup dialogs already dismissed."""
accept_alert(driver, timeout=3) # iOS notification prompt
grant_permission(driver, timeout=3) # Android location or camera
yield driverTests use clean_driver instead of driver when they don't care about permission flows:
def test_login(clean_driver):
LoginPage(clean_driver).login("user", "pass")
# No permission dialog to handle