How to Write Test Cases for Permission Dialogs (With Examples)
How to Write Test Cases for Permission Dialogs (With Examples)
How to Write Test Cases for Permission Dialogs (With Examples)
Permission dialogs are system‑mediated prompts that ask users to grant or deny access to protected resources such as location, camera, contacts, or storage. Because they appear asynchronously, can be influenced by device policy, and directly affect app functionality and privacy compliance, they deserve a dedicated test‑case suite. This guide walks you through the anatomy of a high‑signal test case, shows how to cover positive, negative, edge, and boundary scenarios, provides a concrete matrix of 20+ examples, explains data setup and prioritization, and details both manual and automated execution approaches. Throughout, we reference how an autonomous QA platform like SUSA can augment the process without turning the article into a sales pitch.
How to Write Test Cases for Permission Dialogs (With Examples): Test Case Anatomy
A test case that reliably validates a permission dialog consists of five essential fields: identifier, preconditions, steps, expected result, and post‑conditions (clean‑up). Each field must be explicit enough that another engineer can reproduce the test on any device or emulator without guessing.
Identifier (ID) – Use a short, hierarchical code that reflects the feature area and the permission under test. Example: PERM_LOC_01 for the first location‑permission test. Prefixes make it easy to filter in test‑management tools and to trace back to requirements.
Preconditions – List the exact device state required before the first step. This includes:
- OS version (e.g., Android 13 API 33)
- App version or build number
- Current permission status (granted, denied, or “never ask again”)
- Any necessary account login or network condition
- Device locale and accessibility settings if they affect the dialog
If a precondition can be set via a command, include that command (see the Data Setup section later). Ambiguous preconditions lead to flaky results and wasted effort.
Steps – Write each action as an imperative sentence, using the UI element’s role rather than its positional coordinates. Good steps avoid hard‑coded pixel values and instead rely on accessibility IDs, content‑descriptions, or text that is stable across locales. For a permission dialog, typical steps are:
- Launch the activity that triggers the permission request.
- Wait for the system dialog to appear (use an explicit wait, not a sleep).
- Tap the button that corresponds to the desired user choice (Allow, Deny, or “Don’t ask again”).
- Verify the subsequent app behavior or system state.
If a step involves a system setting (e.g., turning off battery optimization), note the exact path: Settings → Apps → [YourApp] → Battery → Unrestricted.
Expected Result – State the observable outcome that determines pass or fail. For permission tests, this is usually:
- The permission is granted/revoked as reflected in
pm list permissions -g -d. - The app proceeds to the next screen or shows a feature‑specific UI.
- A toast or snackbar matches the expected message (e.g., “Location access granted”).
- No crash, ANR, or unhandled exception occurs.
Post‑conditions – Return the device to a known state so the next test starts clean. This often means revoking the permission via adb shell pm revoke or clearing app data. Include the clean‑up command in the test case document to avoid state bleed.
When these five fields are filled with concrete, unambiguous information, the test case becomes a reliable artifact that can be executed manually, scripted, or fed into an autonomous explorer.
How to Write Test Cases for Permission Dialogs (With Examples): Positive and Negative Scenarios
Permission testing naturally splits into three outcome categories: granting the permission, denying it, and encountering a system‑level restriction. Each category yields distinct test ideas.
Granting Permission on First Prompt
The most common path is the user tapping “Allow” when the dialog appears for the first time. Test cases in this group verify that:
- The app receives the permission callback (
onRequestPermissionsResultor the newer Activity Result API). - The protected feature becomes immediately usable (e.g., the camera preview starts).
- No secondary dialog appears erroneously.
- The app logs the grant for analytics or compliance tracking.
Denying Permission (Ask Again Later)
When the user selects “Deny” but leaves the option to be asked again, the app should:
- Receive a denial callback.
- Disable or hide the feature that depends on the permission.
- Show a user‑friendly explanation (optional) that explains why the permission is needed.
- Not crash if the code attempts to use the protected API; instead, it should handle the
SecurityExceptiongracefully.
Denying with “Never Ask Again”
Choosing “Deny” and checking the “Don’t ask again” box triggers a permanent denial until the user manually changes the setting in system Settings. Test cases must verify:
- The callback returns
PERMISSION_DENIEDwith a flag indicating the user chose never to ask again. - Subsequent attempts to request the same permission do not re‑show the system dialog.
- The app directs the user to the Settings screen via an intent (
Settings.ACTION_APPLICATION_DETAILS_SETTINGS) when it detects the permanent denial state. - No infinite loop of dialog requests occurs.
System‑Level Denial (Device Policy or Enterprise Restriction)
On managed devices, an administrator can disable certain permissions via a Device Policy Controller (DPC). In this scenario:
- The permission request may be silently ignored, or the system may show a brief toast indicating the restriction.
- The app’s permission check returns
PERMISSION_DENIEDwithout showing a dialog. - The app should fall back to a limited‑functionality mode and log the restriction for IT audits.
Covering these four categories ensures that the app behaves correctly regardless of how the user or the device decides to grant access.
How to Write Test Cases for Permission Dialogs (With Examples): Edge and Boundary Cases
Beyond the straightforward allow/deny paths, permission dialogs expose edge conditions that only surface under specific device states, rapid interactions, or localization quirks. Treat these as boundary tests because they push the system to its limits.
Rapid Successive Prompts
If an app requests multiple permissions in quick succession (e.g., location then camera), the system may queue dialogs or collapse them. Test steps:
- Trigger a request for Permission A.
- Before the dialog appears, trigger a request for Permission B.
- Observe whether the system shows one dialog, two dialogs, or merges the requests.
- Verify that the callbacks for each permission are delivered correctly and that the UI does not become unresponsive.
Permission Dialog Appears Under an Overlay
Some apps display custom overlays (e.g., chat heads, screen‑filter layers). If an overlay sits above the system dialog, the user may be unable to tap the buttons. Test steps:
- Enable an overlay app that draws over other apps (e.g., a screen‑dim filter).
- Trigger the permission request.
- Attempt to interact with the dialog via accessibility services or UIAutomator.
- Confirm that the dialog remains tappable and that the overlay does not block input. Log any failure as a potential accessibility issue.
Locale Changes Affecting Dialog Text
The text of permission dialogs is localized. If your app relies on string matching (e.g., using Espresso’s withText("Allow")) the test will fail in non‑English locales. Test steps:
- Change device language to a right‑to‑left language (e.g., Arabic) or a language with longer strings (e.g., German).
- Trigger the permission request.
- Verify that the dialog appears with the correct localized text.
- If your automation uses text‑based locators, ensure they are replaced with resource‑ID or content‑description locators that are locale‑independent.
Dialog Appears After Activity Recreation (Configuration Change)
Rotating the device or changing font scale can cause the activity to recreate while a permission request is pending. Test steps:
- Trigger a permission request.
- Before the user responds, rotate the device to landscape.
- Verify that the dialog remains on screen and is not dismissed.
- After the user responds, ensure the activity’s state (e.g., ViewModel data) is correctly restored and the permission callback is received.
Permission Request During Background Execution
Starting with Android 12, apps targeting API 31+ cannot launch permission dialogs from the background unless they meet specific exceptions. Test steps:
- Move the app to the background (e.g., press Home).
- From a background service or WorkManager, attempt to request a sensitive permission.
- Confirm that the system either denies the request silently or shows a toast indicating the restriction.
- Ensure the app does not crash and logs the background restriction appropriately.
These edge cases often escape manual testing because they depend on timing or device state. Including them in your test matrix raises the likelihood of catching production‑only bugs.
How to Write Test Cases for Permission Dialogs (With Examples): Data Setup and Test Environment
Reliable permission tests start with a known permission state. The Android Debug Bridge (adb) provides deterministic ways to grant, revoke, or reset permissions. Complementing adb with an autonomous explorer like SUSA can reduce manual setup and uncover hidden states.
Using adb to Reset Permissions
Before each test, set the permission to a known baseline:
# Revoke a permission (sets to denied, not “never ask again”)
adb shell pm revoke com.example.app android.permission.ACCESS_FINE_LOCATION
# Grant a permission
adb shell pm grant com.example.app android.permission.CAMERA
# Simulate “never ask again” by adding the app to the ignored list
adb shell appops set com.example.app ACCESS_FINE_LOCATION ignore
Include these commands in the preconditions field of your test case. If you need to restore the default state after a test, run the revoke command again or clear app data:
adb shell pm clear com.example.app
Clearing data also removes any persisted “never ask again” flags, returning the permission to the “not determined” state.
Using SUSA CLI for Exploration
SUSA can be installed via pip install susatest-agent and pointed at an APK or a device. To generate a baseline exploration that surfaces permission‑related screens:
susatest explore --app-path ./app-debug.apk --device emulator-5554 --output ./susa-report
The explorer will autonomously trigger permission dialogs as part of its normal flow discovery. After the run, you can inspect the report to see which dialogs were encountered, how the autonomous personas reacted, and whether any unexpected states (e.g., dialog under overlay) appeared. Use this output to enrich your manual test cases with realistic preconditions that you might not have considered.
Mocking System Dialogs in Unit Tests
For fast feedback, unit tests can mock the permission‑checking layer using frameworks like Mockito or Robolectric. Example with Robolectric:
@Test
public void locationPermissionGranted_startsMap() {
RuntimePermissionsDispatcher.setPermissionResult(
Manifest.permission.ACCESS_FINE_LOCATION,
PackageManager.PERMISSION_GRANTED);
ActivityScenario.launch(MainActivity.class);
onView(withId(R.id.mapView)).check(matches(isDisplayed()));
}
While unit tests cannot verify the actual system dialog UI, they confirm that your app’s logic branches correctly based on the permission result. Pair these with instrumented tests that validate the UI to achieve full coverage.
How to Write Test Cases for Permission Dialogs (With Examples): Prioritization and Traceability
Not all permission tests carry equal weight. Use a risk‑based matrix to decide which cases to automate first, which to keep as manual exploratory checks, and which to document for compliance audits.
Risk‑Based Priority Matrix
| Impact \ Likelihood | High (frequent) | Medium (occasional) | Low (rare) |
|---|---|---|---|
| High (security, privacy violation, core feature blocked) | P0 – Automate & run on every commit | P1 – Automate, run nightly | P2 – Manual, regression‑only |
| Medium (UX degradation, optional feature) | P1 – Automate, run nightly | P2 – Manual, sprint‑level | P3 – Optional, documentation |
| Low (cosmetic, edge‑case unlikely in prod) | P2 – Manual, sprint‑level | P3 – Documentation only | P4 – Skip |
Assign each test case an ID and a priority based on where it lands in the matrix. For example, a test that verifies the app redirects to Settings after a permanent denial (high impact, high likelihood) becomes a P0 candidate for automation.
Linking to Requirements
Trace each test case to a specific requirement artifact:
- Android manifest
tags → Requirement REQ‑PERM‑001: “App must declare ACCESS_FINE_LOCATION.” - Privacy policy clause → Requirement REQ‑PRIV‑004: “User must be able to revoke location access at any time.”
- User story → Requirement REQ‑US‑012: “As a user, I can deny camera access and still browse photos.”
Record the requirement ID in the test case’s “References” field. Most test‑management tools (e.g., Zephyr, TestRail) allow you to link test cases to Jira epics or user stories, providing an audit trail for regulators or internal reviews.
Maintaining Traceability in Test Management
When you add a new permission test:
- Create the test case with its ID, preconditions, steps, expected result, and post‑conditions.
- Add a “Related Requirements” list (e.g., REQ‑PERM‑001, REQ‑PRIV‑004).
- Set the priority field according to the matrix.
- Tag the test with labels like
permission,android,automationfor easy filtering. - After each run, update the “Last Executed” and “Result” fields. If a test fails, link the defect to the test case for root‑cause analysis.
This traceability ensures that when a permission‑related requirement changes (e.g., a new scoped storage rule), you can quickly locate all affected tests and update them.
How to Write Test Cases for Permission Dialogs (With Examples): Worked Test Matrix (20+ Examples)
Below is a consolidated table that captures a broad spectrum of permission‑dialog scenarios. Each row includes an ID, preconditions, steps, and expected result. Feel free to copy‑paste into your test‑management tool and adapt the package name (com.example.app) and permission strings to your own app.
| ID | Preconditions | Steps | Expected Result |
|---|---|---|---|
| PERM_LOC_01 | Device API 33, app installed, location permission not determined | 1. Launch MainActivity.2. Tap “Enable Location” button. 3. Wait for system location dialog. 4. Tap Allow. | Location permission granted (pm grant …).Map fragment appears and shows user’s current location. No crash or ANR. |
| PERM_LOC_02 | Location permission denied (not “never ask again”) | Same steps as PERM_LOC_01, but tap Deny in step 4. | Permission remains denied. Map fragment shows a placeholder with text “Location access denied”. App logs DENIED callback. |
| PERM_LOC_03 | Location permission never ask again (set via appops ignore) | Same steps as PERM_LOC_01. | System does not show location dialog. Permission check returns PERMISSION_DENIED with shouldShowRequestPermissionRationale == false.App displays a snackbar prompting user to go to Settings. Tapping the snackbar opens Settings.ACTION_APPLICATION_DETAILS_SETTINGS. |
| PERM_CAM_01 | Camera permission not determined, device not in call | 1. Open ProfileFragment.2. Tap “Take Photo”. 3. Wait for camera dialog. 4. Tap Allow. | Camera permission granted. Camera preview starts within 2 seconds. Photo can be captured and saved. |
| PERM_CAM_02 | Camera permission denied | Same steps as PERM_CAM_01, tap Deny. | Permission remains denied. Photo button shows a toast “Camera permission required”. No crash when attempting to open camera. |
| PERM_CAM_03 | Camera permission never ask again | Same steps as PERM_CAM_01. | No dialog shown. Permission check returns denied with rationale flag false. App disables the photo button and shows an explanatory banner. |
| PERM_CONTS_01 | Contacts permission not determined | 1. Navigate to InviteFriendsScreen.2. Tap “Import Contacts”. 3. Wait for contacts dialog. 4. Tap Allow. | Contacts permission granted. Contact picker loads and displays at least one contact. Selected contact can be added to invite list. |
| PERM_CONTS_02 | Contacts permission denied | Same steps as PERM_CONTS_01, tap Deny. | Permission denied. Import button shows a snackbar “Cannot access contacts”. App does not crash. |
| PERM_CONTS_03 | Contacts permission never ask again | Same steps as PERM_CONTS_01. | System silently denies. App shows a dialog explaining why contacts are needed and a button to open Settings. Settings screen opens correctly. |
| PERM_SMS_01 | SMS permission not determined, device API ≥33 (runtime) | 1. Open VerificationScreen.2. Tap “Send Code via SMS”. 3. Wait for SMS dialog. 4. Tap Allow. | SMS permission granted. App can send SMS and receives delivery callback. Verification code is auto‑filled if SMS Retriever API used. |
| PERM_SMS_02 | SMS permission denied | Same steps as PERM_SMS_01, tap Deny. | Permission denied. App shows error “Unable to send SMS”. Falls back to email verification path. |
| PERM_SMS_03 | SMS permission never ask again | Same steps as PERM_SMS_01. | No dialog shown. Permission check returns denied with rationale false. App hides the SMS option and shows only email verification. |
| PERM_BODYSENSORS_01 | Body sensors permission not determined, API ≥29 | 1. Start HealthActivity.2. Tap “Start Heart Rate Monitor”. 3. Wait for body‑sensor dialog. 4. Tap Allow. | Permission granted. Heart‑rate sensor begins streaming data. Data displayed in real‑time chart. |
| PERM_BODYSENSORS_02 | Body sensors permission denied | Same steps as PERM_BODYSENSORS_01, tap Deny. | Permission denied. Heart‑rate button disabled, toast “Sensor access required”. No crash. |
| PERM_BODYSENSORS_03 | Body sensors permission never ask again | Same steps as PERM_BODYSENSORS_01. | System does not show dialog. App shows a persistent banner “Enable body‑sensor access in Settings”. Tapping banner opens Settings. |
| PERM_BACKGROUNDLOC_01 | Target API 33, background location permission not determined | 1. Enable foreground location first (grant via step 1 of PERM_LOC_01). 2. Start a foreground service that requests background location. 3. Wait for background‑location dialog. 4. Tap Allow. | Background location granted. Service receives location updates even when app is in background. Battery usage shown in Settings > Apps > [App] > Battery. |
| PERM_BACKGROUNDLOC_02 | Background location denied | Same steps as PERM_BACKGROUNDLOC_01, tap Deny. | Background location denied. Foreground location still works (if previously granted). Service stops receiving updates and logs denial. |
| PERM_BACKGROUNDLOC_03 | Background location never ask again | Same steps as PERM_BACKGROUNDLOC_01. | No dialog shown. Background location requests are ignored silently. App shows a notification prompting user to enable background location in Settings. |
| PERM_OVERLAY_01 | Device with overlay permission not determined, overlay app active (e.g., screen‑filter) | 1. Launch app that requests overlay permission (e.g., chat‑head feature). 2. Wait for overlay dialog. 3. Tap Allow. | Overlay permission granted. Chat‑head appears and can be dragged. Underlying app remains usable. |
| PERM_OVERLAY_02 | Overlay permission denied | Same steps as PERM_OVERLAY_01, tap Deny. | Overlay permission denied. Chat‑head feature button shows toast “Overlay permission needed”. No crash. |
| PERM_OVERLAY_03 | Overlay permission never ask again | Same steps as PERM_OVERLAY_01. | System does not show dialog. App disables chat‑head button and shows a Settings shortcut. |
| PERM_NOTIFICATION_01 | Notification permission not determined, API ≥33 | 1. Open SettingsScreen.2. Toggle “Enable notifications”. 3. Wait for notification dialog. 4. Tap Allow. | Notification permission granted. App can post a test notification that appears in the shade. Notification channel importance set correctly. |
| PERM_NOTIFICATION_02 | Notification permission denied | Same steps as PERM_NOTIFICATION_01, tap Deny. | Permission denied. Toggle remains off, toast “Notifications disabled”. Attempting to post a notification results in silent drop (no exception). |
| PERM_NOTIFICATION_03 | Notification permission never ask again | Same steps as PERM_NOTIFICATION_01. | No dialog shown. App shows a banner “Enable notifications in Settings”. Tapping banner launches the notification settings screen. |
How to use the table
- Copy the rows into a spreadsheet or test‑management tool.
- Replace
com.example.appwith your actual package name and adjust permission strings if you use custom runtime permissions (e.g.,android.permission.BIND_ACCESSIBILITY_SERVICEfor accessibility services). - For each row, add a “Post‑condition” column if your process requires cleaning up (e.g.,
adb shell pm revoke …). - Tag each row with the appropriate priority from the risk matrix (P0‑P4) and link to the relevant requirement IDs.
How to Write Test Cases for Permission Dialogs (With Examples): Manual Execution Approach
Even with strong automation, manual testing remains valuable for exploring subtle UX issues, verifying accessibility, and validating behaviors that are hard to script (e.g., user perception of dialog timing). A disciplined manual process ensures that the automated suite stays aligned with real‑world usage.
Test Lab Setup
- Device matrix – Maintain a small set of physical devices covering the major OS versions you support (e.g., Android 10, 11, 12, 13) and at least one emulator for API level testing.
- Tooling – Install
adb,Android Studio(for logcat), and optionallyUI Automator Viewerto inspect element attributes. - Data‑reset script – Create a shell script that runs the adb revoke/grant commands listed in the Data Setup section before each test case. Store the script in version control so every tester can reproduce the exact starting state.
- Evidence capture – Use
adb shell screenrecordor the built‑in screen‑recording feature on the device to capture video of each test run. Save the clip alongside the test case ID for later review.
Execution Steps
For each test case:
- Run the reset script to achieve the precondition.
- Launch the app and navigate to the screen that triggers the permission request.
- Perform the steps exactly as written, noting any deviations (e.g., dialog does not appear).
- Observe the expected result and record Pass/Fail.
- If the result is Fail, capture a screenshot, logcat snippet, and the screen recording.
- Execute the post‑condition clean‑up script.
Exploratory Augmentation with SUSA
After completing the scripted manual suite, launch SUSA in exploratory mode to see whether its autonomous personas discover additional permission‑related states:
susatest explore --app-path ./app-debug.apk --device emulator-5554 --personas all --timeout 15m
Review the generated report for:
- Dialogs that appeared under unexpected overlays.
- Cases where a persona chose “Never ask again” after multiple rapid prompts.
- Any permission requests that were missed by the scripted cases (e.g., a permission requested only after a deep‑link navigation).
Add any new findings as additional test cases, assigning them IDs and priorities based on the risk matrix. This hybrid approach leverages the repeatability of scripted manual tests and the breadth of autonomous exploration.
How to Write Test Cases for Permission Dialogs (With Examples): Automated Execution Approach
Automated tests give you fast feedback on every commit and enable regression validation across device farms. The key to stable permission tests is handling the asynchronous nature of system dialogs and avoiding brittle locators.
Instrumented UI Tests (Espresso/UIAutomator)
Espresso works well for in‑app interactions but cannot directly interact with system dialogs. Use UIAutomator to tap system buttons, then return control to Espresso for in‑app validation.
Example Kotlin test for location permission:
@Test
fun locationPermissionGranted_showsMap() {
// Revoke to ensure a clean state
AdbShellCommand("pm revoke ${ApplicationProvider.getApplicationContext().packageName} android.permission.ACCESS_FINE_LOCATION")
.execute()
// Launch activity
ActivityScenario.launch(MainActivity::class.java)
// Trigger permission request
onView(withId(R.id.btnEnableLocation)).perform(click())
// Wait for system dialog and click Allow (UIAutomator)
val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
val allowButton = uiDevice.findObject(By.text("Allow"))
assertTrue(allowButton.waitForExists(5000))
allowButton.click()
// Return to Espresso to verify in‑app state
onView(withId(R.id.mapView)).check(matches(isDisplayed()))
}
Key points:
- Use
AdbShellCommand(a small wrapper) to reset permissions before each test. - Replace
By.text("Allow")with a resource‑ID based selector if you want locale‑independent locators:By.res("com.android.permissioncontroller", "permission_allow_button"). - Add an explicit wait (
waitForExists) instead ofThread.sleep. - After the system interaction, immediately switch back to Espresso to validate the app UI.
Flakiness mitigation:
- Wrap the dialog interaction in a retry loop (max 2 attempts) with a short backoff.
- Capture a screenshot on failure using
uiDevice.takeScreenshot(File("fail_${testName}.png")). - Run the test suite on a device farm (e.g., Firebase Test Lab) with multiple locales to ensure locators remain stable.
Web Permission Tests (Playwright)
For web apps, permission dialogs are browser‑mediated (geolocation, notifications, camera). Playwright can handle them natively.
Example TypeScript test for geolocation:
test('geolocation permission granted shows map', async ({ page }) => {
// Clear prior permissions
await page.context().clearPermissions();
// Navigate to page that requests location
await page.goto('https://example.com/map');
// Expect the permission dialog and grant it
await page.waitForEvent('dialog', async dialog => {
await dialog.accept(); // equivalent to clicking Allow
});
// Trigger the request (e.g., click a button)
await page.click('#enable-location');
// Verify map appears
await expect(page.locator('#map')).toBeVisible({ timeout: 5000 });
});
Notes:
clearPermissions()resets all permissions for the browser context, analogous to adb revoke.waitForEvent('dialog')catches the browser‑shown permission prompt without relying on text locators.- For “Never ask again” scenarios, use
page.context().grantPermissions(['geolocation'], { origin: 'https://example.com' })to pre‑set the state, then verify that no dialog appears.
Leveraging SUSA for Regression Script Generation
SUSA can observe the flows it explores and emit ready‑to‑run automation scripts. After an exploratory run:
susatest generate --report ./susa-report --output ./generated-tests --framework appium
The generated Appium (Android) or Playwright (Web) scripts contain:
- The exact sequence of UI interactions that led to each permission dialog.
- Precondition adb commands extracted from the explorer’s internal state‑
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free