---
title: "How to run iOS and Android UI tests in CI: GitHub Actions, GitLab CI, Bitrise and CircleCI"
description: "Working CI config for iOS simulator and Android emulator UI tests in GitHub Actions, GitLab CI, Bitrise and CircleCI, plus how to skip macOS runners."
canonical: "https://momentic.ai/blog/run-ios-android-ui-tests-in-ci"
last-updated: "2026-11-02T19:11:43Z"
---

# How to run iOS and Android UI tests in CI: GitHub Actions, GitLab CI, Bitrise and CircleCI

URL: https://momentic.ai/blog/run-ios-android-ui-tests-in-ci

[blog](/blog) [/ resources](/blog/category/resources)  / run-ios-android-ui-tests-in-ci

Resources

Working CI config for iOS simulator and Android emulator UI tests in GitHub Actions, GitLab CI, Bitrise and CircleCI, plus how to skip macOS runners.

Wei-Wei Wu

CEO, Momentic

## TL;DR

- [iOS simulator](/ios-testing) builds require macOS and Xcode. Keep that build job separate from the UI test job when your CI setup allows it.
- [Android emulators](/android-testing) need a runner with KVM access or supported nested virtualization. Simulator and emulator startup can also make self-managed test jobs flaky.
- Remote [simulators and emulators](/blog/emulators-vs-simulators-in-mobile-testing) let a Linux CI job run UI tests after you upload the app builds. The iOS build still needs macOS.
- Label critical flows and make their passing test job a [required status check](/blog/ci-e2e-test-gating). A failed run can then block the pull request instead of leaving a result that someone must notice.

## Why mobile UI tests are harder to run in CI than unit tests

Mobile UI tests need a running operating system and an installed app. Unit tests can usually run inside the CI job without either, so mobile jobs have more setup to complete before the first assertion.

iOS simulator tests need Xcode and a macOS host. That limits where the job can run and makes macOS runner capacity a cost consideration. Android emulators can run on Linux, but usable performance typically depends on KVM hardware acceleration. A shared runner that does not expose virtualization may leave the emulator too slow for a reliable pull-request check.

Build artifacts add another dependency to each test run. The test job needs an app built for its target, such as an iOS simulator `.app`, rather than whatever package the release job produces. Caching npm dependencies, Gradle outputs, or Xcode DerivedData can reduce rebuild work, but the job must still use an app artifact built from the commit under test.

Emulator startup can fail before application code runs. A booted process may not yet be ready to install an app, and a cold start may exceed the CI job’s timeout. A failure at that point says little about the UI flow being tested, so the pipeline needs to distinguish device startup failures from test failures. This guide covers simulators and emulators throughout. Momentic’s approach here does not run tests on [physical devices](/blog/real-devices-vs-emulators-simulators).

## Two ways to get iOS and Android test runners

You can run mobile UI tests on CI-hosted simulators and emulators, or use remote devices provisioned by Momentic. With local runners, an iOS test job needs macOS and Xcode. An Android emulator job needs a runner that supports KVM or nested virtualization. You also manage simulator boot, emulator startup, and retries in the pipeline.

Remote devices move simulator and emulator provisioning out of the CI job. After you upload the build artifact, a Linux runner can invoke the tests against Momentic’s remote devices. The Linux test job does not need macOS or KVM, but an iOS `.app` still needs a macOS/Xcode build job.

The choice affects where you spend runner time and handle boot failures. Local devices keep provisioning under your control, but test jobs occupy macOS or KVM-capable runners while devices start and tests execute. Remote devices reduce that runner setup and let the test stage use Linux. Device startup can still fail, so keep failure reporting and retries visible in either pipeline.

## GitHub Actions setup

GitHub Actions can keep app builds and mobile UI tests in separate jobs. The macOS job builds simulator-compatible artifacts and uploads them to Momentic. A Linux job then runs the tests against remote simulators and emulators. This example assumes the repository installs `momentic-mobile` through `npm ci`. Replace the Xcode workspace, scheme, app bundle name, and test paths with those in your repository.

