Skip to main content
Testing camera functionality is essential for apps that capture photos or videos. Patrol provides native automation to interact with the device camera, take photos, and verify the captured images are properly handled by your app.

Overview

Patrol enables camera testing by:
  • Opening the native camera app
  • Taking photos using device camera
  • Handling camera permissions
  • Using custom selectors for different devices
  • Testing both Android and iOS camera flows

Key Methods

takeCameraPhoto

Automatically tap shutter and confirm button to take a photo

grantPermissionWhenInUse

Grant camera permission when requested

Custom Selectors

Provide custom selectors for unsupported devices

How takeCameraPhoto Works

The takeCameraPhoto() method performs two actions:
  1. Tap on shutter button - Takes the photo
  2. Tap on confirm button - Confirms and saves the photo

Default Selectors

Patrol uses different native selectors depending on the platform:
PlatformShutter ButtonConfirm Button
Physical Androidcom.google.android.GoogleCamera:id/shutter_buttoncom.google.android.GoogleCamera:id/done_button
Emulator Androidcom.android.camera2:id/shutter_buttoncom.android.camera2:id/done_button
Simulator and Physical iOSPhotoCaptureDone
The default selectors work without customization on iOS devices and most Android emulators and Pixel physical devices. For other Android devices, you may need to provide custom selectors.

Basic Camera Testing

1

Setup camera permissions

Request and grant camera permission:
import 'package:patrol/patrol.dart';

void main() {
  patrolTest('takes a photo', ($) async {
    await $.pumpWidgetAndSettle(const MyApp());
    
    // Tap button in your app that opens camera
    await $.tap(#openCameraButton);
    
    // Grant camera permission if requested
    if (await $.platform.mobile.isPermissionDialogVisible()) {
      await $.platform.mobile.grantPermissionWhenInUse();
    }
  });
}
2

Take the photo

Use takeCameraPhoto() to take a photo:
// Take photo with default selectors
await $.platform.mobile.takeCameraPhoto();
3

Verify in your app

Verify that your app received and processed the photo:
// Verify photo appears in your app
await $('Photo captured successfully').waitUntilVisible();

// Or verify image widget exists
expect($(Image).exists, true);

Complete Examples

Basic Camera Test

Simple test using default selectors:
patrol_test/camera_basic_test.dart
import 'package:patrol/patrol.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  patrolTest(
    'takes a photo using default camera selectors',
    ($) async {
      await $.pumpWidgetAndSettle(const MyApp());
      
      // Open camera from your app
      await $.tap(#addPhotoButton);
      
      // Grant permission if needed
      if (await $.platform.mobile.isPermissionDialogVisible()) {
        await $.platform.mobile.grantPermissionWhenInUse();
      }
      
      // Take photo using default selectors
      await $.platform.mobile.takeCameraPhoto();
      
      // Verify photo was captured
      await $('Photo saved').waitUntilVisible();
      expect($(Image).exists, true);
    },
  );
}

Camera Test with Custom Selectors

For devices that don’t work with default selectors:
patrol_test/camera_custom_test.dart
import 'package:patrol/patrol.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  patrolTest(
    'takes a photo with custom selectors',
    ($) async {
      await $.pumpWidgetAndSettle(const MyApp());
      
      await $.tap(#addPhotoButton);
      
      if (await $.platform.mobile.isPermissionDialogVisible()) {
        await $.platform.mobile.grantPermissionWhenInUse();
      }
      
      // Use custom selectors for specific device
      await $.platform.mobile.takeCameraPhoto(
        shutterButtonSelector: NativeSelector(
          android: AndroidSelector(
            resourceName: 'com.oplus.camera:id/shutter_button',
          ),
          ios: IOSSelector(label: 'Take Picture'),
        ),
        doneButtonSelector: NativeSelector(
          android: AndroidSelector(
            resourceName: 'com.oplus.camera:id/done_button',
          ),
          ios: IOSSelector(label: 'Done'),
        ),
      );
      
      await $('Photo saved').waitUntilVisible();
    },
  );
}

Multiple Photos Test

