How to Test Permission Request Flows (Android + iOS)
Permission prompts are a privacy flashpoint. Ask at the wrong moment, lose the user's trust. Ask for too much, trigger a manual review. Ask carelessly, ship a feature that crashes in production on den
Permission prompts are a privacy flashpoint. Ask at the wrong moment, lose the user's trust. Ask for too much, trigger a manual review. Ask carelessly, ship a feature that crashes in production on denied permission. This guide covers how to test permission flows so they do not betray users or your release.
What permissions apps typically need
Runtime (Android 6+, iOS always)
- Location (fine, coarse, background)
- Camera
- Microphone
- Contacts
- Calendar
- Photo library (read, write)
- Phone (call, read phone number)
- SMS (read, send)
- Body sensors
- Notifications (Android 13+)
- Nearby Bluetooth / Wi-Fi (Android 12+)
Install-time (Android only, auto-granted)
- Internet, network state
- Foreground service
- etc.
Special / signature / manufacturer
- Accessibility service
- Device admin
- System alert window
Testing the happy path
- First encounter with feature → permission prompt appears
- User grants → feature works immediately
- User denies → feature gracefully unavailable with clear UX
- User grants after initial denial → feature works on next attempt
Denied / restricted states
- First denial → app explains why permission is needed and offers to retry
- Second denial → respects choice, does not re-nag
- "Don't ask again" selected → app shows Settings deep link
- Permission revoked in Settings while app open → app detects and handles
- Permission revoked while app closed → cold start handles gracefully
- Location permission downgraded (fine → coarse) → feature degrades gracefully
Rationale and timing
- Permission requested only when needed (in-context, not at launch)
- Rationale shown before Android system dialog (on second attempt)
- iOS purpose string in Info.plist matches feature context
- Rationale is honest (says what the permission is used for)
Foreground vs background
- Foreground location requested first, background only if needed, and with explicit justification
- Background location use case visible to user (Android 11+ requires this)
- Camera / microphone only active when feature is active (privacy indicators, iOS 14+)
Multi-permission features
- Feature requiring 2+ permissions requests each separately
- If one is denied but others granted, partial feature works or clear fallback
- Grouped requests (one-tap grant multiple) clearly list each
iOS specifics
- Info.plist includes
NSCameraUsageDescriptionetc. for every used permission - Missing purpose string = App Store rejection
requestAlwaysAuthorizationonly for features that truly need background location- App Tracking Transparency prompt shown before any IDFA access
- Photo library limited / full access distinction (iOS 14+)
Android specifics
POST_NOTIFICATIONSprompted at the right moment (Android 13+)BLUETOOTH_SCAN/BLUETOOTH_CONNECTfor BT features (Android 12+)READ_MEDIA_IMAGES/VIDEO/AUDIOreplacingREAD_EXTERNAL_STORAGE(Android 13+)- Scoped storage — app writes only to app-specific dirs by default
- Foreground service types declared (Android 14+ strict)
Edge cases
- Cold start with permission previously granted → app uses feature immediately
- Cold start with permission previously denied → app hides / disables feature
- App updated to require new permission → existing users prompted on next feature use
- User downgrades OS to older Android where some permissions did not exist → app handles
- Device admin / work profile restricting permission → app respects and does not loop
Accessibility
- Permission rationale readable by screen reader
- Denied-state UI explains next steps audibly
- Settings deep link works with assistive nav
Privacy
- App requests minimum necessary (not "just in case")
- Permission usage logged for audit (internal)
- No permission requested that is never actually used
- Secondary / optional permissions clearly marked optional
Manual testing
Install fresh. Go through each permission-gated feature. Deny once, deny again, deny with "don't ask again," grant, revoke in settings, revoke while app backgrounded. Observe behavior at each step.
Automated testing
Android (UiAutomator)
# Grant permission programmatically before test
driver.execute_script("mobile: changePermissions", {
"permissions": ["android.permission.CAMERA"],
"target": "com.example.app",
"action": "grant"
})
iOS (XCUITest)
let app = XCUIApplication()
app.launchArguments += ["-AppleLanguages", "(en)", "-AppleLocale", "en_US"]
// Reset permissions
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
app.launch()
// Handle alert
addUIInterruptionMonitor(withDescription: "Camera") { alert in
alert.buttons["Allow"].tap()
return true
}
How SUSA handles permissions
SUSA detects permission dialogs and the adversarial persona tries all branches: allow, deny, deny with "don't ask again." Each variant runs as a separate exploration session. Reports flag:
- Features that crash on permission deny
- Features that silently do nothing on deny (broken UX)
- Features that re-prompt after "don't ask again" (respect violation)
susatest-agent test myapp.apk --persona adversarial --permission-mode random
Common production bugs
- Crash on permission deny — code assumes permission granted
- Infinite prompt loop on "don't ask again" — app re-requests on every feature use
- Feature works with coarse location but app says "grant fine" — unnecessary escalation
- Photo library access prompted but never used — reviewer rejection, Privacy Policy liability
- Missing purpose string for a permission (iOS) — App Store rejection
Permission flow is worth a manual test per release. Automation covers regression; humans judge UX.
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