# The command line tool (CLI)

Ship a new mobile build, point your tests at it, and run them, without opening the dashboard.

Cutting a mobile build and then clicking through settings for every test that should run against it is the kind of work nobody does twice. The CLI collapses it into one command, and the same command works inside a build pipeline. Nothing needs installing: it runs through npx.

```bash
npx @testsnag/cli
```

Run it with no arguments and it asks what you want to do: run tests, or update a build. Every list it shows is searchable, so you pick a test by typing part of its name rather than hunting for an identifier.

> **Note:** Prefer to ask in plain language? An AI assistant connected over [MCP](https://testsnag.com/docs/running-tests/ai-clients-mcp.md#mobile-builds) can upload a build, attach it to your tests and run them too.

## Signing in

Create a token in your [workspace settings](https://testsnag.com/docs/workspace/teams-and-roles.md), on the API tokens tab. It is shown once, so copy it then. Then sign in:

```bash
npx @testsnag/cli login
```

The token is stored in ~/.testsnag/config.json and scoped to one workspace, so a token cannot reach anything outside it. Revoke it from the same tab whenever you want, and the CLI stops working immediately.

## Shipping a new build

Choose Update build and the CLI lists the build artifacts it can find, newest first. It looks in the current directory and in the output paths the common toolchains use, including Gradle, Xcode, Flutter and React Native, so a build you just cut is usually already in the list. Pick one, choose the tests it should run, and it uploads, publishes, and offers to run them. Selecting several tests at once is the point: one upload covers all of them.

> **Note:** The build stays attached to the tests you chose, so a run always records exactly which binary it used. Uploading a new build never changes what other tests run against.

If you only want the build stored for later, choose Just upload it when asked, or pass --no-bind. It lands in your [Files library](https://testsnag.com/docs/building-blocks/files.md) and can be bound to a test whenever you want.

## Running it in CI

Supply every input as a flag and there is no prompt, so it runs anywhere without a terminal attached. Set TESTSNAG_TOKEN in your pipeline secrets and skip the login step entirely. Repeat --test once for each test, because test names often contain commas.

```bash
npx @testsnag/cli update \
    --build ./android/app/build/outputs/apk/release/app-release.apk \
    --test "Checkout smoke" --test "Sign up" \
    --run --wait --json
```

With --wait the command blocks until every run finishes and exits non-zero if any of them failed, so a broken build stops the pipeline instead of shipping. --json prints machine readable output, and implies non-interactive, so it never waits on a prompt that nobody is there to answer.

```yaml
- name: Run mobile tests
  env:
      TESTSNAG_TOKEN: ${{ secrets.TESTSNAG_TOKEN }}
  run: |
      npx @testsnag/cli update \
          --build ./app-release.apk \
          --test "Checkout smoke" \
          --run --wait
```

> **Note:** Runs happen on our servers, so closing the terminal does not stop them. The CLI is only watching. Reopen the run in the dashboard whenever you want, or run the command again with --wait to pick the watching back up.

## The options

- **--test**: a test id, or its full name. Capitalisation and surrounding spaces do not matter, but a partial name is not accepted, so a typo fails loudly instead of quietly running the wrong test. If two tests share a name the command stops and asks you to use the id.
- **--build**: the path to the build file to upload.
- **--no-bind**: upload the build only, leaving it in your Files library without attaching it to a test.
- **--run**: run the tests once the build has published.
- **--wait**: block until the runs finish, and exit non-zero if any failed.
- **--json**: machine readable output. Implies non-interactive.
- **--token**: overrides TESTSNAG_TOKEN for a single command.
- **--url**: point at a specific TestSnag installation.

## When a build is refused

Checks run in the order that fails fastest, so your plan and remaining device minutes are verified before a single byte is uploaded rather than after a few hundred megabytes. An .ipa and an .aab are both real app packages and can be uploaded and stored, but neither can be bound to a test: an .ipa is built for device hardware and the device runs a simulator, and an .aab is not installable until it is converted. Try to bind one and the CLI names the fix, the xcodebuild command for the first and the bundletool command for the second.
