Expo E2E Testing With an AI Coding Agent
If you build with Expo and you've tried to add end-to-end (E2E) tests, you already know the shape of the problem: the frameworks that dominate React Native testing treat Expo as an afterthought. You reach for Detox and its own docs tell you Expo is somebody else's problem. You reach for Appium and it can't tell an Expo app from any other iOS binary. The Expo dev build — the thing you actually run every day — has no first-class E2E path in the incumbent stack.
Ceraph starts there. It's an MCP server that lets your AI coding agent drive and test an Expo app end-to-end on a real iOS device or the iOS Simulator — against the Expo dev client you already run, with no expo prebuild step and no eject. Your agent brings the app up, reasons over a deterministic snapshot of the screen, taps and types through the flow, and reads back any runtime error. Expo is the build target, not a compatibility caveat.
Two things to say once and then set aside. Ceraph is iOS-first: a real iPhone over USB or the iOS Simulator, with Android on the roadmap. And at the driver layer it builds on WebDriverAgent, the same XCUITest server Appium's iOS driver uses. Ceraph doesn't claim a better driver. It claims a better layer above it — the agentic, Expo-native loop. Everything below is about that layer.
Why Expo E2E has been a second-class citizen
Start with Detox, the framework most React Native teams try first. Detox has no first-party Expo support: its own guide states plainly that "Expo support with Detox is entirely a community driven effort. We have no specific support in Detox for Expo applications (ejected or otherwise)." To run it you leave the managed workflow — expo prebuild to generate the native projects, then wire in a community config plugin — and you maintain that scaffolding yourself.
That path got thinner in 2025. Expo removed the Detox config plugin from its officially maintained @config-plugins set: the PR, merged September 15, 2025, drops the Detox references with the maintainer's note, "We no longer support these config plugins." A community fork lives on, but the first-party plugin is gone.
Appium is the other reflex, and it has no Expo-aware path at all. Appium drives whatever binary is installed as a generic native app — it has no concept of an Expo dev client, of Metro, or of a dev-build deep link. You can point it at a prebuilt or ejected app and write scripts against it, but "Expo support" isn't something Appium offers; it automates the compiled result like any other iOS app.
So the honest state of Expo E2E in the incumbent stack: Detox pushes you out of the managed workflow and off the officially-supported plugin, and Appium doesn't model Expo at all.
Maestro supports Expo — but you maintain every flow
Maestro is the one incumbent that supports Expo — Expo Go, dev builds, and EAS, no extra packages. But supporting Expo and being built for the agentic Expo loop aren't the same thing: Maestro still hands your agent a YAML flow for every test to author and keep green as the UI moves — each one a file in your repo, and they pile up. Its MCP makes those files cheaper to write; it doesn't remove them. Ceraph hands the agent a deterministic snapshot to reason over instead: no flows to write, nothing to heal, because there's no script. That axis — snapshot versus YAML — holds regardless of who supports Expo. (More in the full comparison.)
First-class Expo in Ceraph
Ceraph targets the Expo build you already run. Point it at an Expo dev client or a prebuilt app — Expo Go isn't supported — and there's no test-framework config plugin to add and no native test harness to wire in. init configures your agent; the camera and media shim, when a flow needs it, is a subpath import.
Bring-up is one call. ceraph_start takes the app to ready-to-test: it starts Metro, builds and installs, foregrounds the app, and starts WebDriverAgent — running only what's missing and stopping at the first failed gate with a concrete fix. From there your agent drives the running dev build: ceraph_snapshot to see the screen, a screen_* primitive to act, screen_open_url to jump straight to a deep-linked route, rn_get_errors to read anything the app threw.
The agentic loop on Expo
The loop on an Expo app is the same tight motion that makes browser testing with Playwright reliable:
- You edit code — add a screen to your Expo Router stack, fix a form, change a mutation.
- You ask your agent to test what changed.
- The agent drives the dev build — brings it up with
ceraph_start, observes each screen withceraph_snapshot, taps and types through the flow, and reports what passed and what looks off. - It reads the runtime errors — a
__DEV__shim streams JS exceptions, unhandled promise rejections, network failures, andconsole.errorback throughrn_get_errors, so a redbox or a silent crash lands in the conversation instead of getting missed.
Observe, act, observe — the way a person testing by hand works. When the layout shifts, the next snapshot simply reflects it; there's no selector file to update.
A walkthrough: an Expo Router checkout flow
Say you've added a checkout screen to an Expo Router app — a new route at app/checkout.tsx, a form, and a "Place order" button that calls your API. You ask your agent: test the checkout flow.
- Bring the app up. The agent calls
ceraph_start. Metro comes up, the dev client builds and installs on your simulator or connected iPhone, WebDriverAgent starts, and the on-device verify pass confirms it can actually drive. - Jump to the route. Instead of tapping through the whole app to reach checkout, the agent calls
screen_open_urlwith your deep link —myapp://checkout— and Expo Router lands on the new screen. - See what's there.
ceraph_snapshotreturns the screen as structured elements — the address fields, the card input, the "Place order" button — each with its role, name, on-screen bounds, and a stableref. No screenshot-pixel math, no LLM guesswork. - Drive it.
screen_type_into_fieldfills the address and card fields;screen_tap_and_verifytaps "Place order" and asserts the confirmation screen appears. - Read the result. If the mutation threw or the API returned a 500,
rn_get_errorssurfaces it in the same conversation — the agent reports the failure with the actual error, not a green checkmark it can't back up.
If checkout sits behind sign-in, the agent provisions a managed test user with ceraph_test_data_provision against your auth provider and drives past the wall. The whole loop runs where you already are: your connected iPhone or a booted simulator, on your own tokens.
Where this leaves you
For an Expo team whose coding agent already writes the feature, testing it should be the same motion the agent already knows — bring up the dev build, look at the screen, act, look again, read the errors. That's what Ceraph makes first-class on Expo: the dev client you already run, driven in the agent loop, with a deterministic snapshot to reason over instead of a script to keep green. It needs a dev build, not a prebuild; a snapshot, not a plugin.
Try it on your own app — get started.