Skip to main content
Testing permissions is crucial for ensuring your app handles user privacy correctly. Patrol provides powerful native automation capabilities to interact with system permission dialogs on both Android and iOS.

Overview

Patrol allows you to:
  • Detect when permission dialogs appear
  • Grant permissions (“While Using”, “Only This Time”, or “Always”)
  • Deny permissions
  • Select location accuracy (fine vs coarse)
  • Test permission flows across different app states

Permission Dialog Methods

Patrol provides several methods for handling permission dialogs:

isPermissionDialogVisible

Check if a permission dialog is currently visible

grantPermissionWhenInUse

Grant permission for “While Using” or “Allow”

grantPermissionOnlyThisTime

Grant permission for “Only This Time” (Android 11+)

denyPermission

Deny the permission request

Basic Permission Testing

1

Setup your test

Import the necessary packages and create a test using patrolTest:
import 'package:patrol/patrol.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  patrolTest('grants camera permission', ($) async {
    // Test code here
  });
}
2

Navigate to permission request

Navigate to the part of your app that requests a permission:
await $.pumpWidgetAndSettle(const MyApp());
await $('Open permissions screen').scrollTo().tap();
3

Request and handle the permission

Trigger the permission request and handle the system dialog:
if (!await Permission.camera.isGranted) {
  await $('Request camera permission').tap();
  
  if (await $.platform.mobile.isPermissionDialogVisible()) {
    await $.platform.mobile.grantPermissionWhenInUse();
    await $.pump();
  }
}

Complete Examples

Granting Camera Permission

This example shows how to grant camera permission when requested:
patrol_test/camera_permission_test.dart
import 'package:patrol/patrol.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  patrolTest('grants camera permission', ($) async {
    await $.pumpWidgetAndSettle(const MyApp());
    
    await $('Open permissions screen').scrollTo().tap();

    if (!await Permission.camera.isGranted) {
      await $('Request camera permission').tap();

      if (await $.platform.mobile.isPermissionDialogVisible()) {
        await Future<void>.delayed(const Duration(seconds: 1));
        await $.platform.mobile.grantPermissionWhenInUse();
        await $.pump();
      }
    }
    
    // Verify permission was granted
    expect(await Permission.camera.isGranted, true);
  });
}

Testing Multiple Permissions

Test granting and denying different permissions:
patrol_test/multiple_permissions_test.dart
import 'package:patrol/patrol.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_test/flutter_test.dart';

const _timeout = Duration(seconds: 5);

void main() {
  patrolTest('handles multiple permissions', ($) async {
    await $.pumpWidgetAndSettle(const MyApp());
    await $('Open permissions screen').scrollTo().tap();

    // Grant camera permission
    await _requestAndGrantCameraPermission($);
    
    // Grant microphone permission (only this time)
    await _requestAndGrantMicrophonePermission($);
    
    // Deny location permission
    await _requestAndDenyLocationPermission($);
  });
}

Future<void> _requestAndGrantCameraPermission(
  PatrolIntegrationTester $,
) async {
  if (!await Permission.camera.isGranted) {
    await $('Request camera permission').tap();
    if (await $.platform.mobile.isPermissionDialogVisible(timeout: _timeout)) {
      await $.platform.mobile.grantPermissionWhenInUse();
      await $.pump();
    }
  }
  expect(await Permission.camera.isGranted, true);
}

Future<void> _requestAndGrantMicrophonePermission(
  PatrolIntegrationTester $,
) async {
  if (!await Permission.microphone.isGranted) {
    await $('Request microphone permission').tap();
    if (await $.platform.mobile.isPermissionDialogVisible(timeout: _timeout)) {
      await $.platform.mobile.grantPermissionOnlyThisTime();
      await $.pump();
    }
  }
  expect(await Permission.microphone.isGranted, true);
}

Future<void> _requestAndDenyLocationPermission(
  PatrolIntegrationTester $,
) async {
  if (!await Permission.location.isGranted) {
    await $('Request location permission').tap();
    if (await $.platform.mobile.isPermissionDialogVisible(timeout: _timeout)) {
      await $.platform.mobile.denyPermission();
      await $.pump();
    }
  }
  expect(await Permission.location.isDenied, true);
}

Location Permissions

Location permissions have special handling for precision selection:
import 'package:patrol/patrol.dart';

patrolTest('grants precise location permission', ($) async {
  await $.pumpWidgetAndSettle(const MyApp());
  await $('Request location').tap();
  
  if (await $.platform.mobile.isPermissionDialogVisible()) {
    // Select "Precise" location
    await $.platform.mobile.selectFineLocation();
    await $.platform.mobile.grantPermissionWhenInUse();
    await $.pump();
  }
});

Platform-Specific Considerations

Android 11+ (API 30+)

Android 11 introduced “Only this time” permissions:
// This grants temporary permission
await $.platform.mobile.grantPermissionOnlyThisTime();
On Android versions older than 11, grantPermissionOnlyThisTime() behaves the same as grantPermissionWhenInUse().

Android 14+ Special Permissions

Some features like scheduling notifications require additional permissions:
// Check for Android 14+ alarm permission
final alarmPermissionSelector = Selector(
  text: 'Allow setting alarms and reminders',
);
final views = await $.native.getNativeViews(alarmPermissionSelector);
if (views.isNotEmpty) {
  await $.native.tap(alarmPermissionSelector);
  await $.native.pressBack();
}

Best Practices

Always use isPermissionDialogVisible() before attempting to interact with the dialog:
if (await $.platform.mobile.isPermissionDialogVisible()) {
  await $.platform.mobile.grantPermissionWhenInUse();
}
This prevents test failures when permissions are already granted.
Permission dialogs may take time to appear. Use timeouts appropriately:
if (await $.platform.mobile.isPermissionDialogVisible(
  timeout: Duration(seconds: 5),
)) {
  await $.platform.mobile.grantPermissionWhenInUse();
}
Use the permission_handler package to check permission states:
if (!await Permission.camera.isGranted) {
  // Only request if not already granted
  await $('Request camera permission').tap();
}
Always call $.pump() after handling permission dialogs to ensure Flutter processes the state change:
await $.platform.mobile.grantPermissionWhenInUse();
await $.pump(); // Important!
Permission testing works on both physical devices and emulators/simulators. However, permission state may persist between test runs, so consider resetting permissions in your test setup.

Common Issues

Permission already granted: If a permission was previously granted, the dialog won’t appear again. Reset app permissions between test runs or check the permission state before requesting.
Use the Patrol DevTools Extension to inspect native permission dialogs and find the correct selectors if you need custom handling.

Build docs developers (and LLMs) love