Skip to main content
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

Pick Single Image

The pickImageFromGallery() method performs these actions:
  1. Select an image (by default, the first image or the one at the provided index)
  2. Confirm selection (on some Android devices)

Pick Multiple Images

The pickMultipleImagesFromGallery() method:
  1. Selects multiple images (based on provided indexes)
  2. Confirms selection (taps “Add” or “Done” button)

Default Selectors

PlatformImage SelectorConfirm Button
Physical/Emulator Android (API < 34)com.google.android.documentsui:id/icon_thumbcom.google.android.documentsui:id/action_menu_select
Physical/Emulator Android (API 34+)com.google.android.providers.media.module:id/icon_thumbnailcom.google.android.providers.media.module:id/button_add
Simulator and Physical iOSIOSElementType.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.
1

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();
    }
  });
}
2

Pick the image

Use pickImageFromGallery() to select an image:
// Pick first image (index 0)
await $.platform.mobile.pickImageFromGallery(index: 0);
3

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();
    },
  );
}
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));
    },
  );
}

Platform-Specific Considerations

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',
)

Best Practices

Check for and grant photo/storage permissions:
if (await $.platform.mobile.isPermissionDialogVisible()) {
  await $.platform.mobile.grantPermissionWhenInUse();
}
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);
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));
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
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:
1

Install DevTools

2

Open gallery manually

Run your app and manually open the gallery/photo picker.
3

Inspect elements

Use DevTools to inspect image thumbnails and confirm buttons to find their resource IDs or labels.
4

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

  • 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
  • Verify app’s image handling code
  • Check that permissions are granted
  • Add $.pumpAndSettle() after picking
  • Ensure app processes image picker result
  • 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.

Build docs developers (and LLMs) love