# Importing test plans into test-lab with an AI agent

This guide is for an AI coding agent (Claude Code, Codex, Cursor, etc.) asked to
move a user's existing tests into test-lab.ai. You do the format translation; the
`testlab` CLI handles auth and upload.

**Quickest reference: run `testlab examples`** - it prints the exact JSON shape
for every resource (credentials, labels, data fixtures, plans, pre-steps).

## Install (zero-dependency)

```bash
npx @test-lab-ai/cli --help
# or a global `testlab` command: npm i -g @test-lab-ai/cli
```

## The workflow

1. **Read** whatever the user already has: Playwright/Cypress specs, Cucumber
   `.feature` files, a TestRail/Zephyr export, a spreadsheet, or a prose doc.
2. **Convert** each test into a plan object (schema below). test-lab tests are
   natural language, not code:
   - Put the explicit, fully-qualified URL in the `prompt` (imported plans have
     no project, so there is no base URL to inherit).
   - Replace any secret (passwords, tokens, test-account logins) with a
     `{{credentials.<key>}}` placeholder, and collect the real values into the
     top-level `credentials` array.
   - Write clear pass/fail expectations into the prompt ("Confirm the dashboard
     loads", "Expect an order-confirmation page with an order number").
   - For generated/randomized data (a unique email per run, a random name),
     define a **data fixture** and reference it as `{{data.<fixture>.<field>}}`
     (see below).
   - If the user's repo has a test-lab plan skill/format, prefer it.
3. **Write** the plans to a JSON file (or a directory of `*.json`).
4. **Run** `testlab import <path>`. Use `--dry-run` first to show the user the
   creation order without writing anything. To file the plans under a project,
   run `testlab projects list` and pass `--project <id|name>` (auto-picked if
   there is only one; omit for account-level).

Authentication: the user runs `testlab login` once (browser), or you set
`TESTLAB_API_KEY` in the environment for a fully headless run. For several
accounts (e.g. one per client), each is a named profile: `testlab login
--profile <name>`, selected per command with `--profile`, per shell with
`$TESTLAB_PROFILE`, or per folder with a `./.test-lab.json` (`{ "profile":
"<name>" }`, walked up from the cwd). `testlab auth list` shows them and which
is active. Precedence: `--key` > `--profile` > `$TESTLAB_API_KEY` >
`$TESTLAB_PROFILE` > `./.test-lab.json` > the default profile.

## Plan object schema

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `name` | string | yes | Max 200 chars |
| `prompt` | string | yes | The test in natural language, with explicit URL(s) and `{{credentials.<key>}}`. Max 32 KB |
| `ref` | string | no | A handle unique within this import, used only to wire pre-steps (see below) |
| `testType` | `"quickTest"` \| `"deepTest"` | no | Quick is a fast smoke; deep is more thorough |
| `agentType` | string | no | `functional` (default), `accessibility`, `uiux`, `exploratory`, `performance`, `security` |
| `devices` | string[] | no | e.g. `["Desktop Chrome"]` (default), `["iPhone 15 Pro"]` |
| `labels` | (string \| number)[] | no | Names auto-create on the account; ids reuse existing labels. Max 25 |
| `preSteps` | object[] | no | Pipeline dependencies. Max 25. See below |
| `failOnPreStepFailure` | boolean | no | Default `true` |
| `cookies` | `{name,value,domain}[]` | no | Injected at run time |
| `headers` | `{name,value}[]` | no | Injected at run time |

## File shapes

Any of these is valid input to `testlab import`:

```jsonc
// 1. a single plan
{ "name": "...", "prompt": "..." }

// 2. an array of plans
[ { "name": "...", "prompt": "..." }, { "name": "...", "prompt": "..." } ]

// 3. a bundle: any of credentials / labels / fixtures / plans
//    (created in that order; plans are topo-sorted by their pre-step ref)
{
  "credentials": [ { "key": "password", "value": "hunter2" } ],
  "labels": ["smoke"],
  "fixtures": [ { "key": "newUser", "fields": [ { "key": "email", "mode": "dynamic", "generator": "internet.email" } ] } ],
  "plans": [ { "ref": "signup", "name": "...", "prompt": "Register with {{data.newUser.email}} / {{credentials.password}} ..." } ]
}
```

A directory imports every `*.json` file inside it (sorted by filename).

## Pre-steps and ordering

A pre-step makes one plan run after another, sharing browser state (a login that
runs before a checkout). Reference the dependency in one of three ways:

```jsonc
{ "ref": "login" }            // another plan IN THIS IMPORT, by its ref (preferred)
{ "name": "Existing Login" }  // a plan that already exists in the account, by name
{ "testPlanId": 42 }          // an existing plan by id
```

You do NOT need to order the plans array yourself: the CLI topologically sorts by
`ref` dependencies and creates them in the correct order, then wires each
pre-step to the concrete id it just created. Cycles, self-references, and a `ref`
that matches no plan are rejected before anything is written.

## Credentials

Put real secret values in the top-level `credentials` array; reference them in
prompts (and cookie/header values) as `{{credentials.<key>}}`. Keys start with a
letter and use letters/numbers/underscores only. The CLI upserts credentials
before creating plans, and values are stored encrypted (never echoed back).

## Data fixtures (generated test data)

When a test needs fresh/randomized data, define a fixture in the top-level
`fixtures` array and reference its fields as `{{data.<fixtureKey>.<fieldKey>}}`
in prompts. A fixture is `{ key, label?, fields: [...] }`; each field is either
**static** (a literal `value`, may template `{{run.shortId}}`) or **dynamic**
(`"mode": "dynamic"` + a `generator` rolled fresh every run):

```jsonc
{
  "key": "newUser",
  "fields": [
    { "key": "email", "mode": "dynamic", "generator": "internet.email" },
    { "key": "plan",  "mode": "static",  "value": "pro" }
  ]
}
```

Generators include `internet.email`, `person.firstName`, `person.fullName`,
`string.uuid`, `number.int`, `company.name`, ... - run `testlab examples` for the
full list. Reference: `{{data.newUser.email}}`. Keys: start with a letter,
letters/digits/underscores, max 50 chars.

## End-to-end example

```bash
# 1. (user, once) authenticate
testlab login                      # or: export TESTLAB_API_KEY=tl_xxxxx

# 2. you write ./tests/*.json from the user's existing suite (see examples/plans.json)

# 3. preview, then import
testlab import ./tests --dry-run
testlab import ./tests
```

## Notes

- Plans are created fresh every run (no de-duplication). Run `testlab plans list`
  first if you need to avoid creating duplicates of plans already in the account.
- Imported plans have no project and no geolocation proxy; set those in the
  dashboard afterward if the user needs them.
- Prefer calling the CLI over hand-rolling HTTP. If you must call the API
  directly, it is documented at https://test-lab.ai/docs/api/test-plans and uses
  the same `Authorization: Bearer tl_...` key.
