# Test your Android build with a coding agent

Source: https://phonebox.dev/docs/guides/test-android-builds

> Install the APK your coding agent just built on a Phonebox phone in one command, check its version, then have the agent walk the new screens and report what it saw.



A coding agent such as Claude Code, Codex or Cursor can change your Android app, put the new build on a Phonebox phone, click through the screens it changed, and tell you what it saw. This guide sets that up with the CLI. It assumes your agent has the [agent skill](/docs/getting-started/coding-agent) and `PHONEBOX_API_KEY` in its environment.

Once there is a phone, getting a new build onto it is one command after the build:

```bash
./gradlew assembleDebug
phonebox apps "$PHONEBOX_PHONE" install app/build/outputs/apk/debug/app-debug.apk
```

## Create a phone for testing [#create-a-phone-for-testing]

Create one phone for this work, and keep reusing it: a parked phone keeps its apps and signed-in accounts, and costs nothing while it waits for the next build.

```bash
echo "android-qa-$(date +%s)" > android-qa.key   # once for this phone; a repeat of the create reads it again
phonebox create --name android-qa --idle 15m --max 2h --idempotency-key "$(cat android-qa.key)" --no-wait | tee android-qa.json
export PHONEBOX_PHONE=$(grep -o 'ph_[a-z2-7]\{12\}' android-qa.json | head -1)   # the new phone's "id"
phonebox status --wait                  # waits until the phone is ready, only reading it; safe to run again
```

* `--no-wait` prints the new phone at once, and `phonebox status --wait` then waits until it is ready. The phone bills from the moment it exists.
* The key and the phone are kept in files, because an agent's shell may not keep variables from one command to the next. In a new shell, run the `export` line again first.
* If `create` fails, run it again with the same key and the same flags: you get the phone the first attempt created, if it created one, and never a second one. If it exits without printing a phone, run `phonebox ls` and use the newest phone named `android-qa` before you create another.
* A phone is gone only when its `status` is `failed` or `deleted`, and then `create` exits with that phone's error: create the next one with a new key. An error that names the phone but is retryable, such as a start whose phone parked again, leaves the same phone to start again.
* `--idle 15m` gives the agent time between steps, and `--max 2h` caps each session. When a build takes longer than the idle timeout, park the phone while it runs and `phonebox start` it afterwards.

## Install your build in one command [#install-your-build-in-one-command]

Build the APK, then install it on the phone:

```bash
./gradlew assembleDebug
phonebox apps "$PHONEBOX_PHONE" install app/build/outputs/apk/debug/app-debug.apk
```

The command uploads the APK, waits while Phonebox reads its manifest, installs it and waits until the phone lists the app at the APK's `versionCode`. Each step is a line on stderr, and the last line on stdout is the install:

```text
{"uploading":"app/build/outputs/apk/debug/app-debug.apk","bytes":8388608}
{"processing":"upl_q3m7x2k6v4ta","package":null,"version_code":null}
{"ready":"upl_q3m7x2k6v4ta","package":"com.example.app","version_code":42}
{"installing":"com.example.app","upload":"upl_q3m7x2k6v4ta","version_code":42}
{"package":"com.example.app","upload":"upl_q3m7x2k6v4ta","install":"ins_7m2k6q3x4v5a","status":"succeeded","error":null,"next":null,"started_at":"2026-09-29T10:00:08.000Z","updated_at":"2026-09-29T10:00:11.000Z"}
```

* **Any signed APK installs**, debug builds included, up to 524,288,000 bytes (500 MiB). An Android App Bundle (`.aab`) or a split APK can't be installed on its own: build an APK. An unsigned release build is refused: sign it first.
* **A build signed with another key, or an older `versionCode`, needs `--replace`.** Android keeps one signing key per app and refuses to downgrade it, so a debug build from another machine, or one whose `versionCode` went back, fails. `--replace` uninstalls the app first, which deletes its data and sign-ins, then installs the new build. Without it, an older build is refused before anything runs (`409 version_downgrade`), and an install that fails over another signing key says so in its `next` step.
* **One upload installs on many phones.** The command prints the upload's ID; `POST /v1/phones/{id}/apps` with `{"upload": "upl_…"}` installs it on another phone without uploading again. `phonebox uploads` lists your uploads, which are deleted 7 days after their last install.
* **Rebuilding is cheap.** Installing the same file again reuses its upload (through the REST API, completing answers with that ready upload; the new one is a `duplicate` that names it), and a newer build of the package retires the older uploads within a day. A project's uploads hold at most 500 MiB, and a project creates at most 50 uploads a day. [Uploads](/docs/api-reference/uploads) has every limit and status.

