TesterArmy
  • Customers
  • Pricing
Sign inGet started
HomeBlogE2E Testing for Expo Apps: Automated Runs on Every EAS Build

E2E Testing for Expo Apps: Automated Runs on Every EAS Build

How to run e2e tests on Expo apps: build an iOS Simulator app with EAS, hand it to an AI testing agent, and get pass/fail checks with recordings on every pull request. No Detox, no macOS runners.

Oskar Kwasniewski
Oskar KwasniewskiCTO
May 27, 20268 min read
E2E Testing for Expo Apps: Automated Runs on Every EAS Build

How e2e testing works for Expo apps

End-to-end testing an Expo app means running the real app binary, in a real simulator, through the flows your users actually perform: onboarding, login, the core loop. Historically that meant Detox or Maestro plus your own simulator infrastructure, which is exactly the part most Expo teams never get around to standing up. CI needs macOS runners for iOS simulators, the tooling needs native build configuration, and the tests need maintenance every time the UI shifts.

The setup in this post takes a different path: EAS builds the iOS Simulator app, which it already does well, and TesterArmy takes the artifact from there. The agent installs the build in a cloud simulator and runs your saved test flows against it, described in plain English rather than test code. Runs happen on every pull request, and results come back as GitHub checks with recordings and screenshots. There is no simulator fleet to maintain and no test framework to configure. One honest boundary to know up front: runs execute on simulators and emulators, which covers UI and flow regressions well, and does not replace on-device testing for hardware-specific behavior.

More on the setup for plain React Native projects (without EAS) is in React Native e2e testing without Detox, and the product page for this stack is Expo app testing.

Most Expo projects already use EAS as the place where native builds happen. It builds the app, stores the artifact, and can run workflows around that build.

That makes it a good place to add mobile QA. TesterArmy can take the iOS Simulator build from EAS, install it in a cloud simulator, and run your saved mobile tests against it.

You do not have to maintain a macOS runner, keep simulators warm, clean up uploaded artifacts, or write your own polling loop. For JavaScript-only changes, you can also use Expo Repack later and avoid a full native rebuild when the native runtime did not change.

Let's wire it up.

What EAS gives us

EAS already owns the native build step, so we should not duplicate that work somewhere else.

For TesterArmy, the important detail is the artifact type. We need an iOS Simulator .app build, not an .ipa.

An .ipa is meant for physical devices. A simulator run needs a simulator app bundle.

In EAS, the profile looks like this:

eas.json

{
  "build": {
    "testerarmy-ios-simulator": {
      "ios": {
        "simulator": true
      }
    }
  }
}

Once EAS produces that .app, the workflow can upload it to TesterArmy and start a saved mobile test group.

What TesterArmy handles

The EAS workflow should stay simple. It only needs to orchestrate five things:

  1. Build the iOS Simulator app.
  2. Download the .app artifact.
  3. Upload the app to TesterArmy.
  4. Run the selected mobile test group.
  5. Delete the temporary app upload when the run finishes.

TesterArmy handles the rest:

  • Cloud simulator provisioning
  • App installation
  • Agent-driven mobile test execution
  • Screenshots, videos, and run artifacts
  • Result polling
  • GitHub checks and PR comments

That split is the whole integration. EAS gives us a build. TesterArmy runs it.

The required values

The workflow needs three values from TesterArmy.

Environment variableWhat it points to
TESTERARMY_API_KEYAPI key from Profile -> API Keys
TESTERARMY_PROJECT_IDProject ID from Project Settings
TESTERARMY_GROUP_IDGroup ID for the mobile tests you want to run

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

The project ID and group ID are not sensitive in the same way, but keeping all three values in the EAS environment makes the workflow easier to reuse across projects.

Adding the workflow

The basic workflow has three jobs.

First, build the simulator app:

.eas/workflows/testerarmy-mobile-tests.yml

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

Next, download the EAS artifact and upload it to TesterArmy:

.eas/workflows/testerarmy-mobile-tests.yml

jobs:
  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)"

Finally, run the saved test group with the uploaded app ID:

.eas/workflows/testerarmy-mobile-tests.yml

jobs:
  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 || '' }}"

          args=(
            ci
            --group "$TESTERARMY_GROUP_ID"
            --project "$TESTERARMY_PROJECT_ID"
            --app-id "$APP_ID"
            --commit-sha "$COMMIT_SHA"
            --timeout "1800000"
            --poll-interval-seconds "10"
            --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[@]}"

The full copy-paste version is in the Expo EAS docs.

Getting PR feedback

When this runs on a pull request, TesterArmy can create a GitHub check and leave a PR comment. Reviewers get the normal pass/fail signal, plus links to the run details, screenshots, videos, and logs.

To enable that, connect your TesterArmy project to the GitHub repository from the project Integrations tab.

After that, pass the commit SHA and PR number from the EAS workflow:

