with one click
uiautomator-permissions-popups
// Handle Android runtime permission dialogs, ANR popups, and SystemUI interruptions in UI Automator tests using watchFor/ScopedWatcher and legacy UiWatcher APIs.
// Handle Android runtime permission dialogs, ANR popups, and SystemUI interruptions in UI Automator tests using watchFor/ScopedWatcher and legacy UiWatcher APIs.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | uiautomator-permissions-popups |
| description | Handle Android runtime permission dialogs, ANR popups, and SystemUI interruptions in UI Automator tests using watchFor/ScopedWatcher and legacy UiWatcher APIs. |
| tech_stack | ["android"] |
| language | ["kotlin","java"] |
| capability | ["permission","e2e-testing"] |
| version | uiautomator 2.4.0-alpha05 (modern); 2.3.0 (legacy) |
| collected_at | "2026-04-22T00:00:00.000Z" |
Source: https://developer.android.com/training/testing/other-components/ui-automator, https://developer.android.com/training/testing/other-components/ui-automator-legacy, https://developer.android.com/reference/androidx/test/uiautomator/UiDevice
UI Automator tests run outside the app process and frequently encounter system-level dialogs — runtime permission prompts, ANR ("App isn't responding") popups, crash dialogs, and other SystemUI interruptions. This skill covers both the modern watchFor/ScopedWatcher<T> API (2.4.0+) and the legacy registerWatcher/UiWatcher mechanism (2.2.0+) for automatically dismissing or interacting with these unexpected dialogs.
onElement assertions for thoseimport androidx.test.uiautomator.PermissionDialog
@Test fun testWithPermissions() = uiAutomator {
startApp("com.example.app")
// Register watcher BEFORE the action that triggers the dialog
watchFor(PermissionDialog) { clickAllow() }
// Trigger the permission dialog
onElement { textAsString() == "Enable Camera" }.click()
// For multi-phase: switch to deny after clearing app data
clearAppData("com.example.app")
startApp("com.example.app")
watchFor(PermissionDialog) { clickDeny() }
onElement { textAsString() == "Enable Camera" }.click()
onElement { textAsString() == "Permission denied message" }
}
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
device.registerWatcher("PermissionWatcher") {
val allow = device.findObject(By.text("Allow").clazz("android.widget.Button"))
if (allow != null) { allow.click(); true } else { false }
}
// ... test actions ...
if (device.hasWatcherTriggered("PermissionWatcher")) {
device.resetWatcherTriggers() // re-arm for next occurrence
}
device.removeWatcher("PermissionWatcher") // clean up
| API | Generation | Scope | Fires when |
|---|---|---|---|
watchFor(ScopedWatcher) { } | Modern 2.4.0+ | uiAutomator { } block | Matching dialog appears |
PermissionDialog.clickAllow() | Built-in | PermissionDialog scope | Clicks Allow/OK |
PermissionDialog.clickDeny() | Built-in | PermissionDialog scope | Clicks Deny/Cancel |
registerWatcher(name, UiWatcher) | Legacy 2.2.0+ | UiDevice | findObject(UiSelector) fails |
removeWatcher(name) | Legacy | UiDevice | Manual cleanup |
resetWatcherTriggers() | Legacy | UiDevice | Re-arms triggered watcher |
runWatchers() | Legacy | UiDevice | Forces all watchers to run immediately |
hasWatcherTriggered(name) | Legacy | UiDevice | Check if specific watcher fired |
hasAnyWatcherTriggered() | Legacy | UiDevice | Check if any watcher fired |
clearAppData(packageName) | Modern | uiAutomator { } | Resets app + permissions |
UiSelector misses, NOT on BySelector/findObject(By...) failures. If your tests use the By API, legacy watchers are ineffective.resetWatcherTriggers() to re-arm. Modern: watchFor handles re-registration naturally, but re-call it after clearAppData.PermissionDialog handles this; for custom ScopedWatcher, match on resource IDs or content descriptions, not hardcoded "Allow"/"Deny".clearAppData is destructive — it resets all app data including granted permissions, databases, and SharedPreferences.uiautomator-selectors for robust element finding before and after dialog dismissal.uiautomator-device-control when permissions involve device-level actions (e.g., openQuickSettings for location toggles).uiautomator-cross-app when permission flows cross app boundaries (e.g., testing that a denied permission still allows in-app fallback, then launching Settings to grant it).ScopedWatcher<T>, implement match(node: AccessibilityNodeInfo): T? and expose action methods on T — follow the PermissionDialog pattern.