`name: UI tests  on: pull_request  jobs:  browser:  runs-on: ubuntu-latest  env:  MOMENTIC_API_KEY: ${{ secrets.MOMENTIC_API_KEY }}  steps:  - uses: actions/checkout@v4  - uses: actions/setup-node@v4  with:  node-version: 22  cache: npm  - run: npm ci  - run: npx momentic install-browsers  - run: npx momentic run  - if: always()  run: npx momentic results upload   build-mobile:  runs-on: macos-latest  env:  MOMENTIC_API_KEY: ${{ secrets.MOMENTIC_API_KEY }}  ASSET_TAG: ${{ github.event.pull_request.head.sha }}  steps:  - uses: actions/checkout@v4  - uses: actions/setup-node@v4  with:  node-version: 22  cache: npm  - uses: actions/setup-java@v4  with:  distribution: temurin  java-version: '17'  - run: npm ci  - name: Build iOS simulator app  run: |  xcodebuild build-for-testing \  -workspace ios/App.xcworkspace \  -scheme App \  -destination 'generic/platform=iOS Simulator' \  -derivedDataPath "$RUNNER_TEMP/ios-build"  - name: Build Android APK  run: cd android && ./gradlew :app:assembleDebug  - name: Upload mobile builds  run: |  momentic-mobile assets upload \  "$RUNNER_TEMP/ios-build/Build/Products/Debug-iphonesimulator/App.app" \  --channel pr --tag "$ASSET_TAG"  momentic-mobile assets upload \  android/app/build/outputs/apk/debug/app-debug.apk \  --channel pr --tag "$ASSET_TAG"   test-mobile:  needs: build-mobile  runs-on: ubuntu-latest  env:  MOMENTIC_API_KEY: ${{ secrets.MOMENTIC_API_KEY }}  ASSET_TAG: ${{ github.event.pull_request.head.sha }}  steps:  - uses: actions/checkout@v4  - uses: actions/setup-node@v4  with:  node-version: 22  cache: npm  - run: npm ci  - run: momentic-mobile run tests/ios --channel pr --tag "$ASSET_TAG" --parallel AUTO -y --upload-results  - run: momentic-mobile run tests/android --channel pr --tag "$ASSET_TAG" --parallel AUTO -y --upload-results`

### Run Android emulator tests without Firebase Test Lab

The `test-mobile` job uses a remote Android emulator through Momentic, so its GitHub Actions runner does not need KVM or a locally booted AVD. The APK still needs an Android build before the test job starts.

### Run iOS simulator tests on a macOS runner

The `build-mobile` job uses Xcode’s `build-for-testing` action to produce an iOS simulator `.app`. GitHub Actions needs macOS for that build, but the subsequent Momentic test job runs on Linux. Both jobs use the pull request’s head SHA as the asset tag so the test run selects the build from the same commit.

## GitLab CI setup

GitLab can run the test stage on a Linux runner. For a standard Momentic suite, put the API key in GitLab’s CI/CD variables as `MOMENTIC_API_KEY`, then add this job to `.gitlab-ci.yml`.

`stages:  - test  ui_tests:  stage: test  image: node:22  rules:  - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'  script:  - npm install  - npx momentic install-browsers --all  - npx momentic run  after_script:  - npx momentic results upload`

GitLab marks the job failed when `npx momentic run` exits with an error. The `after_script` runs afterward so results can be uploaded even when tests fail.

For iOS simulator tests in GitLab CI, keep the test job on Linux and run against a remote simulator. First, build a simulator `.app` on a macOS runner and upload it using the same `pr` channel and asset tag the test job will use. In a job with the `momentic-mobile` CLI installed, replace the standard suite command with the mobile run command.

