Running Mobile Tests With Expo EAS
Expo EAS can build your iOS Simulator app and hand it to TesterArmy, so pull requests can run mobile QA without a custom simulator setup in CI.
Expo EAS can build your iOS Simulator app and hand it to TesterArmy, so pull requests can run mobile QA without a custom simulator setup in CI.

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.
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 build, not an .
May 27, 2026Expo EAS already knows how to build your app. Here's how to pass that build to TesterArmy and run mobile tests from the same workflow.
May 17, 2026Agents shouldn't QA your app in YOLO mode. They should hand off testing to a specialized system - and the interface for that handoff is a CLI.
.app.ipaAn .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.
The EAS workflow should stay simple. It only needs to orchestrate five things:
.app artifact.TesterArmy handles the rest:
That split is the whole integration. EAS gives us a build. TesterArmy runs it.
The workflow needs three values from TesterArmy.
| Environment variable | What it points to |
|---|---|
TESTERARMY_API_KEY | API key from |
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.
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.
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.
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.
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.
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.

Building an autonomous QA agent sounds simple until the model has to navigate, reason, assert, and stay reliable at the same time.
TESTERARMY_PROJECT_ID | Project ID from Project Settings |
TESTERARMY_GROUP_ID | Group ID for the mobile tests you want to run |