Autonomy Docs
Mobile

Mobile builds

Build an Android APK, then upload it to an environment or send it from CI with the API.

Mobile runs need a build of your app. Put it on an environment target to reuse it for every run until the next version, or upload it from CI and start runs with it directly.

What to upload

VariableRequiredDescription
iOS .app (.zip, .tgz, .tar.gz)NoSimulator build of the .app bundle, archived with the .app at the root of the archive.
Android .apkNoStandard Android package.
Android .aabNoAndroid App Bundle.

For iOS (coming soon), Autonomy needs a simulator build, because runs use iOS simulators. A signed .ipa built for devices can't run. For Android, .apk and .aab both work; runs install the build on an emulator and open it by package name. Mobile builds can be up to 512 MB. Every upload goes through a safety check before a run can use it.

Build for iOS Simulator

iOS testing is coming soon. Keep this for when it's available. Until then, Autonomy doesn't start runs on iOS.

For an Xcode project, build with xcodebuild:

Build .app for iOS Simulatorbash
xcodebuild -workspace ios/MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath ios/build \
CODE_SIGNING_ALLOWED=NO \
build

The bundle lands at ios/build/Build/Products/Release-iphonesimulator/MyApp.app. For Expo or React Native projects, run npx expo prebuild --platform ios first, then the same xcodebuild command against the generated workspace.

Zip the bundle before you upload it, with the .app at the root of the archive. -y keeps symlinks.

Zip the .app bundlebash
cd ios/build/Build/Products/Release-iphonesimulator
zip -qry MyApp.app.zip MyApp.app

Build for Android

For an Android project, build a debug APK. Debug APKs install on emulators without extra signing.

Build debug APKbash
cd android
./gradlew assembleDebug

The APK lands at android/app/build/outputs/apk/debug/app-debug.apk. For Expo or React Native projects, run npx expo prebuild --platform android first, then the same Gradle command against the generated project.

For an App Bundle, run ./gradlew bundleDebug and take the .aab from android/app/build/outputs/bundle/debug/.

Upload from the dashboard

  1. Open the project and click Environments, then open the environment. Owners and admins can edit environments.
  2. Under the iOS or Android target, choose Upload and drop the .app zip, .apk or .aab, or click to browse.
  3. Wait for "Uploaded and scanned", then save the environment.
  4. Run a test case on that environment once, to confirm the build opens and the first step can be reached.

To use a build your CI already publishes, choose URL instead and paste a direct download link. Runs use the build the link pointed to when you saved it. When CI publishes a new build at the same link, save the link again, or upload the build from CI as below. See Environments.

Upload with the API

Use the API from CI when the build is new for this pipeline and shouldn't replace the environment's build. It takes three calls, then the build's storage ID goes to run.trigger. Authenticate with an organization API key starting with aut_, from Organization → API keys (owners and admins).

  1. POST /api/artifacts/upload-url with platform (ios or android), fileName, contentType and declaredBytes (the file size in bytes). The response has uploadUrl, storageId, method ("PUT"), intentId and expiresAt.
  2. PUT the file to uploadUrl with the same Content-Type.
  3. POST /api/artifacts/scan with storageId and intentId. Start runs only when status is "clean"; otherwise findings says what was wrong.
Upload an iOS simulator buildbash
set -euo pipefail
API=https://api.autonomyqa.com
FILE=ios/build/Build/Products/Release-iphonesimulator/MyApp.app.zip

UPLOAD=$(curl -fsS -X POST "$API/api/artifacts/upload-url" \
-H "Authorization: Bearer $AUTONOMY_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --argjson bytes "$(wc -c < "$FILE" | tr -d ' ')" \
  '{platform: "ios", fileName: "MyApp.app.zip", contentType: "application/zip", declaredBytes: $bytes}')")
STORAGE_ID=$(jq -r '.storageId' <<< "$UPLOAD")
INTENT_ID=$(jq -r '.intentId' <<< "$UPLOAD")

curl -fsS -X PUT "$(jq -r '.uploadUrl' <<< "$UPLOAD")" \
-H "Content-Type: application/zip" \
--data-binary "@$FILE"

SCAN=$(curl -fsS -X POST "$API/api/artifacts/scan" \
-H "Authorization: Bearer $AUTONOMY_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg s "$STORAGE_ID" --arg i "$INTENT_ID" '{storageId: $s, intentId: $i}')")
jq -e '.status == "clean"' <<< "$SCAN" > /dev/null

Then start the runs with POST /api/v1/run.trigger. Give exactly one of testPlanId (a test plan: one run per test case) or testCaseId (one test case), the build as an explicit target, and an idempotencyKey in the body. Retrying with the same key returns the same runs instead of starting new ones.

Start runs with the uploaded buildbash
TRIGGER=$(curl -fsS -X POST "$API/api/v1/run.trigger" \
-H "Authorization: Bearer $AUTONOMY_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg plan "$AUTONOMY_TEST_PLAN_ID" --arg s "$STORAGE_ID" \
  --arg key "my-ci:$BUILD_ID:ios" \
  '{
    testPlanId: $plan,
    platforms: ["ios"],
    targets: { ios: { bundleId: "com.example.MyApp", storageId: $s, sourceMode: "upload", fileName: "MyApp.app.zip" } },
    branch: "main",
    deployment: { provider: "my-ci", branch: "main", commitSha: "abc123" },
    idempotencyKey: $key
  }')")
jq -r '.runIds[]' <<< "$TRIGGER"

The response is { runIds, planGroupRunId?, environment? }. For Android, send platform: "android", contentType: "application/vnd.android.package-archive", and trigger with targets.android and packageName instead of bundleId.

To run against a saved environment instead, send environmentSlug in place of targets: the run uses that environment's build and values. An explicit target has no environment, so steps that use {{env.NAME}} end as Couldn't start. If your CI already gives you an HTTPS link to the build, skip the upload and send artifactUrl with sourceMode: "url".

Follow the runs

Call POST /api/v1/run.get with runId, the cursor from the previous response and waitMs up to 25000. Each call waits until the run changes; timedOut: true only means nothing changed yet. Stop when status is passed, failed, couldnt_start or cancelled.

To gate a pipeline, pass only when every run is passed (a flaky pass counts). failed fails it; open the run to see which step and why. couldnt_start fails it too, but it means the environment couldn't be tested, for example the build didn't install, not that your app is broken.

The CI recipes wrap all of this, upload, start and follow, in one .ci/autonomy.sh helper: see GitHub Actions, GitLab CI, Jenkins, Bitrise and CircleCI. Fields and errors are in the API reference and Runs and evidence.

Limitations

  • iOS runs on simulators only. Camera, Face ID, Touch ID and any flow that needs a signed device build don't work.
  • Android runs on emulators. Hardware-only features such as NFC, fingerprint and a real camera aren't available.

Troubleshooting

The iOS upload is rejected

Check that the build used -sdk iphonesimulator and a simulator destination, and that you uploaded a .zip, .tgz or .tar.gz with the .app inside. A build for iphoneos is a device build.

The Android upload is rejected

Only .apk and .aab files are accepted.

The upload URL request returns 400

declaredBytes (a number) and contentType are required. The scan request needs both storageId and intentId from the upload URL response.

The run used an older build

A run with environmentSlug uses the environment's build, not the one you just uploaded. Send the new build as an explicit target, or update the environment's target first.

Didn’t find it?

On this page