# Test Expo Camera on the iOS Simulator and Android Emulator With Your AI Agent

> The iOS Simulator can't take a photo, so camera features go untested. With Ceraph, your AI agent chooses the image or video Expo Camera returns, so it can test the flow end-to-end.

- Author: Ike Studios LLC
- Published: 2026-10-08
- Canonical: https://ceraph.dev/blog/test-camera-ios-simulator-android-emulator

---

The iOS Simulator has no camera. [Expo's own Simulator guide](https://docs.expo.dev/workflow/ios-simulator/) lists the camera among the hardware the Simulator can't provide. The [Expo Camera reference](https://docs.expo.dev/versions/latest/sdk/camera/) marks the component as device-only on both iOS and Android. The Android Emulator does expose a camera, but it's an [emulated scene, a virtual scene, or your host webcam](https://developer.android.com/studio/run/managing-avds), and it supports [fixed PNG or JPEG images in a virtual scene](https://developer.android.com/studio/run/emulator-use-camera). Webcam input and changes to the scene or camera position can make captures vary.

That's a problem for any feature built around a capture: scanning a receipt for OCR, uploading an ID or a profile photo, identifying a plant from a picture, recording a workout clip. Testing those flows needs a deliberate input: a particular receipt, ID, portrait, plant photo, or clip. The iOS Simulator needs a camera substitute, and an Android virtual scene needs the image and camera position configured for the scenario.

Ceraph makes the camera deterministic in development builds. Ceraph is an [MCP](https://modelcontextprotocol.io) server that lets your coding agent drive and test a React Native or Expo app end-to-end. You register a test image or video, and the capture call returns that exact file on the iOS Simulator, the Android Emulator, or a device. Your agent can test the full camera flow with the same input every run, and see what your app does with it.

## How Ceraph mocks the camera in React Native

Ceraph's camera support is built for **Expo Camera** (`expo-camera`). You use `CeraphCamera` from `@ceraph/react-native-mcp/shim` in place of `CameraView`. It accepts the same props as Expo Camera, and `ceraph init` offers to replace the `CameraView` components it finds for you.

Each camera gets a `mediaKey` that names its scenario:

```tsx
import { CeraphCamera } from "@ceraph/react-native-mcp/shim";

export function ScanReceiptScreen() {
  return <CeraphCamera mediaKey="receipt" style={{ flex: 1 }} ref={cameraRef} />;
}
```

In a development build:

- **`takePictureAsync`** returns the image registered for that key.
- **`recordAsync`** returns the video registered for that key.
- One key can have both, for screens that support photo and video capture.

The fixtures are ordinary files in your project, stored under a name that matches the key:

```text
.ceraph/camera-images/receipt.jpg
.ceraph/camera-videos/workout.mp4
```

Your agent adds them itself with `ceraph_add_camera_image` and `ceraph_add_camera_video`. Images can be JPEG, PNG, WebP, or HEIC up to 5 MB. Videos can be MP4, MOV, or M4V up to 25 MB. If a key has no image fixture, development capture returns the project's `default` image, or Ceraph's built-in placeholder if there isn't one. If it has no video fixture, recording uses the project's `default` video, so register a clip for any screen that records. Because fixtures live in the repo and Ceraph keeps only its generated files out of git, you can commit them so every teammate's agent tests against the same inputs.

## Simulators, emulators, and physical devices

Defaults follow the runtime. **iOS Simulators and Android Emulators use test media**, so captures use the project's registered fixtures. **Physical devices use the real camera**, so a phone in your hand still works as a phone.

Your agent can change this on a running development app with `ceraph_set_camera_test_media` without rebuilding. For example, it can turn test media on for a physical iPhone so a scan flow is reproducible on real hardware, then turn it off again. `CERAPH_CAMERA_TEST_MEDIA=on` or `off` fixes the choice and takes precedence over runtime switching; `auto` keeps runtime switching available. The `cameraTestMedia` preference saves your choice.

**Production builds always use the real camera.** Camera fixtures and test behavior are development-only and are left out of production builds.

## Testing photo library pickers

Many camera features also let the user choose an existing photo instead. That flow goes through the system picker, which needs something in the library to pick, so Ceraph's `ceraph_add_media` seeds the runtime's photo library and the agent drives your app's real picker. The full walkthrough, including the manual `simctl`/`adb` route, is in [testing image picker and photo library flows](/blog/test-image-picker-photo-library-react-native).

## Walkthrough: testing a receipt scanner

Say your Expo app has a receipt scanner. The user photographs a receipt, your backend runs OCR, and the next screen shows the parsed total. You've changed how totals are parsed, and you ask your agent to test the scan flow on the iOS Simulator.

1. **Register the input.** The agent calls `ceraph_add_camera_image` with a real receipt photo under the key `receipt`. The expected total is printed right on it.
2. **Bring the app up.** `ceraph_start` gets the development build running on the Simulator and ready to test.
3. **Go to the scanner.** The agent reads the screen with `ceraph_snapshot`, then taps through to the scan screen or opens it with `screen_open_url`.
4. **Capture.** The agent taps the shutter. `takePictureAsync` returns `receipt.jpg`, your upload and OCR code run on it, and the app moves to the result screen.
5. **Verify the result.** The agent takes another snapshot and checks that the parsed total matches the receipt. Since the input is the same every run, a wrong total is a real regression.
6. **Read the errors.** If the upload failed or the parser threw, `rn_get_errors` puts the error in the conversation. The agent reports the actual failure and doesn't call the flow passed.

The same pattern covers other capture flows: a profile photo that should be cropped and uploaded, a plant photo that should return the right species, or a recorded clip that `recordAsync` should hand to your rep counter.

## Where camera testing fits

Camera and gallery testing fits the same agent loop as the rest of Ceraph: start the app, read the screen, act, read it again, check the errors. More on that loop in [the Playwright MCP for React Native](/blog/the-playwright-mcp-for-react-native).

Try it on your camera flow: [get started](/docs/get-started).
