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

# Run in CI

> Start a Minitap run from your pipeline, wait for the verdict and fail the job when a scenario fails, on GitHub Actions or any other CI.

This page gets Mini running from your pipeline: on every release tag, every merge, or before a deploy, with the job failing when a scenario fails. GitHub Actions uses the Minitap GitHub Action; every other CI uses the `minitest` CLI with a workspace API key.

| Your CI | Use | Signs in with |
| - | - | - |
| GitHub Actions | The [`minitap-ai/minitest-trigger`](https://github.com/minitap-ai/minitest-trigger) Action | The workflow's GitHub OIDC token, so there's no secret to store |
| GitLab CI, CircleCI, Bitbucket Pipelines, Azure Pipelines, Jenkins, anything else | The `minitest` CLI | A workspace API key stored as a CI secret |

## Find your app and scenario identifiers

Every setup below needs to say which app to test, and sometimes which scenarios.

| You need | Where to find it |
| - | - |
| **App slug**, for the Action's `app-slug` | **App settings → General → App slug** (it has a copy button), or **Copy slug** in the app's menu on the apps page. Changing the slug breaks the workflows that use it. |
| **App ID**, for the CLI's `--app` or `MINITEST_APP_ID` | The part after `/apps/` in the dashboard URL, or `minitest --json apps list`. The CLI also accepts the app's name. |
| **Scenario names** | The **Tests** page, or `minitest --json --app <app-id> scenario list` |
| **Scenario IDs**, for the Action's `user-stories` | Open the scenario on **Tests**: the URL ends with `?scenario=<id>`. `scenario list` prints each scenario's `id` too. |
| **Tag names**, for the Action's `user-story-types` or the CLI's `--tag` | The tags on the **Tests** page, or `minitest --json --app <app-id> tags list` |

## GitHub Actions

Install the Minitap GitHub App on your organization first (see [GitHub](https://www.minitap.ai/docs/minitest/integrations/github.md)), and give the job the `id-token: write` permission so the Action can sign in.

```yaml .github/workflows/minitap.yml theme={"theme":{"light":"github-light","dark":"vesper"}}
name: Minitap

on:
  push:
    tags: ['v*']

jobs:
  minitap:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # lets the Action sign in with OIDC
      contents: read
    steps:
      - uses: minitap-ai/minitest-trigger@v2
        with:
          app-slug: my-app
```

With only `app-slug`, Mini builds the app for iOS and Android from the commit the workflow runs on, using the app's [build settings](https://www.minitap.ai/docs/minitest/setup/builds-from-repo.md), runs every scenario, and reports back through a GitHub check on the commit. The step returns as soon as the run is queued.

### Upload a build you already made

If your workflow builds the app, pass the file and Mini tests that instead of building:

```yaml theme={"theme":{"light":"github-light","dark":"vesper"}}
- uses: minitap-ai/minitest-trigger@v2
  with:
    app-slug: my-app
    ios-build-path: ./build/Build/Products/Debug-iphonesimulator/MyApp.app
    android-build-path: ./app/build/outputs/apk/debug/app-debug.apk
```

The iOS build must be a simulator `.app` or `.ipa`, and the Android build an `.apk` that runs on x86-64 emulators. Set `run-ios: false` or `run-android: false` to skip a platform. The [Action reference](https://www.minitap.ai/docs/minitest/reference/minitest-trigger-action.md#build-paths-and-requirements) has the build commands.

### Gate a release on the verdict

Add `wait-for-result: true` and the step waits for the verdict. Add `fail-on-failure: true` and the step fails when the run fails, so every job that `needs:` it is skipped.

```yaml theme={"theme":{"light":"github-light","dark":"vesper"}}
jobs:
  minitap-gate:
    runs-on: ubuntu-latest
    timeout-minutes: 70
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: minitap-ai/minitest-trigger@v2
        with:
          app-slug: my-app
          wait-for-result: true
          fail-on-failure: true
          wait-timeout-minutes: 60

  deploy:
    needs: minitap-gate
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
```

Keep the job's `timeout-minutes` above `wait-timeout-minutes` (45 by default), or GitHub stops the runner before the verdict arrives. The verdict lands in the `result` output: `passed`, `failed`, `error`, or `nothing_affected` when no scenario was affected and nothing ran, which passes the gate.

### Run only what changed since the last release

On a release tag, Mini can run only the scenarios affected since the previous release instead of the whole suite. Set the **Release tags** pattern (a glob such as `v*` or `release-v*`) in **Workbench → CI & Maintenance**. From then on, a tag matching it runs the affected scenarios, and every other event runs the full suite. Set `scope` to choose yourself:

```yaml theme={"theme":{"light":"github-light","dark":"vesper"}}
- uses: minitap-ai/minitest-trigger@v2
  with:
    app-slug: my-app
    scope: affected   # or pr-affected, or full
```

To run named scenarios instead, list their IDs in `user-stories`, or tag names in `user-story-types` (the input kept its old name; it takes tags). Either one overrides `scope`. See [Choosing what to run](https://www.minitap.ai/docs/minitest/runs/pull-request-checks.md#choosing-what-to-run) for every option, and the [Action reference](https://www.minitap.ai/docs/minitest/reference/minitest-trigger-action.md) for all inputs and outputs.

## Any other CI, with the CLI

Every other CI runs the same three steps with the CLI: upload the build, start a run, wait for the verdict. The CLI's exit code says whether a command worked, not whether your tests passed, so the job reads the verdict from the JSON output.

### 1. Create an API key

In **Settings → API keys**, click **Create API key** and copy it: it's shown once. Store it in your CI as a secret variable named `MINITEST_API_KEY`, and set `MINITEST_APP_ID` to your app ID. See [API keys](https://www.minitap.ai/docs/minitest/workspace/api-keys.md).

### 2. Add the script

Commit this script as `ci/minitap.sh`. It installs the CLI if needed, uploads the build you pass, runs every scenario on it and fails the job when a scenario has a critical failure. It needs `bash`, `curl` and `jq` on the runner.

```bash ci/minitap.sh theme={"theme":{"light":"github-light","dark":"vesper"}}
#!/usr/bin/env bash
# Upload a build, run every scenario on it, fail the job on a critical failure.
# Needs MINITEST_API_KEY (secret) and MINITEST_APP_ID in the environment.
set -euo pipefail

BUILD_PATH="$1"
TIMEOUT_MINUTES="${MINITAP_TIMEOUT_MINUTES:-60}"

if ! command -v minitest >/dev/null; then
  curl -LsSf https://astral.sh/uv/install.sh | sh
  export PATH="$HOME/.local/bin:$PATH"
  uv tool install minitest-cli
fi

case "$BUILD_PATH" in
  *.ipa|*.app.zip) BUILD_FLAG=--ios-build ;;
  *.apk|*.apks|*.aab) BUILD_FLAG=--android-build ;;
  *) echo "Unknown build file: $BUILD_PATH" >&2; exit 1 ;;
esac

# Upload the build and keep its ID
BUILD_ID=$(minitest --json build upload "$BUILD_PATH" | jq -r '.id')

# Start one run of every scenario on it
RUN_ID=$(minitest --json run all "$BUILD_FLAG" "$BUILD_ID" | jq -r '.batchId')
echo "Minitap run started: $RUN_ID"

# Wait for the run to finish
DEADLINE=$(( $(date +%s) + TIMEOUT_MINUTES * 60 ))
while true; do
  RUN=$(minitest --json batch get "$RUN_ID")
  STATUS=$(jq -r '.status' <<<"$RUN")
  case "$STATUS" in completed|failed|cancelled) break ;; esac
  if [ "$(date +%s)" -ge "$DEADLINE" ]; then
    echo "No verdict after $TIMEOUT_MINUTES minutes" >&2
    exit 1
  fi
  sleep 30
done

# Keep the failing criteria as a job artifact, then gate on them
minitest --json run verdicts "$RUN_ID" --actionable > minitap-verdicts.json
CRITICALS=$(jq '[.targets[].counters.criticals] | add // 0' <<<"$RUN")
if [ "$STATUS" != completed ] || [ "$CRITICALS" -gt 0 ]; then
  echo "Minitap: run $STATUS with $CRITICALS critical failure(s)" >&2
  exit 1
fi
echo "Minitap: no critical failure"
```

A few ways to adapt it:

* **Fewer scenarios.** Replace `run all` with `run start --tag smoke` to run the scenarios carrying a tag, or with `run start "Sign in" "Checkout"` for two or more scenarios by name or ID.
* **Both platforms.** Upload both builds and pass `--ios-build` and `--android-build` to the same `run all`.
* **Web apps.** Skip the upload and run `minitest --json run all --web`, which tests the web targets set in the app's settings.
* **Let Mini build the commit.** For an app Mini builds from its repository, `minitest --json run from-commit <full-sha>` builds that commit, runs the suite and waits for the verdict (one hour by default, `--timeout` in seconds to change it).
* **Physical phones.** Add `--ios-physical` or `--android-physical` to `run all` to use the [real devices](https://www.minitap.ai/docs/minitest/setup/devices.md) assigned to your workspace. An iOS device build is uploaded with `build upload --physical`; for Android, upload the `.aab`.

### 3. Call it from your pipeline

Each example assumes an earlier job produced `build/app-release.aab`; change the path to your build. Give the job a timeout a bit longer than the script's 60 minutes.

> [tab] GitLab CI
>
> Add `MINITEST_API_KEY` as a masked variable in **Settings → CI/CD → Variables**.
>
> ```yaml .gitlab-ci.yml theme={"theme":{"light":"github-light","dark":"vesper"}}
> minitap:
>   stage: test
>   image: ubuntu:24.04
>   timeout: 70m
>   variables:
>     MINITEST_APP_ID: "your-app-id"
>   before_script:
>     - apt-get update
>     - apt-get install -y curl jq
>   script:
>     - bash ci/minitap.sh build/app-release.aab
>   artifacts:
>     when: always
>     paths:
>       - minitap-verdicts.json
> ```

> [tab] CircleCI
>
> Add `MINITEST_API_KEY` to a context (here `minitap`) or to the project's environment variables.
>
> ```yaml .circleci/config.yml theme={"theme":{"light":"github-light","dark":"vesper"}}
> version: 2.1
>
> jobs:
>   minitap:
>     docker:
>       - image: cimg/base:current
>     environment:
>       MINITEST_APP_ID: your-app-id
>     steps:
>       - checkout
>       - attach_workspace:
>           at: .
>       - run:
>           name: Run the Minitap suite
>           command: bash ci/minitap.sh build/app-release.aab
>           no_output_timeout: 70m
>       - store_artifacts:
>           path: minitap-verdicts.json
>
> workflows:
>   release:
>     jobs:
>       - minitap:
>           context: minitap
> ```

> [tab] Bitbucket Pipelines
>
> Add `MINITEST_API_KEY` as a secured repository variable, and `MINITEST_APP_ID` next to it.
>
> ```yaml bitbucket-pipelines.yml theme={"theme":{"light":"github-light","dark":"vesper"}}
> image: ubuntu:24.04
>
> pipelines:
>   default:
>     - step:
>         name: Minitap
>         max-time: 70
>         script:
>           - apt-get update
>           - apt-get install -y curl jq
>           - bash ci/minitap.sh build/app-release.aab
>         artifacts:
>           - minitap-verdicts.json
> ```

> [tab] Azure Pipelines
>
> Add `MINITEST_API_KEY` as a secret pipeline variable. Azure doesn't expose secrets to scripts on its own, so map it in `env`.
>
> ```yaml azure-pipelines.yml theme={"theme":{"light":"github-light","dark":"vesper"}}
> pool:
>   vmImage: ubuntu-latest
>
> steps:
>   - script: bash ci/minitap.sh build/app-release.aab
>     displayName: Run the Minitap suite
>     timeoutInMinutes: 70
>     env:
>       MINITEST_API_KEY: $(MINITEST_API_KEY)
>       MINITEST_APP_ID: your-app-id
> ```

> [tab] Jenkins
>
> Store the key as a **Secret text** credential, here with the ID `minitest-api-key`. The agent needs `bash`, `curl` and `jq`.
>
> ```groovy Jenkinsfile theme={"theme":{"light":"github-light","dark":"vesper"}}
> pipeline {
>   agent any
>   options {
>     timeout(time: 70, unit: 'MINUTES')
>   }
>   environment {
>     MINITEST_API_KEY = credentials('minitest-api-key')
>     MINITEST_APP_ID  = 'your-app-id'
>   }
>   stages {
>     stage('Minitap') {
>       steps {
>         sh 'bash ci/minitap.sh build/app-release.aab'
>       }
>     }
>   }
>   post {
>     always {
>       archiveArtifacts artifacts: 'minitap-verdicts.json', allowEmptyArchive: true
>     }
>   }
> }
> ```

### Exit codes

When a `minitest` command itself fails, its exit code says why:

| Code | Meaning | What to do |
| - | - | - |
| `0` | The command worked | Read the JSON for the verdict |
| `1` | General error, including a rejected API key | Check the message on stderr |
| `3` | Network or API error | The only code worth retrying as is |
| `4` | Not found, for example a wrong app ID or scenario name | Check the identifier |
| `5` | Build rejected as invalid | Check the build type: simulator for iOS, emulator-ready for Android |

Comments and checks on pull requests are GitHub only. On GitLab or Bitbucket, the pipeline job above is the gate. The full command list is in the [CLI reference](https://www.minitap.ai/docs/minitest/reference/cli-commands.md).

## Next steps

> [card] [GitHub Action reference](https://www.minitap.ai/docs/minitest/reference/minitest-trigger-action.md)
>
> Every input and output, web runs and build requirements.

> [card] [Pull request checks](https://www.minitap.ai/docs/minitest/runs/pull-request-checks.md)
>
> A comment and a check on every GitHub pull request.

> [card] [Read the run report](https://www.minitap.ai/docs/minitest/runs/run-report.md)
>
> What each verdict means when the job goes red.

> [card] [Upload a build](https://www.minitap.ai/docs/minitest/setup/upload-a-build.md)
>
> Which build files Mini accepts, per platform and device.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.