This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Run Expo Simulators sessions in CI
Edit page
Start, use, and always stop Expo Simulators sessions from GitHub Actions, EAS Workflows, or another CI system with an Expo access token.
Expo Simulators is a limited-access preview. It is not included with paid or free plans and is currently available only to select partners. Join the waitlist if you are interested in trying it.
A CI job can start an Expo Simulators session, install a build, run checks against the remote device, and stop the session. The job authenticates with an Expo access token and uses EAS CLI or the REST API. The CI machine does not need macOS or a local simulator.
Set up authentication
Create a robot user on the account that owns the project. Give it the Developer role or higher. Developer is the minimum role that can create and stop sessions. Store its access token in the secret store of your CI system as EXPO_TOKEN.
CI jobs cannot use eas login. EAS CLI reads EXPO_TOKEN from the environment.
Write the job
A CI job follows these steps:
- Check access with
eas simulator:availability --json, and stop the job ifavailableisfalse. - Find or create an Android APK or an iOS Simulator build.
- Start the session with the build and a maximum duration.
- Run your checks through
eas simulator:exec. - Stop the session in a step that runs even when an earlier step fails.
This example shows the commands for a shell step. Add them to the job format of your CI system. The access check requires jq:
simulator:availability exits with code 0 even when Expo Simulators is not available for the account. jq -e exits with a non-zero code when available is false, so the job stops before it starts a session.
Then add a cleanup step that always runs:
The start command needs a project directory with an app config that contains the EAS project ID. Check out the repository before you run it.
A CI job that ends does not stop the session. If the cleanup step does not run, the session continues until it reaches its maximum duration. Always set
--max-duration-minutesin CI.
Use the REST API
When the CI job does not have Node.js or the project source, use the REST API with curl or another HTTP client:
POST /v2/device-run-sessionswithappId,platform,type, an application source, andmaxDurationMinutes.- Poll
GET /v2/device-run-sessions/:idevery five seconds untildata.statusisin-progressanddata.remoteConfigis notnull. Set a timeout. - Use the values in
data.remoteConfigto connect your controller. POST /v2/device-run-sessions/:id/stopin a cleanup handler, also after errors and timeouts.
Get a build in EAS Workflows
In EAS Workflows, the pre-packaged get-build job can find an existing iOS Simulator build that matches a filter, and a build job can create one. Pass the build to a later step that starts the session. See Pre-packaged jobs.
For an example of agent-driven testing on pull requests, see Callstack's agent-device EAS Workflows template. It runs its own emulator and simulator in EAS Workflows jobs, not Expo Simulators sessions.
Run end-to-end tests
- To run e2e tests on Expo Simulators, use the
@e2e-dev/easintegration. It starts one session for each e2e worker and stops the sessions when the run ends. - To run an existing Appium suite, start an
appiumsession. See Run Appium tests on a remote Android Emulator or iOS Simulator. - To run Maestro flows, see Use Maestro flows with Expo Simulators.
Keep secrets out of CI logs
The start command writes controller tokens to .env.eas-simulator, and --json output includes connection details. Do not print the file, and do not upload it as a CI artifact. See Expo Simulators security, permissions, and data retention.
Common questions
Do sessions in CI wait in a queue?
Yes, a session can wait for available concurrency before it starts. EAS CLI shows when a session is in the queue, and queue time does not count toward the 15-minute startup timeout. See Expo Simulators usage, limits, and billing.
Can I run sessions in parallel?
Yes. Start each session with its own name and tags. simulator:exec passes your shell environment to the command. When the .env.eas-simulator file in the project directory contains EAS_SIMULATOR_SESSION_ID, its values replace the exported connection variables. The file holds one session, so use a separate copy of the project directory for each parallel session. simulator:get, simulator:stop, and simulator:events also accept --id <session-id>.
With --out-config-type env, the start command prints shell exports instead of writing .env.eas-simulator. Run the printed export lines in the shell of the job before you run controller commands. The exports do not include the session ID, so pass --id to get, stop, and events. A leftover .env.eas-simulator file in the project directory replaces these exports. Delete the file, or run the job in a separate directory. See Run parallel sessions.