Skip to main content
A build is an Android .apk or .aab, or an iOS .ipa, that you upload to QA Wolf. QA Wolf reads the app id, version and build number from the file. Every run records which build it installed, so you know exactly what each run tested.
Builds are turned on per workspace. Until they are on for yours, every upload fails with “Builds are not enabled for this workspace.” Ask your QA Wolf representative to turn them on. Until then, upload with legacy uploads.

What you can upload

  • File types — .apk and .aab for Android, .ipa for iOS. Any other file is refused, including a .zip.
  • Size — at most 500 MB.
  • File name — letters, digits, ., _ and -, at most 255 characters. The Builds page and the CLI replace other characters with -, and the Fastlane plugin replaces spaces with -. Every other way refuses the file.
  • Zipped apps — not accepted. Upload the .apk, .aab or .ipa inside the zip. A zip that holds Payload/<App>.app/ is already an .ipa, so rename it to .ipa. Any other zipped app stays on legacy build uploads for now.
  • Retention — 180 days after upload, except the latest build of each app and platform in a static environment, which stays until a later upload replaces it.

How a run picks its build

A build reaches runs in three ways:
  • Through its environment — upload the build with an environment, and it becomes that environment’s latest build of its app and platform. Every later run in the environment installs it, until a later upload of the same app and platform replaces it. This covers scheduled runs, runs started by hand, and runs a deployment starts.
  • Through a run or a deployment — name the build when you create a run or report a deployment. Only those runs install it. A build uploaded without an environment reaches runs only this way.
  • Through Tester chat — a build sent in chat reaches the runs in that chat session.
“Latest” means uploaded last, not the highest version. Uploading an older version again makes it the latest. For each app and platform, a run installs the first build it finds in this order:
  1. The builds named when the run was created. See Test a build in one run.
  2. The builds the deployment that started the run reported.
  3. The environment’s latest build.
  4. The base environment’s latest build, for an environment that has one.
A build sent in Tester chat comes before all of these, for the runs in that chat session. A build whose file has expired counts as absent.

Upload a build

Each way below uploads the file and creates the build in one step.
The Builds page, Tester chat and the QA Wolf MCP use your sign-in, and the CLI accepts qawolf auth login. Every other way needs an API key. Create one under Workspace Settings → API Keys. An organization or user key also needs the workspace id, shown on the same page.

Builds page

1

Choose the file

Open Settings → Builds and click Upload build, or drop the file on the page.
2

Choose the environment

Choose the static environment whose runs install the build. It defaults to your default environment.
3

Add the build

Click Add build once the file finishes uploading.

GitHub Actions

Use qawolf/upload-build-action@v1. Pass its build-id output to qawolf/notify-qawolf-on-deploy-action@v3, version 3.2.0 or later. The runs the deployment starts then install the build. Example:
The upload action also outputs app-id, platform, version and build-number. To test a pull request in its own ephemeral environment, set environment to a name for the pull request, such as pr-${{ github.event.pull_request.number }}, in both steps. On the upload step, also set ephemeral-environment: "true" and base-environment. See the action’s README for every input.

Fastlane

Use the QA Wolf Fastlane plugin 1.2.0 or later. The plugin is not published to RubyGems. Point the Gemfile at the repository and pin a tag:
upload_build_to_qawolf uploads the file that gradle or build_app built earlier in the lane. notify_deploy_qawolf reports every build the lane uploaded, so you do not pass the build id:
For a pull request, pass ephemeral_environment: true and base_environment to upload_build_to_qawolf. The plugin’s README is the reference for every option.

Node.js

Use uploadBuild from @qawolf/ci-sdk 3.5.0 or later. Pass the build id to deployment.reportStatus as builds:
uploadBuild returns an outcome rather than throwing. Each call creates a new build, so a retry is another call.

CLI

Use qawolf build upload from @qawolf/cli 2.8.0 or later:
Set QAWOLF_API_KEY, or sign in with qawolf auth login. An organization or user key also needs --workspace-id. Use --environment-id in place of --environment-name to name an environment by id. The command prints the build id and the build’s URL.

HTTP API

For a pipeline that cannot run Node.js. Each call is a POST to https://app.qawolf.com/api/trpc/public.<name> with the input under json.
1

Request an upload URL

The response holds buildId, uploadUrl and uploadHeaders under result.data.json.
2

Upload the file

PUT the file to uploadUrl, with every header in uploadHeaders exactly as returned. The URL expires after one hour, and accepts only a file of exactly fileSizeBytes.
3

Create the build

QA Wolf refuses a file that is not an app. A second call with the same buildId returns the same build.
To start runs, report the deployment with "builds": ["<buildId>"]. See Webhook.

Tester chat and the QA Wolf MCP

  • Tester chat — attach an .apk, .aab or .ipa to a message. The build has no environment. The runs in that chat install it, and the environment’s other runs do not.
  • QA Wolf MCP — your coding agent calls build_requestUpload, uploads the file, then calls build_create. To test the build without making it the environment’s latest, the agent omits environment and passes the build id to run_create as builds.

Use the build in a flow

Name the app by its id: the Android package name, or the iOS bundle id. The flow installs the run’s build of that app:
This needs @qawolf/flows 0.4.0 or later. When the run has no build of that app, launch fails with “No build of <id> for <platform> on this environment”. It does not fall back to another file. A flow that passes app ignores RUN_INPUT_PATH. See App resolution for Android or iOS.

Test a build in one run

Pass the build ids as builds when you create the run, to test a build without changing what the environment’s other runs install. To leave the environment’s latest build unchanged, upload the build with no environment. Then create the run with one of these:
  • CLI — qawolf run create --environment-id <env> --builds <build_id>.
  • QA Wolf MCP — run_create with builds.
  • HTTP — POST https://app.qawolf.com/api/trpc/public.run.create with { "json": { "workspaceId": "…", "environmentId": "…", "flowIds": ["…"], "builds": ["<build_id>"] } }.
The Node.js SDK cannot create runs. Each build replaces the environment’s latest build of its app and platform, for that run only. A run takes at most 20 builds.

Move from legacy uploads

A legacy upload puts a file in run inputs storage, and the flow reads its path from RUN_INPUT_PATH. Its runs name no expected build, so QA Wolf cannot tell whether they tested the right file. Move in three steps. The legacy path keeps working while you move.
1

Upload builds

Replace the legacy upload with one of the ways above. On Fastlane, see “Moving from upload_to_qawolf” in the plugin’s README. If your pipeline zips the app, upload the .apk, .aab or .ipa inside the zip.
2

Change your flows

Replace launch() with no app, and app: { path } or app: { env }, with launch({ app: { id } }). A flow that passes app: { id } installs the build and ignores RUN_INPUT_PATH.
3

Remove RUN_INPUT_PATH

Once no mobile flow launches without app, remove RUN_INPUT_PATH from the environment’s variables and from your deployment reports. Only a mobile flow that calls launch() with no app reads it. A Windows flow that passes RUN_INPUT_PATH to startApp() still needs it.
Last modified on November 3, 2026