The SDK does the same with `await phone.installApk("app/build/outputs/apk/debug/app-debug.apk")`. Through the REST API, create an upload, send the file to its `upload_url` with `curl`, complete it, and install it: [Uploads](/docs/api-reference/uploads) shows each request. Over MCP, the `create_app_upload`, `complete_app_upload` and `install_app` tools do the same, as [MCP](/docs/interfaces/mcp#install-your-own-build) shows.

## Check the version before you test [#check-the-version-before-you-test]

The install already waits until the phone lists your package at the build's `versionCode`. When the build arrives another way, such as from Google Play, the agent checks it before it walks a single screen. `phonebox apps` lists the installed apps with their `version_code`:

```bash
phonebox apps
```

The agent finds your package in the list and compares its `version_code` with the build you made. When the phone still has an older one, the new build hasn't arrived yet, and the agent never tests the old build in its place.

## Walk the new screens and report [#walk-the-new-screens-and-report]

With the right build installed, the agent opens the app and works through the screens you changed, looking after every action and saving screenshots as evidence:

```bash
phonebox open com.example.app
phonebox wait --text "Welcome" --timeout 20s
phonebox look
phonebox tap --text "Settings"
phonebox look
phonebox screenshot settings.jpg
phonebox tap --text "Notifications"
phonebox look
phonebox screenshot notifications.jpg
phonebox park
```

A prompt like this gives your agent the whole task:

```text
Build a debug APK of the app with the new notification settings screen, and install it on my Phonebox
phone ($PHONEBOX_PHONE) with `phonebox apps "$PHONEBOX_PHONE" install <the APK>`. If the install says
another signing key or an older version is on the phone, run it again with --replace. Open Settings >
Notifications, try each toggle, and look after every tap. Save a screenshot of each screen you visit.
Then park the phone and report what you saw, with the screenshots, and anything that looked wrong.
```

To test the build your testers get from Google Play instead, name that route in the prompt.

The agent reports from what `phonebox look` and the screenshots showed, and the version check makes sure that it tested your new build. Ask it to park the phone when it's done: an idle phone parks itself about its `idle_timeout` after the last request, but the minutes until then are billed.

## Another route: Google Play [#another-route-google-play]

To test a build the way your testers get it, the phone installs it from the Play Store, signed in as a tester. It takes more setup than the one-command install, and `phonebox apps install PACKAGE` doesn't reach Google Play: the agent drives the Play Store app on the phone.

### Set up once [#set-up-once]

1. **Choose a tester account.** Use a Google account that you keep for testing. Add it to the testers of your internal or closed testing track, and accept the testing invitation with it. For debug builds, also let it download from Internal app sharing, in that page's settings in Play Console.
2. **Sign the phone into that account.** Google's sign-in asks for a password and often a second step, so hand this part to a person. Open the Play Store with `phonebox open com.android.vending`, then create a live view link with `phonebox live --expires 15m` and open it yourself to sign in. [Sign in to apps and hand off to a human](/docs/guides/sign-in-and-handoff) walks through a handoff. The phone stays signed in while it is parked, so you do this only once.
3. **For debug builds, turn on Internal app sharing** in the phone's Play Store, in the same live view. Google's Play Console help describes where it is: in the Play Store's settings, tapping the Play Store version seven times shows developer options, which hold the switch.

### Publish each build [#publish-each-build]

* **A release build goes to a testing track.** Upload it to your internal or closed testing track in Play Console, or with the publishing tool your project already uses. A testing track takes only release builds: Play Console refuses a debuggable build on every track. A new build can take a while to reach testers after you publish it.
* **A debug build goes through Internal app sharing.** Upload the APK or app bundle on Play Console's Internal app sharing page, which takes debug builds. It gives you a link to that build.

### Install it from the Play Store [#install-it-from-the-play-store]

The agent installs or updates the app as a tester would, driving the Play Store's screens with `look` and `tap`. For a testing track, it opens the app's page in the Play Store:

```bash
phonebox open "market://details?id=com.example.app"
phonebox look
phonebox tap --text "Update"
phonebox wait --text "Open" --timeout 30s
```

For Internal app sharing, it opens the build's link instead, with `phonebox open` and the link, and then looks and taps in the same way. The first install of the app shows Install instead of Update. A download can take longer than one `wait`, so when `wait` times out, the agent looks, and waits again while the Play Store still shows the download moving. When the download stops moving, or an error appears, the agent parks the phone and reports to you.

## Clean up between builds [#clean-up-between-builds]

* The phone keeps the installed app while it is parked, and `phonebox start` resumes it for the next build.
* `phonebox apps rm com.example.app` uninstalls the app, with its data, when you need a clean install.
* `phonebox uploads rm upl_…` deletes an upload you no longer need; otherwise it is deleted 7 days after its last install.
