ProductIntegrationsPricingFAQFeedbackX
All posts

Expo E2E Testing With an AI Coding Agent

Ike Studios LLC

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 traditional 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 app binary. The Expo dev build — the thing you actually run every day — has no first-class E2E path in that 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 real devices and virtual runtimes — against the Expo dev client you already run, with no manual 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.

Expo now also ships an official MCP for project guidance and EAS, with a bare-minimum set of local iOS Simulator controls. Ceraph provides the deeper app-testing loop; our Expo MCP comparison explains the overlap and where each fits.

Two things to say once and then set aside. Ceraph supports iOS and Android physical and virtual runtimes. At the driver layer it uses WebDriverAgent/XCUITest on iOS and UiAutomator2 on Android. Ceraph's claim is the agentic, Expo-native layer above those drivers. 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, including development-only camera and media testing when a flow needs it.

Bring-up is one call. ceraph_start takes the app from cold to ready-to-test, does only the work that is missing, and stops at the first blocked step 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, and 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:

  1. You edit code — add a screen to your Expo Router stack, fix a form, change a mutation.
  2. You ask your agent to test what changed.
  3. The agent drives the dev build — brings it up with ceraph_start, observes each screen with ceraph_snapshot, taps and types through the flow, and reports what passed and what looks off.
  4. It reads the runtime errors — Ceraph surfaces failures through rn_get_errors, so a redbox or 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.

  1. Bring the app up. The agent calls ceraph_start and waits for the selected runtime to reach a ready-to-test state.
  2. Jump to the route. Instead of tapping through the whole app to reach checkout, the agent calls screen_open_url with your deep link — myapp://checkout — and Expo Router lands on the new screen.
  3. See what's there. ceraph_snapshot returns 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 stable ref. No screenshot-pixel math, no LLM guesswork.
  4. Drive it. screen_type_into_field fills the address and card fields; screen_tap_and_verify taps "Place order" and asserts the confirmation screen appears.
  5. Read the result. If the mutation threw or the API returned a 500, rn_get_errors surfaces it in the same conversation — the agent reports the failure with the actual error, not a green checkmark it can't back up.
  6. Record the verified path. Once the flow actually works, ceraph_record_run turns it into a clean MP4 with structured UI snapshots and screenshots tracing the verified flow — review evidence under the ignored .ceraph/recorded-runs/ workspace rather than another test file to keep green.

If sign-in would otherwise block the checkout test, a project-owned hook can establish a disposable test persona or session so the agent can continue through the representative UI flow. The whole loop runs on your selected local runtime, on your own infrastructure.

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 client or prebuilt app, not Expo Go; a snapshot, not a plugin.

Try it on your own app — get started.