`momentic-mobile run tests/ios --channel pr --tag "$ASSET_TAG" --parallel AUTO -y --upload-results`

Set `ASSET_TAG` to the value used during the build upload, such as the commit SHA. GitLab does not provide a first-party hosted macOS runner tier, so an iOS build still needs a suitable macOS runner or an externally produced simulator build. The remote simulator removes that requirement from the test job, not the build job.

## Bitrise setup

Bitrise can build the apps on its native Xcode and Android stacks, then run UI tests in a separate workflow against remote simulators and emulators. That separation keeps the iOS build on macOS without requiring the test job to use macOS minutes.

In Bitrise, add a Script step after the build artifacts have been uploaded. Set `MOMENTIC_API_KEY` as a Bitrise secret, and pass the same `ASSET_TAG` used for both uploads into the test workflow. If your Bitrise configuration offers a suitable Linux stack, run the test workflow there. Confirm the stack and CLI installation method before adopting it.

`workflows:  mobile_ui:  steps:  - script:  inputs:  - content: |-  set -eu  momentic-mobile run tests/ios --channel pr --tag "$ASSET_TAG" --parallel AUTO -y --upload-results  momentic-mobile run tests/android --channel pr --tag "$ASSET_TAG" --parallel AUTO -y --upload-results`

Install `momentic-mobile` before this step. Use the `assets upload` command with `--channel pr --tag "$ASSET_TAG"` in the build workflow, but verify its artifact arguments and the Bitrise step configuration against your Momentic setup rather than assuming a Bitrise-specific integration exists. Keep the Script step’s exit status intact so a failed test run fails the workflow.

## CircleCI setup

CircleCI can run the test job on Linux when the tests use remote simulators or emulators. Store `MOMENTIC_API_KEY` in a CircleCI context named `momentic`, then attach that context to the job. The example below installs the project’s dependencies, runs the test suite, and attempts to upload results even if the run fails.

`version: 2.1  jobs:  mobile-ui:  docker:  - image: cimg/node:22.0  steps:  - checkout  - run: npm ci  - run: npx momentic install-browsers  - run: npx momentic run  - run:  name: Upload results  command: npx momentic results upload  when: always  workflows:  pull-request-tests:  jobs:  - mobile-ui:  context: momentic`

For iOS, keep any Xcode build in a separate job using CircleCI’s macOS executor, and pass the simulator `.app` to the remote-test flow described later. The Linux test job does not need a macOS resource class. Momentic’s CI documentation also covers secrets and sharding, but configure shard parameters against the documented commands rather than guessing flag names.

## Skipping macOS runners entirely with remote simulators and emulators

Remote simulators let a Linux CI job run iOS UI tests without booting a simulator on its own runner. You still need macOS and Xcode to produce the iOS simulator `.app` bundle. A device build cannot replace that bundle. After the build job uploads the artifact, Momentic runs the tests on remote simulators and returns the result to the Linux job.

`momentic-mobile assets upload path/to/App.app --channel pr --tag "$ASSET_TAG" momentic-mobile run tests/ios --channel pr --tag "$ASSET_TAG" --parallel AUTO -y --upload-results`

Use the same `ASSET_TAG` in both commands so the test run selects the build uploaded for that pull request. The upload can happen in the macOS build job, while the test command runs later on Linux. Remote Android emulators likewise remove the need to boot an Android emulator inside the test job.

Xcode Cloud is enough if your release check consists of iOS XCTest UI tests. It can build the app and run those tests, but it does not run Android tests. If one required check must cover both platforms, run the Android and iOS suites through a cross-platform test pipeline and gate the release on its result.

## Caching and avoiding flaky boots

Cache inputs that take time to download, but keep the cache key tied to what produced them. For Node dependencies, key npm’s download cache on the lockfile and run `npm ci` so the job installs the versions committed in the repository. For Android builds, retain Gradle’s dependency files and wrapper between jobs. Key iOS DerivedData on the Xcode version and build configuration, or rebuild it when those inputs change. Reusing incompatible build output can cause failures that disappear on a clean run.

