Camera
Feed an image or video into the app's real camera on iOS simulators and Android emulators - preview, photo capture, video recording and QR scanning, in specs, suites and by hand.
Specs, suites and warden ui feed a file into the app's real camera API, so the app's own preview, photo capture, video recording and QR scanning run against a known picture. There is no app-side bypass. Simulators and emulators only: a physical device fails with E_UNSUPPORTED.
Accepted files: png, jpg, heic (image), mp4, mov (video). warden reads the leading bytes, not the extension. Anything else is E_CAMERA_MEDIA, and a missing file is E_CAMERA_NOT_FOUND.
In a spec
import { expect, test } from "@delacour/warden-e2e";
test("scans the ticket", async ({ app, device, screen }) => {
await device.camera.set("fixtures/qr-hello.png");
await app.open();
await expect(screen.getByTestId("scan-result")).toHaveText("hello");
await device.camera.set("fixtures/qr-bye.png", { facing: "both" });
});| Member | Does |
|---|---|
device.camera.set(path, { facing?, loop? }) | path is relative to the spec file. facing: "back" (default), "front" or "both"; the other facing keeps its feed. loop (video, default true). Returns { feeds, appliesOnLaunch?, appliesOnCameraOpen?, rebooted? } |
device.camera.clear(facing?) | takes the feed off one facing, or both (default) |
device.camera.status() | { feeds } the spec has in place |
The camera goes back to the suite's camera (or none) when the test ends. Each change is a condition line in results.jsonl.
In a suite
export default defineConfig({
e2e: {
scan: { camera: { back: "e2e/fixtures/qr-hello.png", front: { path: "e2e/fixtures/face.mp4", loop: true } } },
},
});Paths are relative to warden.config.ts. The feeds apply before each spec's first app.open(). On Android, the suite's legs claim emulators with --camera.
By hand
warden ui camera set fixtures/qr-hello.png # back camera
warden ui camera set clip.mp4 --facing both --no-loop
warden ui camera status
warden ui camera clearThe feed is recorded on your lease and undone on release. With --json the answer is result.camera.
iOS
The app is launched with warden's injected library, which serves the feeds through the real AVFoundation and UIKit API: device discovery, the preview layer, photo output, movie recording, video data output, QR metadata output and the image picker's camera.
- A feed set while the app runs swaps live. Set before launch, it applies on the next
app.open()(appliesOnLaunch). - QR is the only code type decoded.
- Camera permission stays real.
simctl privacyhas no camera service, so the app's prompt shows: answer it withdevice.alert("accept")(turnsystemAlertsoff for the spec so it is not dismissed).
Android
The emulator's cameras read files fixed at boot, so only a --camera emulator can be fed: warden claim android --camera, warden dev android --camera, or a suite with camera. These emulators have their own profile (<profile>+cam) and cold boot. They cannot be combined with --biometrics yet. Feeding any other emulator fails with E_CAMERA_DEVICE.
- An image shows on the app's next camera open (
appliesOnCameraOpen). An open camera keeps its frames, so close and reopen it (or flip facing). - A video feed, or switching between image and video, reboots the emulator (
rebooted). - Grant the permission up front with
device.permissions.set({ camera: "granted" }). - Frame the subject for the preview, not the file. The emulator fills the app's preview from the image and crops the rest, so a code near an edge can fall outside it, and a scanner only reads what reaches the app. With expo-camera on a Pixel emulator, a landscape preview shows about the left half of a 4:3 image: keep a QR code there, with a white margin. iOS passes the whole frame.
Errors and fixes are in Debugging.