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

# PR Testing for Mobile Apps

> Upload each pull request iOS or Android build and run QA.tech tests or change reviews against that binary

Native mobile pull requests do not produce a preview URL. QA.tech tests the **binary your CI built for that PR**, not a deployed website. After the upload, pin the run or change review to that build with `applicationBuildShortId`.

If the GitHub App auto-runs without a build override, it uses the application's default environment (the last configured staging or production build). That is not the PR binary.

## Choose an integration mode

| Mode                               | What runs                                                                                                                             | When to use                                       |
| :--------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------ |
| **Test plan** (API-driven)         | A plan you already maintain (smoke, regression)                                                                                       | Deployment gates, required checks, known coverage |
| **Change review** (AI exploratory) | The same review agent as the [GitHub App](/configuration/github-app): selects relevant tests, fills gaps, posts a native PR/MR review | You want AI to decide what to test from the diff  |

Both modes use the same upload. They differ only in how you start testing after you have a build short ID.

<Note>
  Create the mobile application in **Settings → Applications & Envs** before CI
  uploads. See [Mobile App Testing](/test-features/mobile-app-testing). Passing
  `applicationBuildShortId` creates or reuses an environment for that build; you
  do not add a new environment in the UI for every PR.
</Note>

## Prerequisites

* Mobile testing enabled for the organization (contact [support](mailto:hi@qa.tech) if it is not)
* A mobile application in **Settings → Applications & Envs** (note the short ID, for example `app_gXeBl2`)
* A [test plan](/core-concepts/test-plans) short ID for API-driven runs, **or** the [GitHub App](/configuration/github-app) / [GitLab MR integration](/configuration/gitlab) connected for change reviews
* An API token from **Organization Settings → API Keys**, stored as `QATECH_API_TOKEN` (GitHub) or `QA_TECH_API_TOKEN` (GitLab)
* A **simulator or emulator** build in CI: Android `.apk`, or iOS `.app` compressed as `.zip` or `.tar.gz`. Device and App Store `.ipa` files cannot run on simulators. See [Preparing Your App Build](/test-features/mobile-app-testing#preparing-your-app-build).

## How it works

```
PR opened or updated
  → CI builds the app (simulator APK or zipped .app)
  → CI uploads the file (presigned URL, then create build)
  → CI starts a test plan or change review pinned to applicationBuildShortId
  → QA.tech creates or reuses an environment for that exact build
  → Tests run on a cloud simulator or emulator
```

See the [Application Builds API](/api-reference/application-builds) for request and response details.

## Upload the PR build

Run this **after** your compile step, on every PR. Replace `app_gXeBl2` and the file path.

```bash theme={null}
APP_ID="app_gXeBl2"
BUILD_FILE="app/build/outputs/apk/debug/app-debug.apk"
FILE_NAME=$(basename "$BUILD_FILE")

UPLOAD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds/upload-url" \
  -H "Authorization: Bearer $QATECH_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"fileName\": \"$FILE_NAME\"}")
UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.uploadUrl')
BUILD_TOKEN=$(echo "$UPLOAD_RESPONSE" | jq -r '.buildToken')

curl -sSf -X PUT "$UPLOAD_URL" \
  --upload-file "$BUILD_FILE" \
  -H "Content-Type: application/octet-stream"

BUILD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds" \
  -H "Authorization: Bearer $QATECH_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"platform\": \"android\", \"buildToken\": \"$BUILD_TOKEN\"}")
BUILD_SHORT_ID=$(echo "$BUILD_RESPONSE" | jq -r '.applicationBuildShortId')
echo "Uploaded $BUILD_SHORT_ID"
```

For iOS, zip the `.app` first, set `"platform": "ios"`, and upload the archive:

```bash theme={null}
zip -r AppName.zip AppName.app
BUILD_FILE="$PWD/AppName.zip"
```

The presigned URL expires in about two hours. Maximum file size is 4 GB.

To install companion apps on the same device, upload those builds too and pass their short IDs in `otherApplicationBuildShortIds`. See [Other Apps](/test-features/mobile-app-testing#other-apps).

## Set up GitHub Actions

Store `QATECH_API_TOKEN` under **Settings → Secrets and variables → Actions**.

### Run a test plan on the PR

Use this when a required check should run a known plan against the PR binary.

```yaml theme={null}
name: QA.tech mobile PR tests
on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build APK
        run: ./gradlew assembleDebug

      - name: Upload build to QA.tech
        id: upload
        env:
          QATECH_API_TOKEN: ${{ secrets.QATECH_API_TOKEN }}
          APK_PATH: app/build/outputs/apk/debug/app-debug.apk
        run: |
          APP_ID="app_gXeBl2"
          FILE_NAME=$(basename "$APK_PATH")
          UPLOAD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds/upload-url" \
            -H "Authorization: Bearer $QATECH_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"fileName\": \"$FILE_NAME\"}")
          UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.uploadUrl')
          BUILD_TOKEN=$(echo "$UPLOAD_RESPONSE" | jq -r '.buildToken')
          curl -sSf -X PUT "$UPLOAD_URL" \
            --upload-file "$APK_PATH" \
            -H "Content-Type: application/octet-stream"
          BUILD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds" \
            -H "Authorization: Bearer $QATECH_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"platform\": \"android\", \"buildToken\": \"$BUILD_TOKEN\"}")
          echo "build_short_id=$(echo "$BUILD_RESPONSE" | jq -r '.applicationBuildShortId')" >> "$GITHUB_OUTPUT"

      - uses: QAdottech/run-action@v3
        with:
          api_token: ${{ secrets.QATECH_API_TOKEN }}
          test_plan_short_id: 'pln_abc123'
          blocking: true
          applications_config: |
            {
              "applications": {
                "app_gXeBl2": {
                  "environment": {
                    "applicationBuildShortId": "${{ steps.upload.outputs.build_short_id }}"
                  }
                }
              }
            }
```

Replace `app_gXeBl2`, `pln_abc123`, and the APK path. Set `blocking: true` if the workflow should fail when tests fail.

iOS builds need a macOS runner and a simulator `.app`. Zip it, use `"platform": "ios"`, and point `BUILD_FILE` at the archive. Full `xcodebuild` flags are in [Mobile App Testing](/test-features/mobile-app-testing#preparing-your-app-build).

### Run a change review on the PR

Use this when you want the review agent to select tests from the diff and post a native GitHub review. Install the GitHub App at the organization level with access to the repository. Turn off **Auto-run on PRs** for that repo in **Settings → Integrations → GitHub App** so QA.tech does not also start a review against the default environment.

```yaml theme={null}
name: QA.tech mobile change review
on:
  pull_request:

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build APK
        run: ./gradlew assembleDebug

      - name: Upload build to QA.tech
        id: upload
        env:
          QATECH_API_TOKEN: ${{ secrets.QATECH_API_TOKEN }}
          APK_PATH: app/build/outputs/apk/debug/app-debug.apk
        run: |
          APP_ID="app_gXeBl2"
          FILE_NAME=$(basename "$APK_PATH")
          UPLOAD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds/upload-url" \
            -H "Authorization: Bearer $QATECH_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"fileName\": \"$FILE_NAME\"}")
          UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.uploadUrl')
          BUILD_TOKEN=$(echo "$UPLOAD_RESPONSE" | jq -r '.buildToken')
          curl -sSf -X PUT "$UPLOAD_URL" \
            --upload-file "$APK_PATH" \
            -H "Content-Type: application/octet-stream"
          BUILD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds" \
            -H "Authorization: Bearer $QATECH_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"platform\": \"android\", \"buildToken\": \"$BUILD_TOKEN\"}")
          echo "build_short_id=$(echo "$BUILD_RESPONSE" | jq -r '.applicationBuildShortId')" >> "$GITHUB_OUTPUT"

      - uses: QAdottech/run-action/change-review@v3
        with:
          api_token: ${{ secrets.QATECH_API_TOKEN }}
          applications_config: |
            {
              "applications": {
                "app_gXeBl2": {
                  "environment": {
                    "applicationBuildShortId": "${{ steps.upload.outputs.build_short_id }}"
                  }
                }
              }
            }
```

The Change Review Action reads the PR URL from `github.event.pull_request.html_url` on `pull_request` events. Set `blocking: true` if the job should wait for the verdict. Inputs and outputs are in [Change Review Action](/configuration/github-actions#change-review-action).

Write a PR description that names the user-facing flows to test. The agent uses that context the same way it does for web PRs. See [PR Testing](/best-practices/pr-testing).

## Set up GitLab CI

Store `QA_TECH_API_TOKEN` as a CI/CD variable. Upload the build, then start a run or a change review. Change reviews need the repository connected under **Settings → Integrations → GitLab**.

```yaml theme={null}
qatech_mobile_mr:
  stage: test
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  script: |
    APP_ID="app_gXeBl2"
    BUILD_FILE="app/build/outputs/apk/debug/app-debug.apk"
    FILE_NAME=$(basename "$BUILD_FILE")

    UPLOAD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds/upload-url" \
      -H "Authorization: Bearer $QA_TECH_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{\"fileName\": \"$FILE_NAME\"}")
    UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.uploadUrl')
    BUILD_TOKEN=$(echo "$UPLOAD_RESPONSE" | jq -r '.buildToken')
    curl -sSf -X PUT "$UPLOAD_URL" \
      --upload-file "$BUILD_FILE" \
      -H "Content-Type: application/octet-stream"
    BUILD_RESPONSE=$(curl -sSf -X POST "https://api.qa.tech/v1/applications/$APP_ID/builds" \
      -H "Authorization: Bearer $QA_TECH_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{\"platform\": \"android\", \"buildToken\": \"$BUILD_TOKEN\"}")
    BUILD_SHORT_ID=$(echo "$BUILD_RESPONSE" | jq -r '.applicationBuildShortId')

    MR_URL="${CI_MERGE_REQUEST_PROJECT_URL}/-/merge_requests/${CI_MERGE_REQUEST_IID}"
    curl -sSf -X POST "https://api.qa.tech/v1/chat/change-review" \
      -H "Authorization: Bearer $QA_TECH_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{
        \"mode\": \"pr\",
        \"prUrl\": \"$MR_URL\",
        \"vcsProviderId\": \"gitlab\",
        \"applicationOverrides\": [{
          \"applicationShortId\": \"$APP_ID\",
          \"environment\": { \"applicationBuildShortId\": \"$BUILD_SHORT_ID\" }
        }]
      }"
```

To run a fixed plan instead of a change review, call [Start Run](/api-reference/runs/start-test-run) with `testPlanShortId` and the same `applications` override. Poll [Get Run](/api-reference/runs/get-run) if the job should fail on test failure.

## Set up Bitrise

Bitrise is the same upload-then-run sequence, using `$BITRISE_APK_PATH` or a zipped simulator `.app` from `xcode-build-for-simulator`. The [Bitrise](/configuration/bitrise) page has copy-paste script steps for Android and iOS, including blocking (poll until the run finishes).

To post an MR/PR review instead of only starting a plan, keep that upload script and replace the final `POST /v1/run` with `POST /v1/chat/change-review` and `applicationBuildShortId`, as in the GitLab example.

## Network access

Mobile sessions use a different outbound IP pool than web tests. If the app talks to a firewalled backend, allowlist the ranges under [Settings → Network](https://app.qa.tech/current-project/settings/network) (**Mobile Testing IP Whitelist**). See [Network Access](/test-features/mobile-app-testing#network-access).

## Troubleshooting

**Tests ran, but against the old app**

The job did not pass `applicationBuildShortId`, or GitHub App **Auto-run on PRs** started a second review on the default environment. Pin the CI trigger to the uploaded build and turn auto-run off when CI owns the review.

**Upload or create-build fails for iOS**

The file is a device or App Store `.ipa`. Produce a simulator `.app` and upload a `.zip` or `.tar.gz`. See [Preparing Your App Build](/test-features/mobile-app-testing#preparing-your-app-build).

**Change review starts but never comments on the PR**

The GitHub App is not installed, or it does not have access to the repository. The Change Review Action still needs that App connection so the agent can read the PR and post the review. See [Change Review Action setup](/configuration/github-actions#change-review-action).

**Login or API calls fail in the emulator**

The backend is blocking mobile testing IPs. Update the [Mobile Testing IP Whitelist](/test-features/mobile-app-testing#network-access).

## Related documentation

* **[Mobile App Testing](/test-features/mobile-app-testing)** - Application setup, simulator builds, device presets
* **[Application Builds API](/api-reference/application-builds)** - Upload URL, create build, list builds
* **[GitHub Actions](/configuration/github-actions)** - Test Run Action and Change Review Action reference
* **[GitHub App](/configuration/github-app)** - Automatic PR reviews (web preview URLs)
* **[Bitrise](/configuration/bitrise)** - Mobile CI upload and test plan trigger
* **[CI/CD Integration](/configuration/ci-cd-integration)** - API-driven vs AI exploratory modes
* **[PR Testing](/best-practices/pr-testing)** - PR descriptions and review context
