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
| Variable | Required | Description |
|---|---|---|
| iOS .app (.zip, .tgz, .tar.gz) | No | Simulator build of the .app bundle, archived with the .app at the root of the archive. |
| Android .apk | No | Standard Android package. |
| Android .aab | No | Android 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:
xcodebuild -workspace ios/MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath ios/build \
CODE_SIGNING_ALLOWED=NO \
buildThe 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.
cd ios/build/Build/Products/Release-iphonesimulator
zip -qry MyApp.app.zip MyApp.appBuild for Android
For an Android project, build a debug APK. Debug APKs install on emulators without extra signing.
cd android
./gradlew assembleDebugThe 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
- Open the project and click Environments, then open the environment. Owners and admins can edit environments.
- Under the iOS or Android target, choose Upload and drop the
.appzip,.apkor.aab, or click to browse. - Wait for "Uploaded and scanned", then save the environment.
- 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).
POST /api/artifacts/upload-urlwithplatform(iosorandroid),fileName,contentTypeanddeclaredBytes(the file size in bytes). The response hasuploadUrl,storageId,method("PUT"),intentIdandexpiresAt.PUTthe file touploadUrlwith the sameContent-Type.POST /api/artifacts/scanwithstorageIdandintentId. Start runs only whenstatusis"clean"; otherwisefindingssays what was wrong.
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/nullThen 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.
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?