Test taking multiple photos in sequence:
patrol_test/camera_multiple_test.dart
import 'package:patrol/patrol.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  patrolTest(
    'takes multiple photos',
    ($) async {
      await $.pumpWidgetAndSettle(const MyApp());
      
      // Grant permission once
      await $.tap(#addPhotoButton);
      if (await $.platform.mobile.isPermissionDialogVisible()) {
        await $.platform.mobile.grantPermissionWhenInUse();
      }
      
      // Take first photo
      await $.platform.mobile.takeCameraPhoto();
      await $.pumpAndSettle();
      
      // Verify first photo
      expect($(#photoCount).text, '1');
      
      // Take second photo
      await $.tap(#addPhotoButton);
      await $.platform.mobile.takeCameraPhoto();
      await $.pumpAndSettle();
      
      // Verify second photo
      expect($(#photoCount).text, '2');
      
      // Verify both photos are displayed
      expect($(Image).count, equals(2));
    },
  );
}

Camera with Timeout

Handle slow camera loading:
patrol_test/camera_timeout_test.dart
import 'package:patrol/patrol.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  patrolTest(
    'takes photo with custom timeout',
    ($) async {
      await $.pumpWidgetAndSettle(const MyApp());
      
      await $.tap(#addPhotoButton);
      
      if (await $.platform.mobile.isPermissionDialogVisible()) {
        await $.platform.mobile.grantPermissionWhenInUse();
      }
      
      // Wait longer for camera to initialize
      await Future<void>.delayed(const Duration(seconds: 3));
      
      // Take photo with custom timeout
      await $.platform.mobile.takeCameraPhoto(
        timeout: Duration(seconds: 15),
      );
      
      await $('Photo saved').waitUntilVisible();
    },
  );
}

Finding Custom Selectors

If the default selectors don’t work on your device, use the Patrol DevTools Extension:
1

Install DevTools Extension

Install the Patrol DevTools Extension for your IDE.
2

Open camera manually

Run your app and manually open the camera.
3

Inspect native elements

Use DevTools to inspect the shutter button and done button to find their resource IDs or labels.
4

Use in your test

Use the found selectors in your test:
await $.platform.mobile.takeCameraPhoto(
  shutterButtonSelector: NativeSelector(
    android: AndroidSelector(
      resourceName: 'your.camera.app:id/capture_button',
    ),
  ),
);

Platform-Specific Behavior

Android Camera Apps

Different Android devices use different camera apps:
  • Google Camera (Pixel devices)
  • Samsung Camera
  • OnePlus Camera
  • Generic Camera2 API app
Emulator vs Physical Device:
  • Emulators typically use com.android.camera2 camera app
  • Physical devices vary by manufacturer
// Common Android camera selectors

// Google Camera (Pixel)
AndroidSelector(
  resourceName: 'com.google.android.GoogleCamera:id/shutter_button',
)

// Generic Camera2
AndroidSelector(
  resourceName: 'com.android.camera2:id/shutter_button',
)

// Samsung Camera
AndroidSelector(
  resourceName: 'com.sec.android.app.camera:id/shutter_button',
)

Best Practices

Check for and grant camera permissions before taking photos:
if (await $.platform.mobile.isPermissionDialogVisible()) {
  await $.platform.mobile.grantPermissionWhenInUse();
}
Camera apps need time to initialize:
await $.tap(#openCamera);

// Wait for camera to initialize
await Future<void>.delayed(const Duration(seconds: 2));

await $.platform.mobile.takeCameraPhoto();
Camera behavior varies significantly by device. Test on:
  • Your target physical devices
  • Both Android emulators and iOS simulators
  • Different OS versions
Camera operations can be slow. Use generous timeouts:
await $.platform.mobile.takeCameraPhoto(
  timeout: Duration(seconds: 15),
);

Common Camera Flows

patrolTest('captures photo', ($) async {
  await $.pumpWidgetAndSettle(const MyApp());
  await $.tap(#cameraButton);
  
  if (await $.platform.mobile.isPermissionDialogVisible()) {
    await $.platform.mobile.grantPermissionWhenInUse();
  }
  
  await $.platform.mobile.takeCameraPhoto();
  await $('Photo saved').waitUntilVisible();
});
Due to device differences, takeCameraPhoto() may not work on 100% of devices. Always test on your target devices and provide custom selectors when needed.

Troubleshooting

  • Verify camera permission is granted
  • Check if device has a working camera (emulators need camera emulation enabled)
  • Ensure your app correctly implements camera opening
  • Try adding a delay after tapping the camera button
  • Device may use a different camera app
  • Use Patrol DevTools to find the correct selector
  • Provide custom shutterButtonSelector
  • Verify camera has fully loaded before taking photo
  • Verify your app’s image handling code
  • Check that done/confirm button was tapped
  • Ensure app has permission to save photos
  • Add $.pumpAndSettle() after taking photo
  • Emulators may have unreliable camera emulation
  • Increase timeouts for camera initialization
  • Consider mocking camera for unit tests
  • Use physical devices for integration tests
For faster and more reliable tests, consider mocking the camera functionality for most test cases, and only test actual camera integration on a subset of critical tests.

Build docs developers (and LLMs) love