CI/CD Integration
Kane CLI runs headlessly in CI/CD pipelines using credentials passed as environment variables or inline flags. Tests fail fast on assertion errors and return standard exit codes for pipeline control flow.
Common Patterns
These patterns apply to every CI system; the platform-specific recipes below differ only in how they wire up the secrets.
-
Always pass
--headless. CI runners have no display. -
Always set
--timeout <seconds>. A hung run cannot be allowed to block the pipeline. -
Authenticate with
--usernameand--access-keyfrom CI secrets. Do not callkane-cli loginin CI. That flow opens a browser for OAuth and will not work on a runner. -
Load test data with
--variables-file <path>. Check the file into your repo (without secret values), or generate it before the step. A{{name}}with no value fails the job with exit2before any browser starts, with a receipt naming the variable, so fill values from CI secrets in that step, not later. -
Run the suite on the cloud grid when the runner cannot run it.
kane-cli testrun run … --remoteturns the suite into one HyperExecute job: the grid supplies Chrome on a macOS runner, or, for mobile_test.mdmembers, a virtual Android emulator or iOS simulator, so the runner needs no Chrome, Xcode or Android Studio, and--parallel Nspreads the members across N grid runners. The recordings and the evidence pack come back to the checkout as if the suite had run on your machine.kane-cli plugin install remote-execution
# a web suite on 4 grid runners
kane-cli testrun run tests/web/ --remote --parallel 4 \
--username "$LT_USERNAME" --access-key "$LT_ACCESS_KEY" \
--on-failure fail-fast
# a mobile suite, from a Linux runner
kane-cli testrun run tests/app/ --remote \
--device-name "Pixel 7" --os-version 14 \
--username "$LT_USERNAME" --access-key "$LT_ACCESS_KEY" \
--on-failure fail-fastIt needs a TestMu AI plan with HyperExecute macOS runners. Web and mobile members go in separate runs. Pick devices with
kane-cli devices list --target emulator|simulator --remote, and allow a timeout of about 10 minutes. See Remote Runs. -
Check the exit code.
0passed,1failed,2error,3timeout or cancellation.
The runner spawns Chrome itself, so the CI image must have Chrome available on PATH. If your runner image cannot install Chrome, you have two options. For a single kane-cli run or testmd run, point Kane CLI at a remote browser with --cdp-endpoint <url> or --ws-endpoint <url> (for example, a TestMu AI wss:// endpoint), where Kane CLI still runs on the runner and drives that browser. For a whole suite, kane-cli testrun run … --remote moves the run itself to the grid, as above.
Authentication in CI/CD
Pass credentials directly on the run command using environment variables from your secrets store:
kane-cli run "Verify checkout flow completes" \
--url https://staging.myapp.com \
--username "undefined" \
--access-key "undefined" \
--headless \
--agent \
--timeout 300
Get your username and access key from the TestMu AI dashboard > Credentials.
Exit Codes
| Code | Meaning | Pipeline Behavior |
|---|---|---|
0 | Test passed | Pipeline continues |
1 | Test failed (assertion not met) | Pipeline stops |
2 | Error (auth failure, Chrome crash) | Pipeline stops |
3 | Timeout or cancelled | Pipeline stops |
CI/CD Checklist
- Always use
--headlessand--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
- GitHub Actions
- GitLab CI
- Jenkins
- Bitbucket Pipelines
- Docker / Generic
Store LT_USERNAME and LT_ACCESS_KEY as repository secrets under Settings > Secrets and variables > Actions.
# .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" \
--headless \
--timeout 300 \
--username "$LT_USERNAME" \
--access-key "$LT_ACCESS_KEY" \
--variables-file ./tests/variables.json
- name: Upload test logs
if: always()
uses: actions/upload-artifact@v4
with:
name: kane-test-logs
path: ~/.testmuai/kaneai/sessions/
Define LT_USERNAME and LT_ACCESS_KEY as masked CI/CD variables in your project settings.
# .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" \
--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/kaneai/sessions/
when: always
expire_in: 7 days
Store credentials in Jenkins > Manage Jenkins > Manage Credentials. The two credentials(...) IDs refer to Username/Password or Secret Text credentials configured in Jenkins.
// 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" \
--headless \
--timeout 300 \
--username "$LT_USERNAME" \
--access-key "$LT_ACCESS_KEY" \
--variables-file ./tests/variables.json
'''
}
}
}
post {
always {
archiveArtifacts artifacts: '~/.testmuai/kaneai/sessions/**',
allowEmptyArchive: true
}
}
}
The pipeline fails on any non-zero exit code from the sh step, which matches Kane CLI's exit-code semantics.
Add LT_USERNAME and LT_ACCESS_KEY as repository variables under Repository settings > Repository variables.
# 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
--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/kaneai/sessions/**
The shell command below works in any CI that can run a Linux container with Chrome installed:
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), or point a single run at a remote browser:
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
Run several tests and fail the pipeline if any fail:
#!/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:
{
"app_url": { "value": "https://staging.myapp.com" },
"test_product_sku": { "value": "PROD-001" }
}
Merge with secrets in your pipeline:
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 through a location, a pipeline works on the same store: clone it once, pull before each run, and push the facts the run produced after. The sync commands never call the agent or spend credits, while the run between them, kane-cli context extract below, is an ordinary extraction and consumes credits like any other.
- Sign in without a person. For a GitHub location over HTTPS, set
KANE_SYNC_GIT_TOKENfrom a CI secret: a repository-scoped personal access token with Contents read and write, or a GitHub App installation token. A workflow's ownGITHUB_TOKENonly reaches the workflow's repository, so a separate context repository needs its own token. Over SSH, an SSH deploy key on the context repository works with no token. Kane CLI never answers an SSH prompt, so the runner must have the key loaded and the host's key already accepted (ssh-keyscan github.com >> ~/.ssh/known_hosts). Repository rules must also allow the connection check's scratch reference underrefs/kane/probe/, see Three kinds of location. For an S3-compatible location, setKANE_SYNC_S3_ACCESS_KEY_IDandKANE_SYNC_S3_SECRET_ACCESS_KEY. Neither is ever written to disk by Kane CLI. See Context sync environment variables. - Use
--mode agentfor structured output, and read the exit code:0done,3a person has to decide (the runner is behind or diverged, or a rebase stopped on decisions),2a precondition (the location cannot be reached, keys missing, a rebase still open). - Do not answer decisions blindly. A rebase that stops on a decision is a job that stops. Save
kane-cli context sync status origin --jsonas a build artifact: it is a report of what is waiting, not something a later job can replay on its own, because answers go to the store that holds the open rebase. The follow-up job must run on the same persisted.context/, a workspace or cache that survives between jobs, where a person or an agent answers withkane-cli context sync origin --answer <id>=<choice>.keep-theirswrites nothing, but it is a choice: the local change stays in the backup. The full contract is in Agents and CI. - Keep
.context/out of version control. The store never goes through a git merge, and the location is where it is shared.
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
exit "$code"
}
# the run itself: it fails on a high-risk question and takes the recommended default for the rest
kane-cli context extract --mode ci
# after the run: publish what landed
kane-cli context push origin --mode agent
