> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.tester.army/llms.txt.
> For full documentation content, see https://docs.tester.army/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.tester.army/_mcp/server.

# Expo EAS

Run TesterArmy mobile tests from Expo EAS workflows by uploading the EAS iOS Simulator build and triggering a remote test group.

## Prerequisites

Before you set it up, make sure you have already:

* Created mobile project (can be without uploaded app)
* Created at least one mobile test

Also make sure that you have configured [Expo EAS with Github](https://docs.expo.dev/build/setup/)

## Required environment variables

| Environment variable    | Description                                     |
| ----------------------- | ----------------------------------------------- |
| `TESTERARMY_API_KEY`    | API key for TesterArmy                          |
| `TESTERARMY_PROJECT_ID` | Project ID for project you want to run tests on |
| `TESTERARMY_GROUP_ID`   | Group ID for group you want to run tests on     |

These values can be found in the TesterArmy Dashboard:

* API key - **Profile → API Keys**
* Project ID - **Project Settings**
* Group ID - **Test Tab, then click on three dots on the group you want to run tests on and copy the ID**

Set `TESTERARMY_API_KEY` as an EAS secret or sensitive environment variable. Do not commit it, and
do not expose it through an `EXPO_PUBLIC_*` environment variable.

## Instructions for AI agents

Below you will find instructions for AI agent to set up EAS integration with TesterArmy for you.

You are working in an Expo app repository. Add TesterArmy mobile testing through Expo EAS workflows.

Before editing files:

* Inspect the existing `eas.json`, `.eas/workflows`, `package.json`, and package manager lockfile.
* Do not overwrite an existing EAS workflow. Create a new workflow file or make the smallest safe edit.
* Do not hard-code secrets, API keys, project IDs, or group IDs in committed files.
* Use the repository's existing package manager style when adding commands.

Implement this setup:

1. Ensure `eas.json` has an iOS Simulator build profile for TesterArmy. Prefer the profile name `testerarmy-ios-simulator`. If the project already has a simulator build profile, reuse it and keep the workflow profile name in sync.
2. The build profile must set `ios.simulator` to `true`. TesterArmy needs a `.app` simulator build, not an `.ipa` device build.
3. Create `.eas/workflows/testerarmy-mobile-tests.yml` if it does not already exist.
4. The workflow should support `push` to `main`, `pull_request` to `main`, and `workflow_dispatch`.
5. The workflow should read these values from EAS environment variables:
   * `TESTERARMY_API_KEY`
   * `TESTERARMY_PROJECT_ID`
   * `TESTERARMY_GROUP_ID`
6. The workflow should build the iOS Simulator app with EAS.
7. The workflow should download the build artifact with `eas/download_build`.
8. The workflow should upload the downloaded `.app` artifact with `npx --yes testerarmy@latest upload-app`.
9. The workflow should run the saved TesterArmy group with the uploaded app ID.
10. The workflow should pass commit SHA for GitHub check reporting when the EAS/GitHub context provides it.
11. The workflow should pass PR number only when the workflow is running for a pull request.
12. The workflow should delete the uploaded app after the run.

Use this workflow as the baseline and adapt it to the repository if needed:

```yaml
name: TesterArmy Mobile Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  workflow_dispatch: {}

jobs:
  build_ios:
    name: Build iOS Simulator app
    type: build
    environment: preview
    params:
      platform: ios
      profile: testerarmy-ios-simulator

  upload_app:
    name: Upload app to TesterArmy
    needs: [build_ios]
    environment: preview
    outputs:
      app_id: ${{ fromJSON(steps.upload_app.outputs.upload_result).uploadedAppId }}
    steps:
      - uses: eas/checkout

      - uses: eas/download_build
        id: download_build
        with:
          build_id: ${{ needs.build_ios.outputs.build_id }}
          extensions:
            - app

      - name: Upload app
        id: upload_app
        run: |
          set -euo pipefail

          APP_PATH="${{ steps.download_build.outputs.artifact_path }}"

          mkdir -p .testerarmy

          npx --yes testerarmy@latest upload-app \
            --app-path "$APP_PATH" \
            --project "$TESTERARMY_PROJECT_ID" \
            --output .testerarmy/upload.json

          set-output upload_result "$(tr -d '\n' < .testerarmy/upload.json)"

  run_tests:
    name: Run TesterArmy tests
    needs: [upload_app]
    environment: preview
    steps:
      - uses: eas/checkout

      - name: Run tests
        run: |
          set -euo pipefail

          APP_ID="${{ needs.upload_app.outputs.app_id }}"
          COMMIT_SHA="${{ github.sha }}"
          EVENT_NAME="${{ github.event_name }}"
          PR_NUMBER="${{ github.event.pull_request.number || '' }}"
          TIMEOUT_MS="1800000"
          POLL_INTERVAL_SECONDS="10"

          mkdir -p .testerarmy

          args=(
            ci
            --group "$TESTERARMY_GROUP_ID"
            --project "$TESTERARMY_PROJECT_ID"
            --app-id "$APP_ID"
            --commit-sha "$COMMIT_SHA"
            --timeout "$TIMEOUT_MS"
            --poll-interval-seconds "$POLL_INTERVAL_SECONDS"
            --output .testerarmy/ci-result.json
            --delete-app-after-run
          )

          if [ "$EVENT_NAME" = "pull_request" ] && [ -n "$PR_NUMBER" ]; then
            args+=(--pr-number "$PR_NUMBER")
          fi

          npx --yes testerarmy@latest "${args[@]}"
```

Use these command shapes:

```bash
npx --yes testerarmy@latest upload-app \
  --app-path "$APP_PATH" \
  --project "$PROJECT_ID" \
  --output .testerarmy/upload.json
```

```bash
npx --yes testerarmy@latest ci \
  --group "$GROUP_ID" \
  --project "$PROJECT_ID" \
  --app-id "$APP_ID" \
  --commit-sha "$COMMIT_SHA" \
  --timeout "$TIMEOUT_MS" \
  --poll-interval-seconds "$POLL_INTERVAL_SECONDS" \
  --output .testerarmy/ci-result.json
```

If a PR number is available, append:

```bash
--pr-number "$PR_NUMBER"
```

If cleanup is enabled, append:

```bash
--delete-app-after-run
```

After editing:

* Show the user which files changed.
* Tell the user to configure `TESTERARMY_API_KEY`, `TESTERARMY_PROJECT_ID`, and `TESTERARMY_GROUP_ID` in their EAS environment.
* Tell the user which EAS workflow command to run.
* Do not run the real TesterArmy CLI or EAS build unless the user explicitly asks you to.

Finish by summarizing the files changed and the manual test command.

## Configure an iOS Simulator build

TesterArmy requires an iOS Simulator build profile in EAS. Add the following snippet to your `eas.json` file:

```json eas.json
{
  "build": {
    "testerarmy-ios-simulator": {
      "ios": {
        "simulator": true
      }
    }
  }
}
```

## Add the EAS workflow

Here is an example workflow file that:

1. Builds the app for iOS Simulator
2. Uploads the app to TesterArmy
3. Runs the tests
4. Deletes the app from TesterArmy, after the tests are completed

```yaml .eas/workflows/testerarmy-mobile-tests.yml
name: TesterArmy Mobile Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  workflow_dispatch: {}

jobs:
  build_ios:
    name: Build iOS Simulator app
    type: build
    environment: preview
    params:
      platform: ios
      profile: testerarmy-ios-simulator

  upload_app:
    name: Upload app to TesterArmy
    needs: [build_ios]
    environment: preview
    outputs:
      app_id: ${{ fromJSON(steps.upload_app.outputs.upload_result).uploadedAppId }}
    steps:
      - uses: eas/checkout

      - uses: eas/download_build
        id: download_build
        with:
          build_id: ${{ needs.build_ios.outputs.build_id }}
          extensions:
            - app

      - name: Upload app
        id: upload_app
        run: |
          set -euo pipefail

          APP_PATH="${{ steps.download_build.outputs.artifact_path }}"

          mkdir -p .testerarmy

          npx --yes testerarmy@latest upload-app \
            --app-path "$APP_PATH" \
            --project "$TESTERARMY_PROJECT_ID" \
            --output .testerarmy/upload.json

          set-output upload_result "$(tr -d '\n' < .testerarmy/upload.json)"

  run_tests:
    name: Run TesterArmy tests
    needs: [upload_app]
    environment: preview
    steps:
      - uses: eas/checkout

      - name: Run tests
        run: |
          set -euo pipefail

          APP_ID="${{ needs.upload_app.outputs.app_id }}"
          COMMIT_SHA="${{ github.sha }}"
          EVENT_NAME="${{ github.event_name }}"
          PR_NUMBER="${{ github.event.pull_request.number || '' }}"
          TIMEOUT_MS="1800000"
          POLL_INTERVAL_SECONDS="10"

          mkdir -p .testerarmy

          args=(
            ci
            --group "$TESTERARMY_GROUP_ID"
            --project "$TESTERARMY_PROJECT_ID"
            --app-id "$APP_ID"
            --commit-sha "$COMMIT_SHA"
            --timeout "$TIMEOUT_MS"
            --poll-interval-seconds "$POLL_INTERVAL_SECONDS"
            --output .testerarmy/ci-result.json
            --delete-app-after-run
          )

          if [ "$EVENT_NAME" = "pull_request" ] && [ -n "$PR_NUMBER" ]; then
            args+=(--pr-number "$PR_NUMBER")
          fi

          npx --yes testerarmy@latest "${args[@]}"
```

This version uses Expo's fingerprint and repack jobs to avoid full native rebuilds when the current native runtime matches an existing iOS Simulator build.

The workflow:

1. Calculates the iOS fingerprint
2. Looks for a compatible iOS Simulator build with the same fingerprint
3. Repacks that build with the current JavaScript bundle when a match exists
4. Falls back to a full iOS Simulator build when no compatible build exists
5. Uploads the resulting `.app` to TesterArmy and runs the saved test group

Keep the fingerprint job's `environment` aligned with the build profile environment. Expo's
fingerprint job is intended for CNG/managed projects; if your repository commits native `ios` or
`android` directories, use the basic workflow or verify your Expo fingerprint setup first.

```yaml .eas/workflows/testerarmy-mobile-tests.yml
name: TesterArmy Mobile Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  workflow_dispatch: {}

jobs:
  fingerprint:
    name: Calculate fingerprint
    type: fingerprint
    environment: preview

  find_compatible_ios_build:
    name: Find compatible iOS build
    needs: [fingerprint]
    type: get-build
    params:
      platform: ios
      profile: testerarmy-ios-simulator
      simulator: true
      fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }}
      wait_for_in_progress: true

  repack_ios:
    name: Repack iOS app
    needs: [find_compatible_ios_build]
    if: ${{ needs.find_compatible_ios_build.outputs.build_id }}
    type: repack
    params:
      build_id: ${{ needs.find_compatible_ios_build.outputs.build_id }}
      profile: testerarmy-ios-simulator

  build_ios:
    name: Build iOS Simulator app
    needs: [find_compatible_ios_build]
    if: ${{ !needs.find_compatible_ios_build.outputs.build_id }}
    type: build
    environment: preview
    params:
      platform: ios
      profile: testerarmy-ios-simulator

  upload_app:
    name: Upload app to TesterArmy
    after: [repack_ios, build_ios]
    environment: preview
    outputs:
      app_id: ${{ fromJSON(steps.upload_app.outputs.upload_result).uploadedAppId }}
    steps:
      - uses: eas/checkout

      - uses: eas/download_build
        id: download_build
        with:
          build_id: ${{ after.repack_ios.outputs.build_id || after.build_ios.outputs.build_id }}
          extensions:
            - app

      - name: Upload app
        id: upload_app
        run: |
          set -euo pipefail

          APP_PATH="${{ steps.download_build.outputs.artifact_path }}"

          mkdir -p .testerarmy

          npx --yes testerarmy@latest upload-app \
            --app-path "$APP_PATH" \
            --project "$TESTERARMY_PROJECT_ID" \
            --output .testerarmy/upload.json

          set-output upload_result "$(tr -d '\n' < .testerarmy/upload.json)"

  run_tests:
    name: Run TesterArmy tests
    needs: [upload_app]
    environment: preview
    steps:
      - uses: eas/checkout

      - name: Run tests
        run: |
          set -euo pipefail

          APP_ID="${{ needs.upload_app.outputs.app_id }}"
          COMMIT_SHA="${{ github.sha }}"
          EVENT_NAME="${{ github.event_name }}"
          PR_NUMBER="${{ github.event.pull_request.number || '' }}"
          TIMEOUT_MS="1800000"
          POLL_INTERVAL_SECONDS="10"

          mkdir -p .testerarmy

          args=(
            ci
            --group "$TESTERARMY_GROUP_ID"
            --project "$TESTERARMY_PROJECT_ID"
            --app-id "$APP_ID"
            --commit-sha "$COMMIT_SHA"
            --timeout "$TIMEOUT_MS"
            --poll-interval-seconds "$POLL_INTERVAL_SECONDS"
            --output .testerarmy/ci-result.json
            --delete-app-after-run
          )

          if [ "$EVENT_NAME" = "pull_request" ] && [ -n "$PR_NUMBER" ]; then
            args+=(--pr-number "$PR_NUMBER")
          fi

          npx --yes testerarmy@latest "${args[@]}"
```

## GitHub PR comments and checks

When your workflow is triggered by a pull request, TesterArmy will create a GitHub check and comment on the PR.

When workflow is triggered by a commit (eg. on push to main), TesterArmy will create a GitHub check on the commit.

To enable this, you need to configure your TesterArmy project with your GitHub repository.
You can do it in **Integrations** Tab in project.

## Example repository

You can find a working example repository [here](https://github.com/tester-army/mobile-example).

## Troubleshooting

### The build is not a simulator app

TesterArmy requires an iOS Simulator build to run tests. If you are using a physical device build, you need to build a simulator build first.

Ensure you are building **release** build - not debug build.

### TesterArmy warns that GitHub integration is not configured / TesterArmy cannot create a PR comment

If you are seeing this error, it means that your TesterArmy project has no GitHub integration configured.
You can configure it in **Integrations** Tab in project.

## Related docs

* [App Uploads](/mobile/app-uploads)
* [GitHub Actions](/mobile/github-actions)