Run TestDriver tests in CI/CD with parallel execution and cross-platform support
npx skills add https://github.com/testdriverai/testdriverai --skill testdriver:ci-cd
<!-- Generated from ci-cd.mdx. DO NOT EDIT. -->
TestDriver integrates seamlessly with popular CI providers, enabling automated end-to-end testing on every push and pull request.
On GitHub Actions, prefer OIDC via the published testdriverai/action —
there's no TD_API_KEY secret to store, copy, or rotate. The action proves the
workflow is running inside your org and TestDriver exchanges that proof for your
team's key at run time. See the GitHub Actions tab below.
For other CI providers (or self-hosted runners without OIDC), fall back to a
stored API key from console.testdriver.ai/team,
added as a TD_API_KEY secret in your CI provider's settings.
<Note>
Never commit your API key directly in code. Always use OIDC or your CI provider's secrets management.
</Note>
<Tabs>
<Tab title="GitHub Actions">
testdriverai/action (recommended)Use the published testdriverai/action — it mints the OIDC token, exchanges it for your team's API key, and exports TD_API_KEY for the steps that follow. No TD_API_KEY secret to store or rotate.
<Note>
One-time setup: authorize the TestDriver GitHub App for your org so the org → team binding exists. If your org authorized the App before OIDC support shipped, re-authorize once. If the App isn't authorized, the action fails with a console link (or falls back to the api-key secret if you provide one).
</Note>
name: TestDriver Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
permissions:
id-token: write # REQUIRED to mint an OIDC token
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Authenticate to TestDriver
uses: testdriverai/action@stable # pin @stable / @canary / @test to your SDK channel
with:
api-key: ${{ secrets.TD_API_KEY }} # optional fallback if OIDC isn't set up
- name: Run TestDriver tests
run: npx vitest run
Only if you can't use OIDC (e.g. self-hosted runners without an OIDC provider). Add the key as a secret and pass it via env:
TD_API_KEY, Value: your API keyCreate .github/workflows/testdriver.yml:
name: TestDriver Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run TestDriver tests
env:
TD_API_KEY: ${{ secrets.TD_API_KEY }}
run: vitest --run
Use matrix strategy to run tests in parallel:
name: TestDriver Tests (Parallel)
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run tests (shard ${{ matrix.shard }}/4)
env:
TD_API_KEY: ${{ secrets.TD_API_KEY }}
run: vitest --run --shard=${{ matrix.shard }}/4
name: TestDriver Tests (Multi-Platform)
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
td-os: [linux, windows]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run tests on ${{ matrix.td-os }}
env:
TD_API_KEY: ${{ secrets.TD_API_KEY }}
TD_OS: ${{ matrix.td-os }}
run: vitest --run
</Tab>
<Tab title="GitLab CI">
TD_API_KEY, Value: your API keyCreate .gitlab-ci.yml:
stages:
- test
testdriver:
stage: test
image: node:20
cache:
paths:
- node_modules/
script:
- npm ci
- vitest --run
variables:
TD_API_KEY: $TD_API_KEY
stages:
- test
.testdriver-base:
stage: test
image: node:20
cache:
paths:
- node_modules/
before_script:
- npm ci
variables:
TD_API_KEY: $TD_API_KEY
testdriver-shard-1:
extends: .testdriver-base
script:
- vitest --run --shard=1/4
testdriver-shard-2:
extends: .testdriver-base
script:
- vitest --run --shard=2/4
testdriver-shard-3:
extends: .testdriver-base
script:
- vitest --run --shard=3/4
testdriver-shard-4:
extends: .testdriver-base
script:
- vitest --run --shard=4/4
stages:
- test
.testdriver-base:
stage: test
image: node:20
cache:
paths:
- node_modules/
before_script:
- npm ci
variables:
TD_API_KEY: $TD_API_KEY
testdriver-linux:
extends: .testdriver-base
variables:
TD_OS: linux
script:
- vitest --run
testdriver-windows:
extends: .testdriver-base
variables:
TD_OS: windows
script:
- vitest --run
</Tab>
<Tab title="CircleCI">
TD_API_KEY, Value: your API keyCreate .circleci/config.yml:
version: 2.1
jobs:
test:
docker:
- image: cimg/node:20.0
steps:
- checkout
- restore_cache:
keys:
- npm-deps-{{ checksum "package-lock.json" }}
- run: npm ci
- save_cache:
key: npm-deps-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run:
name: Run TestDriver tests
command: vitest --run
environment:
TD_API_KEY: ${TD_API_KEY}
workflows:
test:
jobs:
- test
version: 2.1
jobs:
test:
docker:
- image: cimg/node:20.0
parallelism: 4
steps:
- checkout
- restore_cache:
keys:
- npm-deps-{{ checksum "package-lock.json" }}
- run: npm ci
- save_cache:
key: npm-deps-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run:
name: Run TestDriver tests
command: |
vitest --run --shard=$((CIRCLE_NODE_INDEX + 1))/$CIRCLE_NODE_TOTAL
environment:
TD_API_KEY: ${TD_API_KEY}
workflows:
test:
jobs:
- test
version: 2.1
jobs:
test:
docker:
- image: cimg/node:20.0
parameters:
td-os:
type: string
steps:
- checkout
- run: npm ci
- run:
name: Run TestDriver tests on << parameters.td-os >>
command: vitest --run
environment:
TD_API_KEY: ${TD_API_KEY}
TD_OS: << parameters.td-os >>
workflows:
test:
jobs:
- test:
td-os: linux
- test:
td-os: windows
</Tab>
<Tab title="Azure Pipelines">
TD_API_KEY with your API keyCreate azure-pipelines.yml:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
displayName: 'Setup Node.js'
- script: npm ci
displayName: 'Install dependencies'
- script: vitest --run
displayName: 'Run TestDriver tests'
env:
TD_API_KEY: $(TD_API_KEY)
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
shard1:
SHARD: '1/4'
shard2:
SHARD: '2/4'
shard3:
SHARD: '3/4'
shard4:
SHARD: '4/4'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npm ci
displayName: 'Install dependencies'
- script: vitest --run --shard=$(SHARD)
displayName: 'Run TestDriver tests'
env:
TD_API_KEY: $(TD_API_KEY)
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
linux:
TD_OS: 'linux'
windows:
TD_OS: 'windows'
steps:
- task: NodeTool@0
inputs:
versionSpec: '20.x'
- script: npm ci
displayName: 'Install dependencies'
- script: vitest --run
displayName: 'Run TestDriver tests on $(TD_OS)'
env:
TD_API_KEY: $(TD_API_KEY)
TD_OS: $(TD_OS)
</Tab>
<Tab title="Jenkins">
td-api-key, Secret: your API keyCreate Jenkinsfile:
pipeline {
agent {
docker {
image 'node:20'
}
}
environment {
TD_API_KEY = credentials('td-api-key')
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Test') {
steps {
sh 'vitest --run'
}
}
}
}
pipeline {
agent none
environment {
TD_API_KEY = credentials('td-api-key')
}
stages {
stage('Test') {
parallel {
stage('Shard 1') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'vitest --run --shard=1/4'
}
}
stage('Shard 2') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'vitest --run --shard=2/4'
}
}
stage('Shard 3') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'vitest --run --shard=3/4'
}
}
stage('Shard 4') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'vitest --run --shard=4/4'
}
}
}
}
}
}
pipeline {
agent none
environment {
TD_API_KEY = credentials('td-api-key')
}
stages {
stage('Test') {
parallel {
stage('Linux') {
agent { docker { image 'node:20' } }
environment {
TD_OS = 'linux'
}
steps {
sh 'npm ci'
sh 'vitest --run'
}
}
stage('Windows') {
agent { docker { image 'node:20' } }
environment {
TD_OS = 'windows'
}
steps {
sh 'npm ci'
sh 'vitest --run'
}
}
}
}
}
}
</Tab>
</Tabs>
When using multi-platform testing, read the TD_OS environment variable in your test:
import { describe, expect, it } from "vitest";
import { TestDriver } from "testdriverai/vitest/hooks";
describe("Cross-platform tests", () => {
it("should work on both Linux and Windows", async (context) => {
const os = process.env.TD_OS || 'linux';
const testdriver = TestDriver(context, {
os: os // 'linux' or 'windows'
});
await testdriver.provision.chrome({
url: 'https://example.com',
});
const result = await testdriver.assert("the page loaded successfully");
expect(result).toBeTruthy();
});
});
Your plan allows a fixed number of sandboxes running at once. When a test asks for
a sandbox and you're already at that limit, the request is queued rather than
failed immediately: the SDK waits for a slot to free up, retrying every 10 seconds,
then proceeds automatically once one opens. This is what lets a parallel CI matrix
(many jobs starting at once) work on a plan with fewer slots than jobs — the extra
jobs simply wait their turn instead of erroring.
By default the SDK waits up to 60 seconds for a slot before giving up with a
concurrency-limit error. Control that ceiling with TD_CONCURRENCY_MAX_WAIT:
| Value | Behavior |
| ----- | -------- |
| _unset_ | Wait up to 60 seconds (the default). |
| TD_CONCURRENCY_MAX_WAIT=300 | Wait up to 300 seconds (5 minutes) before giving up. |
| TD_CONCURRENCY_MAX_WAIT=0 | Don't queue — fail on the first denial. |
The value is in seconds (fractional values are allowed and rounded to the
nearest millisecond). Any invalid or negative value falls back to the 60-second
default. The wait applies per sandbox request, across both the initial allocation
and the realtime slot-approval handshake.
# Example: a large parallel matrix that may queue for a while.
# Give each job up to 5 minutes to acquire a slot before failing.
- name: Run TestDriver tests
env:
TD_API_KEY: ${{ secrets.TD_API_KEY }}
TD_CONCURRENCY_MAX_WAIT: "300"
run: npx vitest run
<Tip>
Raise TD_CONCURRENCY_MAX_WAIT when you run more parallel jobs than your plan has
slots and would rather they queue than fail. Set it to 0 when you'd prefer a job
to fail fast on a busy account (e.g. a quick smoke test that shouldn't sit
waiting). When jobs routinely give up waiting, that's the signal to
</Tip>
All test runs are automatically recorded and visible in your TestDriver dashboard at console.testdriver.ai:
Assess Kubernetes workloads and cluster configuration for AKS Automatic compatibility. Identifies incompatibilities, generates fixes, and guides migration from AKS Standard to AKS Automatic. WHEN: migrate to AKS Automatic, check AKS Automatic readiness, validate manifests for Automatic, assess cluster for Automatic compatibility, fix deployment for Automatic compatibility, identify AKS Automatic migration blockers, is my cluster ready for AKS Automatic.
Discovers available Azure OpenAI model capacity across regions and projects. Analyzes quota limits, compares availability, and recommends optimal deployment locations based on capacity requirements. USE FOR: find capacity, check quota, where can I deploy, capacity discovery, best region for capacity, multi-project capacity search, quota analysis, model availability, region comparison, check TPM availability. DO NOT USE FOR: actual deployment (hand off to preset or customize after discovery), quota increase requests (direct user to Azure Portal), listing existing deployments.
Interactive guided deployment flow for Azure OpenAI models with full customization control. Step-by-step selection of model version, SKU (GlobalStandard/Standard/ProvisionedManaged), capacity, RAI policy (content filter), and advanced options (dynamic quota, priority processing, spillover). USE FOR: custom deployment, customize model deployment, choose version, select SKU, set capacity, configure content filter, RAI policy, deployment options, detailed deployment, advanced deployment, PTU deployment, provisioned throughput. DO NOT USE FOR: quick deployment to optimal region (use preset).
Unified Azure OpenAI model deployment skill with intelligent intent-based routing. Handles quick preset deployments, fully customized deployments (version/SKU/capacity/RAI policy), and capacity discovery across regions and projects. USE FOR: deploy model, deploy gpt, create deployment, model deployment, deploy openai model, set up model, provision model, find capacity, check model availability, where can I deploy, best region for model, capacity analysis. DO NOT USE FOR: listing existing deployments (use foundry_models_deployments_list MCP tool), deleting deployments, agent creation (use agent/create), project creation (use project/create).
Intelligently deploys Azure OpenAI models to optimal regions by analyzing capacity across all available regions. Automatically checks current region first and shows alternatives if needed. USE FOR: quick deployment, optimal region, best region, automatic region selection, fast setup, multi-region capacity check, high availability deployment, deploy to best location. DO NOT USE FOR: custom SKU selection (use customize), specific version selection (use customize), custom capacity configuration (use customize), PTU deployments (use customize).
This skill should be used when working with LaminDB, an open-source data framework for biology that makes data queryable, traceable, reproducible, and FAIR. Use when managing biological datasets (scRNA-seq, spatial, flow cytometry, etc.), tracking computational workflows, curating and validating data with biological ontologies, building data lakehouses, or ensuring data lineage and reproducibility in biological research. Covers data management, annotation, ontologies (genes, cell types, diseases, tissues), schema validation, integrations with workflow managers (Nextflow, Snakemake) and MLOps platforms (W&B, MLflow), and deployment strategies.
Latch platform for bioinformatics workflows. Build pipelines with Latch SDK, @workflow/@task decorators, deploy serverless workflows, LatchFile/LatchDir, Nextflow/Snakemake integration.
Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.
Take testdriverai/testdriver:ci-cd from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.