> ## Documentation Index
> Fetch the complete documentation index at: https://e2e.tester.army/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Expo Simulators

> Run mobile targets on Expo Simulators' hosted iOS simulators and Android emulators with the expoSimulators() device provider from @e2e-dev/eas.

`expoSimulators()` from `@e2e-dev/eas` is a
[device provider](https://e2e.tester.army/docs/mobile#hosted-devices) for
[Expo Simulators](https://expo.dev/services/simulators): hosted iOS
simulators and Android emulators. The engine drives each one through the
agent-device daemon that Expo runs beside it, so the machine that runs e2e
needs no Mac, Xcode, or Android SDK.

<Note>
To run e2e in Expo's CI service instead, see [Expo Workflows](https://e2e.tester.army/docs/ci/eas-workflows).
</Note>

## Setup

Expo Simulators is in limited access. To check yours, run
`npx eas-cli@latest simulator:availability`. Then install the provider. It
needs no SDK.

<CodeGroup>
```bash npm
npm install --save-dev @e2e-dev/eas
```

```bash pnpm
pnpm add -D @e2e-dev/eas
```

```bash bun
bun add -d @e2e-dev/eas
```
</CodeGroup>

Set the target's `device` to `expoSimulators()`:

```ts title="e2e.config.ts"

export default {
  targets: [
    {
      name: 'ios',
      engine: mobile({
        platform: 'ios',
        device: expoSimulators({ buildId: process.env.EAS_BUILD_ID }),
        videoTouches: false,
      }),
      app: { bundleId: 'com.example.app' },
    },
  ],
  workers: 2,
} satisfies E2EConfig;
```

<Note>
`expoSimulators()` used to be called `easSimulators()`. The old name still
works but is deprecated.
</Note>

## Link the Expo project

The provider reads the project id from `extra.eas.projectId` in the app
config beside `e2e.config.ts`, which `eas init` writes. If the app config is
elsewhere or links no project, pass `projectId`: the id, not the slug.

<Accordion title="How the provider reads the app config">
- **`app.json` or `app.config.json`**: read as JSON, from under `expo` when
  the file has that key.
- **A dynamic config** (`app.config.ts`, `.js`, `.mjs`, `.cjs`, `.mts`, or
  `.cts`): evaluated with the project's own `expo config --type public`, in
  the run's environment, because the config can compute the id. It needs
  `expo` installed in the project. As with eas-cli, `.env` files don't load
  (`EXPO_NO_DOTENV=1`). A variable the config reads must be in the
  environment that `e2e run` starts in.

A config that links no project fails the lease before any session starts,
naming the file. An `@e2e-dev/mobile` too old to pass `projectRoot` to
providers fails the lease too: upgrade it or pass `projectId`.
</Accordion>

## Authenticate

The provider authenticates the way eas-cli does:

- **`EXPO_TOKEN`**: an [access token](https://expo.dev/settings/access-tokens)
  of an account or robot with access to the project, in the environment
  that `e2e run` starts in. It wins over a login.
- **The eas-cli login** from `eas login`, which eas-cli keeps in
  `~/.expo/state.json` (`USERPROFILE` on Windows).

Sessions always go to production `api.expo.dev`, so eas-cli's
`EXPO_STAGING` and `EXPO_LOCAL` logins don't count.

<Warning>
On a CI runner or a shared machine, set `EXPO_TOKEN`. Without it, a login
that someone left in the runner's home starts billed sessions as their
account.
</Warning>

## Install the app

The simulator boots with no apps. Choose one way to put yours on it:

| Way | `expoSimulators()` | Target's `app` |
| --- | --- | --- |
| A build from Expo Build | `buildId`: a simulator build (`"ios": { "simulator": true }` in `eas.json`) or an APK. Expo installs and launches it before the session is ready. The build never leaves Expo. | `bundleId`, so `app.open()` knows what to launch. No `appPath`, or the lease fails. |
| A URL | `applicationArchiveUrl`: an app archive that Expo downloads and installs the same way. | `bundleId`, no `appPath`. |
| A local build | Neither option. | `bundleId`, `appPath`, and a fixture that calls `device.installApp()`, as with a [local simulator](https://e2e.tester.army/docs/mobile). The build uploads through the session's daemon to each simulator. |

## Options

| Option                  | Default     | Meaning |
| ----------------------- | ----------- | ------- |
| `projectId`             | the app config's | See [Link the Expo project](#link-the-expo-project). |
| `buildId`               | none        | See [Install the app](#install-the-app). |
| `applicationArchiveUrl` | none        | See [Install the app](#install-the-app). |
| `device`                | Expo's pick | iOS device name or UDID (`'iPhone 17 Pro'`), or an Android AVD hardware profile (`'pixel_9'`). |
| `maxIdleTimeMinutes`    | `10`        | Minutes without an agent-device command before Expo stops the session, which also stops the sessions of a run that died. A shorter `maxDurationMinutes` lowers the default to one less. |
| `maxDurationMinutes`    | account cap | Minutes a session can run after it is ready. Expo caps it at 40, or 115 on a high-priority plan. |
| `agentDeviceVersion`    | the engine's | The agent-device version of the session's daemon. The default matches the client that `@e2e-dev/mobile` pins. |
| `tags`                  | `[]`        | Tags on every session, beside the run's own. |

An unknown option, `buildId` with `applicationArchiveUrl`, or a
`maxIdleTimeMinutes` not below a `maxDurationMinutes` passed with it fails
the config load
with `INVALID_CONFIG`. Both minute limits must be non-negative integers.

## Sessions

Each target starts up to `workers` sessions, fewer when it has fewer test
files. They start in parallel before the run's clock starts, and take about
three minutes each (queue, boot, build install). Tests start when all are
ready.

A run must fit in one session, because Expo stops a session
`maxDurationMinutes` after it is ready. Split a longer suite across more
workers or CI jobs.

Expo queues sessions beyond the account's concurrency limit. No session ends
before the run does, so keep `workers` times the number of Expo targets
within that limit:

- A session still queued after two minutes logs
  `still queued at Expo after two minutes`.
- Targets prepare one after another, so one target's ready sessions idle
  while the next target's queue and boot.
- A session still queued when another has been ready for
  `maxIdleTimeMinutes` fails the lease, because Expo may be stopping the idle
  one. The run then stops
  every session.

## Watch a run

The provider logs the expo.dev page of each session when it creates it:

```text
ℹ leasing 2 ios device(s) from expo-simulators
ℹ expo-simulators (1 of 2): simulator session 01a0…, https://expo.dev/accounts/acme/projects/shop/simulator-sessions/01a0…
ℹ expo-simulators: leased 01a0…
```

A browser preview that opens without a token logs as `watch at <url>`. One
that needs a token stays out of the log, because the token controls the
simulator.

Sessions are named `e2e <target> <n> of <total>` and tagged `e2e`,
`e2e-run:<run id>`, and `e2e-target:<target>`. To list a run's sessions, use
`eas simulator:list --tag e2e-run:<run id>`.

## Recordings

An attempt that records [video](https://e2e.tester.army/docs/reference/config#video) records through
agent-device, as it does locally, to `video/video.mp4` in its artifact
directory.

Set the mobile engine's `videoTouches: false`, as in [Setup](#setup).
agent-device draws touches into a recording when it stops, which takes
minutes on an Expo iOS simulator. The attempt then runs out its
`cleanupTimeout` and keeps no file. Without touches, the stop takes about
two seconds.

## When a session fails to start

A lease fails, and the run ends before any test, when there is no
`EXPO_TOKEN` and no eas-cli login, when the account has no Expo Simulators,
or when a session fails to start. A session fails when it errors, stays
queued too long (see [Sessions](#sessions)), or still boots 15 minutes after
it leaves the queue.

The provider stops the failed session and the run's other sessions. It
cancels the job of a queued one, so Expo doesn't start it later and bill the
account.

## Next

<CardGroup cols={2}>
  <Card title="Hosted devices" icon="mobile" href="/mobile#hosted-devices">
    How a device provider leases devices, and how to write your own.
  </Card>
  <Card title="Expo Workflows" icon="/images/integrations/expo.svg" href="/ci/eas-workflows">
    Run the suite in an Expo workflow.
  </Card>
</CardGroup>
