---
title: Maestro CI/CD Integration | TestingBot
description: Run Maestro mobile tests on TestingBot from CI/CD using the TestingBot
  CLI. GitHub Actions, GitLab CI, Jenkins, CircleCI and Bitbucket Pipelines examples
  plus exit codes and reports.
source_url:
  html: https://testingbot.com/support/app-automate/maestro/ci-cd
  md: https://testingbot.com/support/app-automate/maestro/ci-cd/index.md
---
# CI/CD Integration

The [TestingBot CLI](https://testingbot.com/support/app-automate/maestro#cli) integrates seamlessly with any CI/CD environment. It runs all your Maestro flows, streams the output in real-time and exits with the appropriate exit code for pipeline automation.

We recommend using `npx` to run the CLI without installing it globally. This ensures you're always using the latest version.

## Quick Start

Run Maestro tests in any CI/CD service with a single command:

    npx --yes @testingbot/cli@latest maestro app.apk ./flows \
      --device "Pixel 8" \
      --deviceVersion "14" \
      --api-key $TB_KEY \
      --api-secret $TB_SECRET

This command will:

- Upload your mobile app (`.apk`, `.aab`, `.ipa` or `.app`)
- Upload and run all Maestro flow files from the `./flows` directory
- Stream test progress to your CI logs
- Exit with code `0` on success, non-zero on failure

You can pass credentials as arguments (`--api-key` and `--api-secret`) or set them as environment variables. Environment variables are the recommended approach for CI/CD.

## Environment Variables

Instead of passing credentials as command arguments, you can set them as environment variables. This is the most secure approach for CI/CD pipelines since credentials are never exposed in logs or command history.

| Variable | Description |
| --- | --- |
| `TB_KEY` | Your TestingBot API key |
| `TB_SECRET` | Your TestingBot API secret |

When these environment variables are set, you can omit the credential arguments entirely:

    export TB_KEY="your_api_key"
    export TB_SECRET="your_api_secret"
    
    npx --yes @testingbot/cli@latest maestro app.apk ./flows \
      --device "Pixel 8" \
      --deviceVersion "14"

Never hardcode credentials in your CI configuration files. Always use your CI provider's secrets management feature to store `TB_KEY` and `TB_SECRET`.

## Exit Codes

The CLI uses standard exit codes for CI/CD integration. Your pipeline will automatically fail when tests fail:

| Exit Code | Description |
| --- | --- |
| `0` | All tests passed successfully |
| `1` | One or more tests failed, or an error occurred |

## Test Reports

Generate test reports for CI/CD result decoration and test analytics. The CLI supports both JUnit XML and HTML report formats.

### JUnit Report

Generate JUnit XML reports for integration with CI/CD test result visualization:

    npx --yes @testingbot/cli@latest maestro app.apk ./flows \
      --device "Pixel 8" \
      --deviceVersion "14" \
      --report junit \
      --report-output-dir ./test-results

| Option | Description |
| --- | --- |
| `--report <format>` | Report format: `junit` |
| `--report-output-dir <path>` | Directory to save test reports (required when `--report` is used) |

JUnit reports can be used with most CI/CD platforms for:

- Test result visualization in your pipeline
- Tracking test trends over time
- Annotating pull requests with test results
- Integration with test management tools

You can also fetch reports via the [REST API](https://testingbot.com/support/app-automate/maestro/api#report) if you need to retrieve them separately.

## CI/CD Metadata

Pass repository and commit information to link test runs with your source control. This enables better traceability and integration with your development workflow.

| Option | Description |
| --- | --- |
| `--commit-sha <sha>` | The commit SHA of this upload |
| `--pull-request-id <id>` | The ID of the pull request this upload originated from |
| `--repo-name <name>` | Repository name (e.g., GitHub repo slug) |
| `--repo-owner <owner>` | Repository owner (e.g., GitHub organization or user) |

Example with metadata:

    npx --yes @testingbot/cli@latest maestro app.apk ./flows \
      --device "Pixel 8" \
      --deviceVersion "14" \
      --commit-sha "$COMMIT_SHA" \
      --pull-request-id "$PR_NUMBER" \
      --repo-owner "your-org" \
      --repo-name "your-repo"

## View Result Details

A link to the current test results will be printed to your CI logs during execution.

The test details page provides detailed test result information, including:

- Real-time test execution status
- Video recordings of each test run
- Screenshots captured during tests
- Device logs and Maestro output
- Test artifacts for debugging

## GitHub Actions

Add Maestro testing to your GitHub Actions workflow. Store your credentials in **Settings → Secrets and variables → Actions** as repository secrets.

    name: Maestro Tests
    
    on:
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      maestro-tests:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '20'
    
          - name: Build Android App
            run: ./gradlew assembleDebug
    
          - name: Run Maestro Tests
            run: |
              npx --yes @testingbot/cli@latest maestro \
                app/build/outputs/apk/debug/app-debug.apk \
                ./maestro-flows \
                --device "Pixel 8" \
                --deviceVersion "14" \
                --report junit \
                --report-output-dir ./test-results \
                --commit-sha ${{ github.sha }} \
                --pull-request-id ${{ github.event.pull_request.number }} \
                --repo-owner ${{ github.repository_owner }} \
                --repo-name ${{ github.event.repository.name }}
            env:
              TB_KEY: ${{ secrets.TB_KEY }}
              TB_SECRET: ${{ secrets.TB_SECRET }}
    
          - name: Upload Test Results
            uses: actions/upload-artifact@v4
            if: always()
            with:
              name: maestro-results
              path: test-results/
    
          - name: Publish Test Results
            uses: dorny/test-reporter@v1
            if: always()
            with:
              name: Maestro Test Results
              path: test-results/*.xml
              reporter: java-junit

Add `TB_KEY` and `TB_SECRET` as repository secrets in your GitHub repository settings. Never commit credentials to your repository.

## GitLab CI

Add Maestro testing to your `.gitlab-ci.yml`. Store your credentials in **Settings → CI/CD → Variables** and mark them as protected and masked.

    stages:
      - build
      - test
    
    build:
      stage: build
      image: gradle:8-jdk17
      script:
        - ./gradlew assembleDebug
      artifacts:
        paths:
          - app/build/outputs/apk/debug/
    
    maestro-tests:
      stage: test
      image: node:20
      dependencies:
        - build
      script:
        - npx --yes @testingbot/cli@latest maestro
            app/build/outputs/apk/debug/app-debug.apk
            ./maestro-flows
            --device "Pixel 8"
            --deviceVersion "14"
            --report junit
            --report-output-dir ./test-results
            --commit-sha $CI_COMMIT_SHA
            --pull-request-id $CI_MERGE_REQUEST_IID
            --repo-owner $CI_PROJECT_NAMESPACE
            --repo-name $CI_PROJECT_NAME
      variables:
        TB_KEY: $TB_KEY
        TB_SECRET: $TB_SECRET
      artifacts:
        when: always
        paths:
          - test-results/
        reports:
          junit: test-results/*.xml

Add `TB_KEY` and `TB_SECRET` as CI/CD variables in your GitLab project settings. Enable "Mask variable" to prevent them from appearing in job logs.

## Jenkins

Add Maestro testing to your Jenkins pipeline (`Jenkinsfile`). Store your credentials using **Manage Jenkins → Manage Credentials** as secret text.

    pipeline {
        agent any
    
        environment {
            TB_KEY = credentials('testingbot-api-key')
            TB_SECRET = credentials('testingbot-api-secret')
        }
    
        stages {
            stage('Build') {
                steps {
                    sh './gradlew assembleDebug'
                }
            }
    
            stage('Maestro Tests') {
                steps {
                    sh """
                        npx --yes @testingbot/cli@latest maestro \\
                            app/build/outputs/apk/debug/app-debug.apk \\
                            ./maestro-flows \\
                            --device "Pixel 8" \\
                            --deviceVersion "14" \\
                            --report junit \\
                            --report-output-dir test-results \\
                            --commit-sha ${env.GIT_COMMIT} \\
                            --pull-request-id ${env.CHANGE_ID ?: ''} \\
                            --repo-owner "your-org" \\
                            --repo-name "your-repo"
                    """
                }
                post {
                    always {
                        junit 'test-results/*.xml'
                        archiveArtifacts artifacts: 'test-results/**', allowEmptyArchive: true
                    }
                }
            }
        }
    }

Create credentials of type "Secret text" in Jenkins for `TB_KEY` and `TB_SECRET`. Reference them using the `credentials()` helper in your pipeline.

## CircleCI

Add Maestro testing to your `.circleci/config.yml`. Store your credentials in **Project Settings → Environment Variables**.

    version: 2.1
    
    jobs:
      maestro-tests:
        docker:
          - image: cimg/node:20
        steps:
          - checkout
    
          - run:
              name: Run Maestro Tests
              command: |
                npx --yes @testingbot/cli@latest maestro \
                  ./app.apk \
                  ./maestro-flows \
                  --device "Pixel 8" \
                  --deviceVersion "14" \
                  --report junit \
                  --report-output-dir test-results \
                  --commit-sha $CIRCLE_SHA1 \
                  --pull-request-id $CIRCLE_PR_NUMBER \
                  --repo-owner $CIRCLE_PROJECT_USERNAME \
                  --repo-name $CIRCLE_PROJECT_REPONAME
    
          - store_test_results:
              path: test-results
    
          - store_artifacts:
              path: test-results
              destination: maestro-results
    
    workflows:
      test:
        jobs:
          - maestro-tests

Add `TB_KEY` and `TB_SECRET` as environment variables in your CircleCI project settings. CircleCI automatically masks these values in build output.

## Bitbucket Pipelines

Add Maestro testing to your `bitbucket-pipelines.yml`. Store your credentials in **Repository settings → Pipelines → Repository variables** and enable "Secured".

    image: node:20
    
    pipelines:
      default:
        - step:
            name: Build and Test
            caches:
              - node
            script:
              - ./gradlew assembleDebug
              - npx --yes @testingbot/cli@latest maestro
                  app/build/outputs/apk/debug/app-debug.apk
                  ./maestro-flows
                  --device "Pixel 8"
                  --deviceVersion "14"
                  --report junit
                  --report-output-dir test-results
                  --commit-sha $BITBUCKET_COMMIT
                  --pull-request-id $BITBUCKET_PR_ID
                  --repo-owner $BITBUCKET_WORKSPACE
                  --repo-name $BITBUCKET_REPO_SLUG
            artifacts:
              - test-results/**

Add `TB_KEY` and `TB_SECRET` as secured repository variables in Bitbucket. Secured variables are encrypted and masked in build logs.

## Frequently asked questions

Which CI/CD systems can run Maestro tests on TestingBot?

Any CI runner that can execute a shell command. The page below has copy-paste-ready examples for [GitHub Actions](https://testingbot.com#github), [GitLab CI](https://testingbot.com#gitlab), [Jenkins](https://testingbot.com#jenkins), [CircleCI](https://testingbot.com#circleci) and [Bitbucket Pipelines](https://testingbot.com#bitbucket). The same TestingBot CLI works on any other CI runner with a shell.

How do I store TestingBot credentials in CI securely?

Set TB\_KEY and TB\_SECRET as encrypted CI secrets. GitHub Actions uses repository or environment secrets, GitLab CI uses protected variables, Jenkins uses credentials, CircleCI uses environment variables in contexts, and Bitbucket uses secured repository variables. Never commit the key or secret in plain text.

What exit codes does the TestingBot CLI return?

Exit code 0 for all flows passing, non-zero when one or more flows fail or when an infrastructure error occurs. See the [Exit Codes section](https://testingbot.com#exitcodes) for the full mapping so you can wire conditional follow-up steps in CI.

How do I publish Maestro test reports in CI?

The CLI writes JUnit-style reports that most CI runners can parse to show per-flow results. See the [Test Reports section](https://testingbot.com#reports) for the file path and configuration in each CI system.

How do I link CI metadata (branch, commit, pull request) to TestingBot test runs?

Pass --repo-owner, --repo-name, --branch, --commit-sha and --pull-request-id flags to the CLI. The values appear in the TestingBot dashboard so you can jump from a CI failure to the exact commit. See the [CI/CD Metadata section](https://testingbot.com#metadata).