Remote simulators and emulators move device startup out of your CI job, so the job no longer has to create an AVD, start it, and wait for its boot sequence. Remote devices can still be unavailable, and uploads or test connections can still fail. Keep infrastructure retries separate from test retries so a failed assertion remains visible rather than passing after an automatic rerun.

## Gating releases on test results

A test run blocks a merge only when CI treats its exit code as a required check. Label the mobile flows that must pass before release as `critical`, then run that subset in the pull request test job.

`set -e momentic-mobile run tests/ios --channel pr --tag "$ASSET_TAG" --labels critical --reporter steps -y --upload-results momentic-mobile run tests/android --channel pr --tag "$ASSET_TAG" --labels critical --reporter steps -y --upload-results`

The `--labels critical` flag selects the release-blocking flows, while `--reporter steps` prints their progress in CI logs and `--upload-results` preserves the run for investigation. Keep the commands in a step that fails when either command returns a nonzero exit code. Do not append `|| true` or pipe the output through a command that hides the test exit code.

In GitHub Actions, add the resulting test job to the branch’s required status checks. Apply the equivalent required pipeline or merge-check setting in GitLab CI, Bitrise, or CircleCI, and make the release job depend on the passing mobile test jobs. Keep those jobs active on every pull request. A dashboard result alone cannot block a merge, and a skipped test job cannot verify the critical flows.

## Engineering takeaway

Remote simulators and emulators let you add mobile UI test jobs without expanding a macOS runner fleet or maintaining KVM-enabled hosts. You still need macOS to build an iOS app, but test execution no longer has to occupy that runner.

As mobile CI scales, decide which flows should block a merge. Label the purchase or sign-in paths you consider critical, run them as required checks, and leave exploratory coverage outside the gate until its failures provide a reliable signal. More test runs help only when you can tell which failures require action.

## Frequently asked questions

Can Momentic test physical devices?    No. Momentic runs mobile UI tests on iOS simulators and Android emulators, not physical phones or tablets.    What does --channel pr --tag $ASSET_TAG do?    The channel identifies the pull-request asset group, and the tag identifies the uploaded build that a test run should use. Pass the same values to momentic-mobile assets upload and momentic-mobile run so the run selects the intended build.    How does sharding work across CI providers?    Run separate CI jobs with distinct --shard-index values and the same --shard-count. Add a merge step after the jobs finish so you can evaluate the complete test result before allowing a merge or release.    Can I use Jenkins, Buildkite, Azure DevOps, or Travis CI?    Yes. Momentic has documentation for those CI providers, though this guide covers GitHub Actions, GitLab CI, Bitrise, and CircleCI.

Still have additional questions?

## Keep reading.

[Resources   What replaces Appium for Android end-to-end tests? Lower-maintenance options compared     Espresso, UI Automator, Maestro, Detox and Momentic compared for Android end-to-end tests: maintenance cost, CI fit and limits.     Wei-Wei Wu     10 min read](/blog/appium-alternatives-android-e2e)[Resources   XCUITest Tutorial: Write and Run iOS UI Tests     Set up a UI test target in Xcode, write your first XCUITest in Swift, run it from xcodebuild and GitHub Actions, and fix the failures you will hit first.     Wei-Wei Wu     5 min read](/blog/xcuitest-tutorial)[Resources   Best Android Testing Tools: 11 Tools Compared     Espresso, Appium, Maestro, Detox, Momentic and six more Android testing tools, compared by testing layer, cross-platform support, selector maintenance and pricing model.     Wei-Wei Wu     5 min read](/blog/best-android-testing-tools)

## Close the feedback loop.

Point Momentic at your app. Free to start, no credit card.

[Try for free](https://app.momentic.ai/signup) [Contact sales](/sales)
