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

# Uploading a build

> Get your app onto clickproof, from the dashboard or from CI.

Every run needs a **build**: the app artifact the agent installs on the cloud
device. A project can hold builds for both platforms, and each run picks which
one to use.

| Platform | Artifact                     | Not accepted    |
| -------- | ---------------------------- | --------------- |
| Android  | `.apk`, `.apks`              | `.aab`, `.xapk` |
| iOS      | `.app.zip` (simulator build) | `.ipa`          |

Builds can be up to **600 MB**.

## iOS builds run on a simulator

This is the part people get wrong, so it is worth being blunt: iOS tests run on
an **Apple Simulator**, which cannot install a device `.ipa`. An `.ipa` is signed
and arm64-only. Uploading one is refused rather than accepted and failed later,
where it would look like a broken test instead of the wrong file.

Build for the simulator SDK and zip the `.app`:

```bash theme={null}
xcodebuild -workspace ios/MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -sdk iphonesimulator \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath ios/build \
  build

cd ios/build/Build/Products/Release-iphonesimulator
zip -r -X MyApp.app.zip MyApp.app
```

The archive's top-level entry must be `MyApp.app/`. If it is `Payload/MyApp.app/`
you have re-zipped an unpacked `.ipa`, and the bundle inside is still a device
build. That is refused too, for the same reason.

## From the dashboard

Open the project's **Mobile** tab and drop the file in. The platform comes from
the file, so one upload area takes either. Uploads are kept rather than
overwritten, and the newest build for each platform is the one runs install.

## From CI

You do not upload anything yourself. Connect the repository to the project and
the [GitHub App](/ci-cd/pull-requests) takes the build straight from the artifact
your workflow already uploads, on every pull request.

<Note>
  Uploading does not disturb a run that has already started. In-flight runs keep
  the build they began with, and a queued run uses the newest build for its
  platform when it starts.
</Note>
