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

February 27, 2026 · 3 min read · How-To Guides

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)

Install-time (Android only, auto-granted)

Special / signature / manufacturer

Testing the happy path

  1. First encounter with feature → permission prompt appears
  2. User grants → feature works immediately
  3. User denies → feature gracefully unavailable with clear UX
  4. User grants after initial denial → feature works on next attempt

Denied / restricted states

  1. First denial → app explains why permission is needed and offers to retry
  2. Second denial → respects choice, does not re-nag
  3. "Don't ask again" selected → app shows Settings deep link
  4. Permission revoked in Settings while app open → app detects and handles
  5. Permission revoked while app closed → cold start handles gracefully
  6. Location permission downgraded (fine → coarse) → feature degrades gracefully

Rationale and timing

  1. Permission requested only when needed (in-context, not at launch)
  2. Rationale shown before Android system dialog (on second attempt)
  3. iOS purpose string in Info.plist matches feature context
  4. Rationale is honest (says what the permission is used for)

Foreground vs background

  1. Foreground location requested first, background only if needed, and with explicit justification
  2. Background location use case visible to user (Android 11+ requires this)
  3. Camera / microphone only active when feature is active (privacy indicators, iOS 14+)

Multi-permission features

  1. Feature requiring 2+ permissions requests each separately
  2. If one is denied but others granted, partial feature works or clear fallback
  3. Grouped requests (one-tap grant multiple) clearly list each

iOS specifics

  1. Info.plist includes NSCameraUsageDescription etc. for every used permission
  2. Missing purpose string = App Store rejection
  3. requestAlwaysAuthorization only for features that truly need background location
  4. App Tracking Transparency prompt shown before any IDFA access
  5. Photo library limited / full access distinction (iOS 14+)

Android specifics

  1. POST_NOTIFICATIONS prompted at the right moment (Android 13+)
  2. BLUETOOTH_SCAN / BLUETOOTH_CONNECT for BT features (Android 12+)
  3. READ_MEDIA_IMAGES / VIDEO / AUDIO replacing READ_EXTERNAL_STORAGE (Android 13+)
  4. Scoped storage — app writes only to app-specific dirs by default
  5. Foreground service types declared (Android 14+ strict)

Edge cases

  1. Cold start with permission previously granted → app uses feature immediately
  2. Cold start with permission previously denied → app hides / disables feature
  3. App updated to require new permission → existing users prompted on next feature use
  4. User downgrades OS to older Android where some permissions did not exist → app handles
  5. Device admin / work profile restricting permission → app respects and does not loop

Accessibility

  1. Permission rationale readable by screen reader
  2. Denied-state UI explains next steps audibly
  3. Settings deep link works with assistive nav

Privacy

  1. App requests minimum necessary (not "just in case")
  2. Permission usage logged for audit (internal)
  3. No permission requested that is never actually used
  4. 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:


susatest-agent test myapp.apk --persona adversarial --permission-mode random

Common production bugs

  1. Crash on permission deny — code assumes permission granted
  2. Infinite prompt loop on "don't ask again" — app re-requests on every feature use
  3. Feature works with coarse location but app says "grant fine" — unnecessary escalation
  4. Photo library access prompted but never used — reviewer rejection, Privacy Policy liability
  5. 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