Testing gallery image picking is essential for apps that allow users to select photos from their device. Patrol provides native automation to interact with the system photo gallery, pick single or multiple images, and verify the selection process.
Overview
Patrol enables gallery testing by:
Opening the native photo picker/gallery
Selecting single or multiple images
Handling gallery permissions
Using custom selectors for different devices
Testing both Android and iOS gallery flows
Key Methods
pickImageFromGallery Pick a single image from the gallery
pickMultipleImagesFromGallery Pick multiple images from the gallery
Custom Index Specify which image to pick by index
Custom Selectors Provide selectors for unsupported devices
How Gallery Picking Works
Pick Single Image
The pickImageFromGallery() method performs these actions:
Select an image (by default, the first image or the one at the provided index)
Confirm selection (on some Android devices)
Pick Multiple Images
The pickMultipleImagesFromGallery() method:
Selects multiple images (based on provided indexes)
Confirms selection (taps “Add” or “Done” button)
Default Selectors
Platform Image Selector Confirm Button Physical/Emulator Android (API < 34) com.google.android.documentsui:id/icon_thumbcom.google.android.documentsui:id/action_menu_selectPhysical/Emulator Android (API 34+) com.google.android.providers.media.module:id/icon_thumbnailcom.google.android.providers.media.module:id/button_addSimulator and Physical iOS IOSElementType.image (+2 offset for simulator, +1 for physical)IOSElementType.button with label “Add”
The default selectors work without customization on iOS devices and most Android emulators and Pixel physical devices.
Basic Gallery Testing
Setup gallery permissions
Request and grant photo/storage permission: import 'package:patrol/patrol.dart' ;
void main () {
patrolTest ( 'picks image from gallery' , ($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
// Tap button that opens gallery
await $. tap (#addPhotoFromGalleryButton);
// Grant permission if requested
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
});
}
Pick the image
Use pickImageFromGallery() to select an image: // Pick first image (index 0)
await $.platform.mobile. pickImageFromGallery (index : 0 );
Verify in your app
Verify that your app received and displayed the image: // Verify image appears in your app
await $( 'Image selected' ). waitUntilVisible ();
// Or verify image widget exists
expect ($( Image ).exists, true );
Complete Examples
Pick Single Image
Basic test for picking a single image:
patrol_test/gallery_single_test.dart
import 'package:patrol/patrol.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
void main () {
patrolTest (
'picks an image from gallery' ,
($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
// Open gallery picker
await $. tap (#addPhotoFromGalleryButton);
// Grant permission if needed
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
// Pick first image using default selectors
await $.platform.mobile. pickImageFromGallery (index : 0 );
// Verify image was selected
await $( 'Image added successfully' ). waitUntilVisible ();
expect ($( Image ).exists, true );
},
);
}
Pick Specific Image by Index
Select a specific image from the gallery:
patrol_test/gallery_index_test.dart
import 'package:patrol/patrol.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
void main () {
patrolTest (
'picks third image from gallery' ,
($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
await $. tap (#addPhotoFromGalleryButton);
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
// Pick the third image (index 2)
await $.platform.mobile. pickImageFromGallery (index : 2 );
await $. pumpAndSettle ();
expect ($( Image ).exists, true );
},
);
}
Pick Multiple Images
Select multiple images at once:
patrol_test/gallery_multiple_test.dart
import 'package:patrol/patrol.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
void main () {
patrolTest (
'picks multiple images from gallery' ,
($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
// Open multi-image picker
await $. tap (#addMultiplePhotosFromGalleryButton);
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
// Pick first three images
await $.platform.mobile. pickMultipleImagesFromGallery (
imageIndexes : [ 0 , 1 , 2 ],
);
await $. pumpAndSettle ();
// Verify all three images were selected
expect ($( Image ).count, equals ( 3 ));
await $( '3 images selected' ). waitUntilVisible ();
},
);
}
Gallery with Custom Selectors
For devices that don’t work with default selectors:
patrol_test/gallery_custom_test.dart
import 'package:patrol/patrol.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
void main () {
patrolTest (
'picks image with custom selectors' ,
($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
await $. tap (#addPhotoFromGalleryButton);
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
// Use custom selectors for specific device/gallery
await $.platform.mobile. pickImageFromGallery (
index : 1 ,
imageSelector : NativeSelector (
android : AndroidSelector (
resourceName : 'com.oplus.gallery:id/image' ,
),
ios : IOSSelector (label : 'Photo' ),
),
);
await $. pumpAndSettle ();
expect ($( Image ).exists, true );
},
);
}
Pick Multiple with Custom Selectors
patrol_test/gallery_multiple_custom_test.dart
import 'package:patrol/patrol.dart' ;
import 'package:flutter_test/flutter_test.dart' ;
void main () {
patrolTest (
'picks multiple images with custom selectors' ,
($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
await $. tap (#addMultiplePhotosFromGalleryButton);
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
await $.platform.mobile. pickMultipleImagesFromGallery (
imageIndexes : [ 0 , 1 ],
imageSelector : NativeSelector (
android : AndroidSelector (
resourceName : 'com.oplus.gallery:id/image' ,
),
ios : IOSSelector (label : 'Photo' ),
),
);
await $. pumpAndSettle ();
expect ($( Image ).count, equals ( 2 ));
},
);
}
Android Photo Picker Android has different photo pickers depending on the OS version: Android 13+ (API 33+):
New Photo Picker UI
No storage permission required for photo picker
More consistent across devices
Android 12 and below:
Uses DocumentsUI or device-specific gallery
May require storage permissions
API 34+ Changes: // Android API 34+ uses different selectors
AndroidSelector (
resourceName : 'com.google.android.providers.media.module:id/icon_thumbnail' ,
)
API < 34: // Older Android versions
AndroidSelector (
resourceName : 'com.google.android.documentsui:id/icon_thumb' ,
)
iOS Photo Picker iOS uses PHPicker (iOS 14+) or UIImagePickerController: Selector Behavior:
Image elements are indexed starting from 1 or 2 (device dependent)
Patrol automatically adds +2 offset for simulators
Patrol automatically adds +1 offset for physical devices
// iOS automatically handles offset
await $.platform.mobile. pickImageFromGallery (index : 0 );
// This picks the first visible image after offset
Multiple Selection: // iOS shows "Add" button to confirm selection
await $.platform.mobile. pickMultipleImagesFromGallery (
imageIndexes : [ 0 , 1 , 2 ],
);
// Automatically taps "Add" button
Best Practices
Always handle permissions
Check for and grant photo/storage permissions: if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
Use appropriate wait times
Gallery apps need time to load: await $. tap (#openGallery);
// Wait for gallery to load
await Future < void >. delayed ( const Duration (seconds : 2 ));
await $.platform.mobile. pickImageFromGallery (index : 0 );
Verify image count for multiple selection
When picking multiple images, verify the count: await $.platform.mobile. pickMultipleImagesFromGallery (
imageIndexes : [ 0 , 1 , 2 ],
);
// Verify correct number of images
expect ($( Image ).count, equals ( 3 ));
Test on real devices with photos
Emulators/simulators may have no photos or only default images. For realistic testing:
Use physical devices with real photos
Pre-populate emulator/simulator with test images
Test with various image sizes and formats
Common Gallery Flows
Simple Image Upload
Multi-Image Post
Image Replacement
patrolTest ( 'uploads profile picture' , ($) async {
await $. pumpWidgetAndSettle ( const MyApp ());
await $. tap (#profileTab);
await $. tap (#editProfilePicture);
await $. tap (#chooseFromGallery);
if ( await $.platform.mobile. isPermissionDialogVisible ()) {
await $.platform.mobile. grantPermissionWhenInUse ();
}
await $.platform.mobile. pickImageFromGallery (index : 0 );
await $( 'Profile updated' ). waitUntilVisible ();
});
Finding Custom Selectors
If default selectors don’t work:
Open gallery manually
Run your app and manually open the gallery/photo picker.
Inspect elements
Use DevTools to inspect image thumbnails and confirm buttons to find their resource IDs or labels.
Use in your test
await $.platform.mobile. pickImageFromGallery (
imageSelector : NativeSelector (
android : AndroidSelector (
resourceName : 'found.resource.id' ,
),
),
);
Due to differences between devices and gallery apps, the default methods may not work on 100% of devices. Always test on your target devices and provide custom selectors when needed.
Troubleshooting
Emulator/simulator may not have photos
Add test images using adb (Android) or Photos app (iOS)
Use physical device with actual photos
Download sample images during test setup
Gallery layout may differ from expected
Use specific index instead of default (0)
Verify image order in gallery app
Consider using custom selectors with image attributes
Image not appearing in app
Verify app’s image handling code
Check that permissions are granted
Add $.pumpAndSettle() after picking
Ensure app processes image picker result
Test fails on different Android version
Android 13+ uses different photo picker
API 34+ changed resource IDs
Use OS version detection for conditional selectors:
final osVersion = await $.platform.mobile. getOsVersion ();
if (osVersion >= 34 ) {
// Use API 34+ selectors
}
For faster tests, consider mocking the image picker for most test cases. Only test actual gallery integration in critical end-to-end tests.