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

# How to Run Kane CLI in CI/CD

> Integrate Kane CLI into GitHub Actions, GitLab CI, Jenkins, Bitbucket Pipelines, and Docker for automated browser testing.

Kane CLI runs headless in CI/CD pipelines, with credentials passed as environment variables or inline flags. Tests fail fast on assertion errors and return standard exit codes that your pipeline can act on.

Kane CLI works in any CI system that can run a Node binary and reach Chrome. To run it non-interactively, authenticate with basic auth from CI secrets, force the browser into headless mode, set a wall-clock timeout, and check the exit code.

## Common Patterns

These patterns apply to every CI system. The platform recipes below differ only in how they pass in the secrets.

* **Always pass `--headless`**. CI runners have no display.

* **Always set `--timeout <seconds>`**, so a hung run cannot block the pipeline.

* **Authenticate with `--username` and `--access-key`** from CI secrets. Do not call `kane-cli login` in CI: that flow opens a browser for OAuth and will not work on a runner.

* **Provide a start URL**. A CI run cannot prompt for input. If the objective names no site, no `--url` flag is passed, and no `default_url` is configured, the run fails fast instead of waiting. The simplest fix is to start the objective with the site ("Go to https\://… and …") or pass `--url <url>`. To start from the browser's current page on purpose, add `--allow-missing-url`. See [Default start URL](https://www.testmuai.com/docs/kane-cli-configuration#default-start-url).

* **Load test data with `--variables-file <path>`**. Commit the file to your repo without secret values, or generate it before the step, filling values from CI secrets. A `{{name}}` with no value does not change the exit code on its own. Kane CLI warns before the browser starts and then types the name as written. If a step fails because of that literal text, the job fails as usual. The warning is a `warning` line on stdout with `code: "unresolved_variables"`. If a missing value should block the pipeline, fail the step when that line appears.

