Quick answer
A GitHub Actions workflow can start an Expo Simulators session on every pull request. The job signs in with an EXPO_TOKEN secret from a robot user, builds the app for the iOS Simulator with EAS Build, and starts a session. It drives the app through eas simulator:exec with a controller: agent-device in this example, or Argent or Appium. It uploads the screenshots as workflow artifacts, and stops the session in a step that always runs.
The iOS Simulator runs on EAS, so the CI machine does not need macOS or a local simulator.
What do you need first?
- An app config that contains the EAS project ID (
extra.eas.projectId). Commands that run with an access token fail without it. - An Expo account with access to Expo Simulators.
- A build profile in
eas.jsonfor the iOS Simulator:
iOS Simulator builds are not signed, so EAS Build does not ask for Apple credentials.
Add EXPO_TOKEN to GitHub
CI jobs cannot use eas login. EAS CLI reads an access token from the EXPO_TOKEN environment variable.
- Create a robot user on the Expo account that owns the project. Give it the Developer role or higher. Developer is the lowest role that can create and stop sessions.
- Create an access token for the robot user.
- Add the token to the repository as a secret named
EXPO_TOKEN, in Settings > Secrets and variables > Actions, or with the GitHub CLI:
Add the workflow
Save this file as .github/workflows/expo-simulators.yml. Replace com.example.app with the ios.bundleIdentifier from npx expo config --json.
What each step does
- Concurrency: a new push cancels the older run. If that run started a session, its stop step still runs.
- Shell:
shell: bashaddspipefail, so a failed command in a pipe fails the step. - Check access:
simulator:availabilityexits with code 0 even without access.jq -efails the step whenavailableisfalse. The runner includesjq. - Build:
eas buildwaits for the build and prints it as JSON, andjqreads the build ID. The build contains the pull request's JavaScript. - Start:
--non-interactivereturns when the session is ready. The session keeps running, and--max-duration-minutesstops it if the stop step never runs. - Drive: the start command writes
.env.eas-simulatorto the project directory. Steps share the workspace, sosimulator:execreads the file in later steps. agent-device needs an open app before a screenshot, and it downloads each file to the runner. Replace the scroll with your flow, such assnapshot -i,press, andfill. - Stop:
if: always()runs the step after a failure or a cancel. Thesteps.start.outcomecheck skips it when the start step did not run.
Use Argent or Appium instead
Each session gets the controller for its session type. Change --type in the start step, then change the commands in the drive step:
- Argent: start with
--type argent, install the Argent CLI (npm install --global @swmansion/argent), and run its tools witheas simulator:exec argent run <tool>. - Appium: start with
--type appium, and run your Appium client or test suite througheas simulator:exec. It loadsAPPIUM_URLandAPPIUM_CAPS, so your client reads the server URL and capabilities from them.
Keep tokens out of the logs
GitHub redacts secrets such as EXPO_TOKEN from logs. The session tokens that the start command creates are not GitHub secrets, so GitHub does not redact them.
- Do not print or upload
.env.eas-simulator. It holds the controller tokens. Upload only theartifactsfolder. - Do not print
--jsonoutput fromsimulator:startorsimulator:get. It includes connection details, andsimulator:getalso lists artifact download URLs. - Add
.env.eas-simulatorto.gitignore.
Other ways to get a build
- To reuse a build when native code has not changed, pass
--build-fingerprintwith the hash fromeas fingerprint:generate. That build can contain older JavaScript, because the fingerprint covers only native code. - In EAS Workflows, the
get-buildjob can find an iOS Simulator build, and abuildjob can create one. Pass the build to a later step that starts the session. - For Android, build an APK and use
--platform android. Android support is still in development, so some features that work on iOS may not work on Android yet.
Let an agent set it up
If an agent writes or changes the workflow for you, have it load these Expo Skills first:
expo-overview: the entry point that routes the agent to the right Expo skill.eas-simulator: the currenteas simulatorcommands, session types, and cleanup steps.eas-workflows: EAS workflow YAML, if you move the job to EAS Workflows.
Install them:
- Claude Code:
claude plugin install expo@claude-plugins-official - Codex:
codex plugin add expo@openai-curated - Cursor and other agents:
npx skills add expo/skills
The full list is at docs.expo.dev/skills.
Try it
Expo Simulators is in a limited beta release. The docs on running sessions in CI also cover the REST API and parallel sessions.
Verified on 20 October 2026.