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
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
});
}
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 ();
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:
Fine Location (Precise)
Coarse Location (Approximate)
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 ();
}
});
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 ();
}
iOS Permission States iOS has different permission states:
Allow While Using App : User can use the feature when app is active
Allow Once : One-time permission (similar to Android 11+)
Don’t Allow : Permission denied
// Grant "While Using" permission
await $.platform.mobile. grantPermissionWhenInUse ();
// Or grant "Allow Once" permission
await $.platform.mobile. grantPermissionOnlyThisTime ();
Background Permissions Some permissions (like location) may prompt twice - once for “When In Use” and again for “Always”: // First dialog - While Using
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
// Second dialog might appear for "Always" access
await Future < void >. delayed ( const Duration (seconds : 2 ));
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
Best Practices
Always check if dialog is visible
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 ();
}
Check permission state before requesting
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 ();
}
Call $.pump() after handling dialogs
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.