testerarmy ci \
  --group "$TESTERARMY_GROUP_ID" \
  --project "$TESTERARMY_PROJECT_ID" \
  --app-id "$APP_ID" \
  --commit-sha "$COMMIT_SHA" \
  --pr-number "$PR_NUMBER"

On push events, TesterArmy attaches the check to the commit. On pull requests, it can attach the check and the PR comment.

Using Expo Repack

The basic workflow runs a full simulator build every time. Start there. It is slower, but it removes a lot of variables while you are setting up the integration.

Once that is stable, you can make the workflow faster with EAS fingerprint and Expo Repack. This is useful when a pull request changes JavaScript only and the native runtime is still compatible.

The optimized workflow calculates the native runtime fingerprint, looks for a compatible simulator build, and repacks it with the current JavaScript bundle when the native layer did not change.

You still test the code from the branch, but you do not pay for a full native rebuild on every PR.

That workflow variant is included in the Expo EAS docs. Treat it as an optimization, not the first thing to debug.

Common mistakes

The most common issue is uploading the wrong artifact.

TesterArmy needs an iOS Simulator .app build. If the workflow uploads an .ipa, the run cannot install it in the simulator.

The second issue is running a group that does not contain mobile tests. Before adding EAS, run the mobile test manually from the dashboard once. Check that the app upload works, the test passes, and the test is in the group you plan to call from CI.

The third issue is missing GitHub integration. Tests still run without it, but PR comments and commit checks need the TesterArmy project to be connected to the repository.

That's a wrap

Expo EAS already knows how to build your app. TesterArmy just needs the simulator artifact.

The setup is: create an iOS Simulator build profile, add the EAS workflow, configure the three environment variables, and run the saved test group on every PR.

If you want the exact copy-paste setup, start with the Expo EAS integration docs. If you still need to create your first mobile test, start with App Uploads.

Frequently asked questions

Can you run e2e tests on Expo apps without Detox? Yes. Build an iOS Simulator app with EAS and run it with an agent-based platform like TesterArmy. Detox requires native build integration and test code in your repo; the agent approach needs the .app artifact and plain-English flow descriptions. The practical walkthrough is in our React Native guide.

Do I need an .ipa or an .app build for testing? For simulator runs you need an iOS Simulator .app bundle, produced by an EAS profile with the simulator flag. An .ipa targets physical devices and will not install in a simulator.

Does this work with Expo Repack for JS-only changes? Yes. Once the basic workflow is stable, Repack lets you skip full native rebuilds when only JavaScript changed, which makes the PR loop substantially faster. The post covers when it is safe to use.

What does the pull request actually see? A GitHub check plus a PR comment with pass/fail per flow, links to the run, screenshots, and a full video recording of the simulator session.

ON THIS PAGE

  • How e2e testing works for Expo apps
  • What EAS gives us
  • What TesterArmy handles
  • The required values
  • Adding the workflow
  • Getting PR feedback
  • Using Expo Repack
  • Common mistakes
  • That's a wrap
  • Frequently asked questions

SHARE THIS ARTICLE

  • X

Related

  • React Native E2E Testing Without Detox or a Device Farm
  • Mobile App Testing Without a Device Farm
  • Why Teams Are Replacing Detox for React Native Testing
Contact us

Let's connect

Contact usGet a demo
XLinkedInDiscord
TesterArmyTesterArmy

AI-powered QA testing for modern teams. Ship faster with confidence.

SOC 2 Type 2 badge
GDPR badge

© 2026 TesterArmy, Inc.

Platform
  • Web testingWeb testing
  • Mobile app testingMobile app testing
  • Pull request testingPull request testing
  • CI/CD testingCI/CD testing
  • Production monitoringProduction monitoring
  • Coding agentsCoding agents
By stack and product
  • AI app testingAI app testing
  • React Native testingReact Native testing
  • Expo app testingExpo app testing
  • WordPress testingWordPress testing
  • Ecommerce testingEcommerce testing
Quick links
  • HomeHome
  • DemoDemo
  • How it worksHow it works
  • FAQFAQ
  • PricingPricing
  • Get a demoGet a demo
  • About usAbout us
  • Contact usContact us
Resources
  • DocumentationDocumentation
  • IntegrationsIntegrations
  • Migrate to TesterArmyMigrate to TesterArmy
  • Compare toolsCompare tools
  • Recruit a friendRecruit a friend
  • Affiliate programAffiliate program
  • BlogBlog
  • CustomersCustomers
  • Open sourceOpen source
  • Brand assetsBrand assets
  • API referenceAPI reference
  • Getting startedGetting started
Legal
  • Privacy policyPrivacy policy
  • Terms of serviceTerms of service
TesterArmy
  • Customers
  • Pricing
Sign inGet started
Quick links
  • HomeHome
  • DemoDemo
  • How it worksHow it works
  • FAQFAQ
  • PricingPricing
  • Get a demoGet a demo
  • About usAbout us
  • Contact usContact us