* **Project and folder are optional.** To file uploads under a specific Test Manager project or folder, set them in advance with `kane-cli config project <id>` and `kane-cli config folder <id>`. Find the IDs with `kane-cli projects list` and `kane-cli folders list`. If you skip this, Kane CLI picks a default project and folder on the first run and reports which ones it chose. See [Test Manager Integration](https://www.testmuai.com/docs/kane-cli-tms-integration).

* **Check the exit code**. In short: `0` passed, `1` failed, `2` error, `3` timeout or cancellation. The full mapping is in [Exit Codes](https://www.testmuai.com/docs/kane-cli-modes#exit-codes).

* **Archive the evidence pack.** Name a `kane-cli run` with `--name <name>`, and Kane CLI copies its sealed [evidence pack](https://www.testmuai.com/docs/kane-cli-evidence) into `.testmuai/evidence/` in the workspace. `testmd run` and `testrun run` always write their pack there. Archive that folder as a CI artifact. Without `--name`, a single run keeps its pack only in its session folder under `~/.testmuai/kaneai/sessions/`, outside the workspace.

* **Run whole suites with one command.** If your repo has committed `_test.md` tests, prefer one `testrun` invocation over a shell loop:

  ```bash theme={null}
  kane-cli testrun run --tags smoke --parallel 4 --headless --on-failure fail-fast
  ```

  The suite runs as one execution, with one exit code (`0` all passed, `1` any failure, `2` invalid plan, `3` cancelled) and one sealed [evidence pack](https://www.testmuai.com/docs/kane-cli-evidence). Archive `.testmuai/evidence/*.evidence` as a CI artifact so anyone can open it in the viewer. See [Batch Runs (testrun)](https://www.testmuai.com/docs/kane-cli-testrun).

* **Run the suite on the cloud grid when your runner cannot.** `kane-cli testrun run … --remote` turns the suite into one HyperExecute job. The grid supplies Chrome on a macOS runner, or a virtual Android emulator or iOS simulator for mobile `_test.md` tests, so your runner needs no Chrome, Xcode, or Android Studio. `--parallel N` spreads the tests across N grid runners. The recordings and the evidence pack come back to your checkout, just as if the suite had run on your machine.

  ```bash theme={null}
  kane-cli plugin install remote-execution

  # a web suite on 4 grid runners
  kane-cli testrun run --match '^tests/web/' --remote --parallel 4 \
    --username "$LT_USERNAME" --access-key "$LT_ACCESS_KEY"

  # a mobile suite, from a Linux runner
  kane-cli testrun run --match '^tests/app/' --remote \
    --device-name "Pixel 7" --os-version 14 \
    --username "$LT_USERNAME" --access-key "$LT_ACCESS_KEY"
  ```

  This requires a  plan with HyperExecute macOS runners. Web and mobile tests must go in separate runs. Choose devices with `kane-cli devices list --target emulator|simulator --remote`, and allow a timeout of about 10 minutes. See [Remote Runs](https://www.testmuai.com/docs/kane-cli-remote-execution).

> [!WARNING]
>
> **Chrome Requirement**
>
> Kane CLI launches Chrome itself, so your CI image must have Chrome on `PATH`. If the image cannot include Chrome, you have two options:
>
> * For a single `kane-cli run` or `testmd run`, connect to a remote browser with `--cdp-endpoint <url>` or `--ws-endpoint <url>`, for example a  `wss://` endpoint. Kane CLI still runs on your runner and drives that browser.
> * For a whole suite, `kane-cli testrun run … --remote` moves the run itself to the grid, as described above.

On slow or freshly started CI runners, Chrome can take a while to become reachable over the Chrome DevTools Protocol. If you see occasional "Chrome failed to launch" errors, raise one or both of these:

* `KANE_CLI_CDP_TIMEOUT_MS`: how long each launch attempt waits for Chrome to be ready. Default `30000`.
* `KANE_CLI_CDP_RETRIES`: how many times to retry the launch after the first attempt. Default `2`.

If Chrome is installed at a non-standard path in the image, set `KANE_CLI_CHROME_PATH`. See [Chrome environment variables](https://www.testmuai.com/docs/kane-cli-configuration#chrome-environment-variables).

## Authentication in CI/CD

Pass credentials directly on the run command using environment variables from your secrets store:

```bash theme={null}
kane-cli run "Verify checkout flow completes" \
  --url https://staging.myapp.com \
  --username "YOUR_LT_USERNAME" \
  --access-key "YOUR_LT_ACCESS_KEY" \
  --headless \
  --agent \
  --timeout 300
```

Get your username and access key from the  [dashboard](https://accounts.lambdatest.com/dashboard) > **Credentials**.

## Exit Codes

| Code | Meaning | Pipeline Behavior |
| - | - | - |
| `0` | Test passed | Pipeline continues |
| `1` | The run went ahead and did not pass, or the upload failed | Pipeline stops |
| `2` | The run did not start (sign-in, configuration, no start URL, Chrome, network), and no credits were used | Pipeline stops |
| `3` | Timeout or cancelled | Pipeline stops |

## CI/CD Checklist

* Always use `--headless` and `--agent`: no display server in CI
* Set `--timeout`: prevents pipeline hangs (e.g. `--timeout 300`)
* Set `--max-steps`: caps run length (e.g. `--max-steps 50`)
* Use `--variables-file`: load test data from a committed config file
* Store credentials as secrets: never hardcode in pipeline files

***

## Platform Guides

> [tab] GitHub Actions
>
> Store `LT_USERNAME` and `LT_ACCESS_KEY` as repository secrets under **Settings > Secrets and variables > Actions**.
>
> **Use the official action.** The Kane CLI repository ships a GitHub Action, `LambdaTest/kane-cli/integrations/github-action/kane-cli-browser-run@main`. In one step, it sets up Node.js, Chrome, and Kane CLI on the runner, signs in, and runs either one plain-English objective or every committed `_test.md` file that matches a glob as one `testrun` suite, in headless Chrome. It uploads the sealed evidence pack as an artifact kept for 30 days, links it to the hosted evidence viewer, writes the results to the job summary, posts the verdict as a pull request comment, and fails the check on any non-zero exit.
>
> ```yaml theme={null}
> # .github/workflows/kane.yml
> name: Kane browser check
> on: [pull_request]
>
> permissions:
>   contents: read
>   pull-requests: write   # lets the action post the verdict comment
>
> jobs:
>   verify-ui:
>     runs-on: ubuntu-latest
>     steps:
>       - uses: actions/checkout@v4
>       - uses: LambdaTest/kane-cli/integrations/github-action/kane-cli-browser-run@main
>         with:
>           objective: "Go to https://staging.myapp.com/pricing and verify the Pro plan card shows a monthly price"
>           username: ${{ secrets.LT_USERNAME }}
>           access-key: ${{ secrets.LT_ACCESS_KEY }}
> ```
>
> To run your committed tests instead, replace `objective` with `test-files: "tests/**/*_test.md"`, and set `parallel` for more Chrome workers. `@main` tracks the latest action. Pin a release tag of the kane-cli repository for a fixed version. Pull requests from forks get a read-only token, so the action skips the comment there. All inputs, outputs, and example workflows are in the [action's README](https://github.com/LambdaTest/kane-cli/tree/main/integrations/github-action/kane-cli-browser-run).
>
> **Or call Kane CLI yourself.** This workflow installs Chrome and Kane CLI, runs one objective, and uploads the evidence:
>
> ```yaml theme={null}
> # .github/workflows/browser-tests.yml
> name: Browser Tests
> on: [push, pull_request]
>
> jobs:
>   kane-tests:
>     runs-on: ubuntu-latest
>     steps:
>       - uses: actions/checkout@v4
>
>       - uses: actions/setup-node@v4
>         with:
>           node-version: '20'
>
>       - name: Install Chrome
>         uses: browser-actions/setup-chrome@v1
>
>       - name: Install Kane CLI
>         run: npm install -g @testmuai/kane-cli
>
>       - name: Run browser tests
>         env:
>           LT_USERNAME: ${{ secrets.LT_USERNAME }}
>           LT_ACCESS_KEY: ${{ secrets.LT_ACCESS_KEY }}
>         run: |
>           kane-cli run \
>             "Search for 'wireless headphones' on Amazon and open the first result" \
>             --name amazon-search \
>             --headless \
>             --timeout 300 \
>             --username "$LT_USERNAME" \
>             --access-key "$LT_ACCESS_KEY" \
>             --variables-file ./tests/variables.json
>
>       - name: Upload evidence
>         if: always()
>         uses: actions/upload-artifact@v4
>         with:
>           name: kane-evidence
>           path: |
>             .testmuai/evidence/
>             **/output-*/Result.md
>           include-hidden-files: true
> ```
>
> In this workflow, `--name` puts the run's sealed evidence pack in `.testmuai/evidence/`, where the upload step finds it. When the job runs saved tests with `testmd run` or `testrun run`, `**/output-*/Result.md` adds each test's report, which names the step that failed and why. `include-hidden-files: true` is required, because `upload-artifact` skips files inside folders whose names start with a dot, such as `.testmuai/`.

> [tab] GitLab CI
>
> Define `LT_USERNAME` and `LT_ACCESS_KEY` as masked CI/CD variables in your project settings.
>
> ```yaml theme={null}
> # .gitlab-ci.yml
> stages:
>   - test
>
> kane-cli:
>   stage: test
>   image: node:20
>   before_script:
>     - apt-get update && apt-get install -y wget gnupg
>     - wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
>     - echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list
>     - apt-get update && apt-get install -y google-chrome-stable
>     - npm install -g @testmuai/kane-cli
>   script:
>     - |
>       kane-cli run "Verify the homepage loads and the login button is visible" \
>         --name homepage-check \
>         --headless \
>         --timeout 300 \
>         --username "$LT_USERNAME" \
>         --access-key "$LT_ACCESS_KEY" \
>         --variables-file ./tests/variables.json
>   variables:
>     LT_USERNAME: $LT_USERNAME
>     LT_ACCESS_KEY: $LT_ACCESS_KEY
>   artifacts:
>     paths:
>       - .testmuai/evidence/
>     when: always
>     expire_in: 7 days
> ```

> [tab] Jenkins
>
> Store credentials in **Jenkins > Manage Jenkins > Manage Credentials**. The two `credentials(...)` IDs refer to Username/Password or Secret Text credentials configured in Jenkins.
>
> ```groovy theme={null}
> // Jenkinsfile
> pipeline {
>     agent any
>     environment {
>         LT_USERNAME   = credentials('lt-username')
>         LT_ACCESS_KEY = credentials('lt-access-key')
>     }
>     stages {
>         stage('Install') {
>             steps {
>                 sh 'npm install -g @testmuai/kane-cli'
>             }
>         }
>         stage('Run kane-cli') {
>             steps {
>                 sh '''
>                     kane-cli run "Sign in and confirm the dashboard renders" \
>                         --name dashboard-check \
>                         --headless \
>                         --timeout 300 \
>                         --username "$LT_USERNAME" \
>                         --access-key "$LT_ACCESS_KEY" \
>                         --variables-file ./tests/variables.json
>                 '''
>             }
>         }
>     }
>     post {
>         always {
>             archiveArtifacts artifacts: '.testmuai/evidence/*.evidence',
>                              allowEmptyArchive: true
>         }
>     }
> }
> ```
>
> The pipeline fails on any non-zero exit code from the `sh` step, which matches Kane CLI's exit-code semantics.

> [tab] Bitbucket Pipelines
>
> Add `LT_USERNAME` and `LT_ACCESS_KEY` as repository variables under **Repository settings > Repository variables**.
>
> ```yaml theme={null}
> # bitbucket-pipelines.yml
> pipelines:
>   default:
>     - step:
>         name: Browser Tests
>         image: node:20
>         script:
>           - npm install -g @testmuai/kane-cli
>           - kane-cli run
>               --url https://staging.myapp.com
>               --name checkout-flow
>               --username $LT_USERNAME
>               --access-key $LT_ACCESS_KEY
>               --headless
>               --agent
>               --timeout 300
>               --max-steps 50
>               "Complete the checkout flow and verify order confirmation"
>         artifacts:
>           - .testmuai/evidence/**
> ```

> [tab] Docker / Generic
>
> The shell command below works in any CI that can run a Linux container with Chrome installed:
>
> ```bash theme={null}
> kane-cli run "Open the pricing page and verify the Pro plan is listed" \
>     --headless \
>     --timeout 300 \
>     --username "$LT_USERNAME" \
>     --access-key "$LT_ACCESS_KEY" \
>     --variables-file ./tests/variables.json
> ```
>
> If your CI image cannot install Chrome, for example a minimal Node Alpine image, either run a whole suite on the grid with `kane-cli testrun run … --remote` (see [Remote Runs](https://www.testmuai.com/docs/kane-cli-remote-execution)), or point a single run at a remote browser:
>
> ```bash theme={null}
> kane-cli run "Open the pricing page and verify the Pro plan is listed" \
>     --headless \
>     --timeout 300 \
>     --ws-endpoint "$LT_BROWSER_WSS" \
>     --username "$LT_USERNAME" \
>     --access-key "$LT_ACCESS_KEY" \
>     --variables-file ./tests/variables.json
> ```
>
> `--cdp-endpoint <url>` works the same way for browsers that expose a Chrome DevTools Protocol URL. With either flag, Kane CLI skips its own Chrome launch and connects to the endpoint you provide.

***

## Running Multiple Tests

> [!TIP]
>
> If your repo has committed `_test.md` tests, prefer one `testrun` invocation over a shell loop. The suite becomes one execution with one exit code and one sealed evidence pack. See [Batch Runs (testrun)](https://www.testmuai.com/docs/kane-cli-testrun#using-testrun-in-ci).

Run several tests and fail the pipeline if any fail:

```bash theme={null}
#!/bin/bash
set -e

PASS=0
FAIL=0
FAILED_TESTS=()

run_test() {
  local name="$1"
  local objective="$2"
  echo "Running: $name"
  if kane-cli run "$objective" \
      --url https://staging.myapp.com \
      --username $LT_USERNAME \
      --access-key $LT_ACCESS_KEY \
      --headless --agent --timeout 120; then
    ((PASS++))
  else
    ((FAIL++))
    FAILED_TESTS+=("$name")
  fi
}

run_test "Login" "Log in with valid credentials and verify dashboard appears"
run_test "Search" "Search for 'laptop' and verify at least one result appears"
run_test "Checkout" "Add first product to cart and complete checkout"
run_test "Settings" "Open account settings and verify profile page loads"

echo ""
echo "Results: $PASS passed, $FAIL failed"
if [[ $FAIL -gt 0 ]]; then
  echo "Failed tests: ${FAILED_TESTS[*]}"
  exit 1
fi
```

***

## Variables in CI/CD

Commit a non-secret variables file to your repo, and inject secrets at runtime:

```json theme={null}
{
  "app_url": { "value": "https://staging.myapp.com" },
  "test_product_sku": { "value": "PROD-001" }
}
```

Merge with secrets in your pipeline:

```bash theme={null}
kane-cli run "Log in as {{email}} with {{password}} and verify dashboard" \
  --variables-file ./test-variables.json \
  --variables "{\"email\": {\"value\": \"$TEST_EMAIL\"}, \"password\": {\"value\": \"$TEST_PASSWORD\", \"secret\": true}}" \
  --username $LT_USERNAME \
  --access-key $LT_ACCESS_KEY \
  --headless --agent
```

## A shared context store in CI

When your team [shares the context graph](https://www.testmuai.com/docs/kane-cli-assurance-sharing) through a shared location, your pipeline can use the same store. Clone it once, pull before each run, and push the facts the run produced afterwards. The sync commands never call the agent and never spend credits. The run between them, `kane-cli context extract` below, is an ordinary extraction and consumes credits like any other.

* **Authenticate without a person.** The method depends on the location:

  * **GitHub over HTTPS**: set `KANE_SYNC_GIT_TOKEN` from a CI secret. Use a repository-scoped personal access token with Contents read and write, or a GitHub App installation token. A workflow's own `GITHUB_TOKEN` reaches only the workflow's repository, so a separate context repository needs its own token.
  * **GitHub over SSH**: an SSH deploy key on the context repository works without a token. Kane CLI never answers an SSH prompt, so the runner must have the key loaded and the host key already accepted (`ssh-keyscan github.com >> ~/.ssh/known_hosts`).
  * For either GitHub option, repository rules must also allow the scratch reference that the connection check pushes under `refs/kane/probe/`. See [Three kinds of location](https://www.testmuai.com/docs/kane-cli-assurance-sharing#locations).
  * **S3-compatible storage**: set `KANE_SYNC_S3_ACCESS_KEY_ID` and `KANE_SYNC_S3_SECRET_ACCESS_KEY`.

  Kane CLI never writes these credentials to disk. See [Context sync environment variables](https://www.testmuai.com/docs/kane-cli-configuration#context-sync-environment-variables).
* **Use `--mode agent`** for structured output, and read the exit code:
  * `0`: done.
  * `3`: a person has to decide. The runner's store is behind or has diverged, or a rebase stopped on decisions.
  * `2`: a precondition failed. The location cannot be reached, keys are missing, or a rebase is still open.
* **Do not answer decisions blindly.** When a rebase stops on a decision, the job stops too. Save the output of `kane-cli context sync status origin --json` as a build artifact. It reports what is waiting, but a later job cannot replay it on its own, because answers must go to the store that holds the open rebase. The follow-up job must run on the same persisted `.context/`, such as a workspace or cache that survives between jobs. There, a person or an agent answers with `kane-cli context sync origin --answer <id>=<choice>`. `keep-theirs` writes nothing, but it is still a choice: the local change stays in the backup. The full contract is in [Agents and CI](https://www.testmuai.com/docs/kane-cli-assurance-automation#the-sync-verbs-on-the-stream).
* **Keep `.context/` out of version control.** The store is never merged with git. It is shared through the location instead.

```bash theme={null}
set -e   # stop at the first failing command, so every exit code below is read

# GitHub Actions step, where the secret CONTEXT_REPO_TOKEN grants Contents read and write on the context repository
export KANE_SYNC_GIT_TOKEN="$CONTEXT_REPO_TOKEN"

# first run on this runner: a store from the team location
[ -d .context ] || kane-cli context clone https://github.com/example-org/team-context.git . --mode agent

# before the run: take the team's new records, and stop the job if a person has to decide
kane-cli context pull origin --mode agent > pull.ndjson || {
  code=$?
  [ "$code" -eq 3 ] && kane-cli context sync status origin --json > sync-status.json   # the relation (behind or diverged) and any open decisions
  exit "$code"
}

# the run itself: it fails on a high-risk question and takes the recommended default for the rest; a failed or paused extraction (exit 1 or 3) ends the job here, before anything is pushed
kane-cli context extract --mode ci

# after the run: publish what landed
kane-cli context push origin --mode agent
```

## Terminal First Testing With Kane CLI

Natural language browser & mobile app tests right from terminal.

Book a Demo

![Kane CLI terminal](https://assets.testmu.ai/resources/images/Terminal-KaneCLI.png)

×

Schedule Your Personal Demo

<form>
  Select Product

  Schedule Demo
</form>


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