Skip to main content
testmd lets you write browser tests as Markdown files (_test.md) and commit them to your repo. On the first run, the AI agent authors each step and saves a recording. On every subsequent run, each step replays from cache with no LLM cost and much faster execution. Commit the test file and its recordings to git so teammates and CI can re-run the same tests without re-authoring. A _test.md file is a kane-cli test you can commit to git. It captures the same kind of natural-language objective you would type into kane-cli run, but in a structured Markdown file that:
  • Lives in your repository alongside the code it tests.
  • Reads as a human-friendly description of what the test does.
  • Splits a flow into named steps you can edit independently.
  • Caches each step on first run, so a passing test replays in seconds the next time you run it.
  • Composes: one test can @import another file to reuse a flow such as login.
This page covers the file format end-to-end. To run a test once you have written one, see Running test.md files. To reuse flows across tests, see Composing tests with @import.

Quick Start

Step 1: Create a test file. The filename must end in _test.md:
Step 2: Run the test:
The agent launches Chrome, works through each step in order, writes a result file to output-amazon/Result.md, and exits 0 if everything passed. On the first run, the agent authors each step and caches the recording. On every later run, the steps replay from cache instantly.
Always combine --agent with --headless in CI/CD environments to avoid display server errors.

When to Use testmd vs run

kane-cli run is one-shot. It runs an objective, uploads results, and exits. It is ideal for quick, one-off verifications like checking if a page loads correctly or extracting a value from a live site. kane-cli testmd run is for tests you want to persist. Use it when you are building a login flow smoke test, a regression suite, or any test you plan to re-run across builds. The test file lives in your repo, recordings are cached and committed alongside it, and every subsequent run replays from cache without consuming LLM credits. Teammates and CI pick up the same recordings and replay them identically. If you run an ad-hoc objective with kane-cli run and later decide you want to keep it, use the --name flag to save it as a _test.md file (see Recording a Test from a Live Session below).

File Format

A _test.md file has four parts in order:
  1. Frontmatter: run-wide settings inside --- ... --- at the top of the file.
  2. ## Step heading: every step starts with an H2. Anything before the first ## is silently ignored, so the optional # Title is decorative.
  3. Optional yaml block: fenced ```yaml ... ``` immediately under the step heading, overriding the frontmatter for that step only.
  4. Step body: either a prose objective or a single @import <path> line. Not both.

File name

Files that end in _test.md are tests. They are valid entry points for kane-cli testmd run. Files that do not end in _test.md are helpers and can only be reached through @import from a test. See Composing tests with @import.

YAML Frontmatter

Frontmatter is YAML inside --- fences at the top of the file. Every key is optional:
Supported keys: Keys not in this table are rejected with unknown config key: <key> at parse time. Authentication is set via CLI flags or your active profile, never in frontmatter.

Mobile Target

target: also accepts four mobile values: emulator for a virtual Android device, simulator for a virtual iOS device, and android or ios for a real device in the TestMu AI cloud. The app under test is its own root key:
  • target: emulator runs on an Android emulator, simulator runs on an iOS simulator, and android or ios runs on a real device. You do not set the platform separately; the target determines it.
  • app: the app under test. It is required with a mobile target and rejected with a browser target. Use a build path (emulator .apk, simulator .zip, android .apk or .aab, ios .ipa) or an uploaded app id, which is APP followed by six or more digits. On-device package ids are not accepted.
  • no_reset: optional. Keeps the app’s existing state between runs instead of resetting it. Refused with android or ios, because a real-device session always starts with a fresh install.
  • device_name / os_version: optional default device for this test. Use the names exactly as kane-cli devices list --target <kind> prints them (local), or kane-cli devices list --target <kind> --remote (grid catalog, or real devices). The run flags --device-name / --os-version override them. On an emulator or simulator, if you set a name, also set a version. On a real device, you can set either one alone, and a name can be a pattern (Pixel.*). If you set neither, the hub (the TestMu AI service that assigns real devices) picks any free device.
The nested form, target: {platform, app}, is not accepted. The parser rejects it and shows the flat form above in its error message.
Run mobile tests with kane-cli testmd run, or in batches with testrun. Running emulator and simulator tests locally requires macOS on Apple Silicon; see Mobile Testing for setup. With testrun run --remote, the suite runs on a grid emulator or simulator, and you can start it from any machine. Simulator members accept a local .zip build or an APP… id in app:. Real-device tests run from any machine with no setup beyond kane-cli login. See Real Devices.

Root-only vs root-or-per-step

  • Root only: Chrome settings (target, chrome_profile, cdp_endpoint, ws_endpoint, headless), the mobile keys (app, no_reset), url, mode, tags, and on_lock_conflict. These apply to the whole run; setting them on an individual step is a parse error.
  • Root or per-step: everything else can appear in the per-step yaml block to override the frontmatter for that step only.

Title and Steps

After the frontmatter, add a # H1 title for the test. This is purely decorative. Kane CLI ignores everything before the first ## heading. Each ## H2 heading marks a test step. The heading is purely a label for humans and for the result file; the agent reads the step body underneath it, not the heading. Write the body as either plain English prose describing what the agent should do, or a single @import <path> line to pull in a reusable helper file. Do not mix prose and @import in the same step.

Per-Step Config Overrides

You can override frontmatter settings for individual steps by adding a yaml fenced block immediately under the step heading. The block must be closed with a matching ```.
Allowed keys: any root-or-per-step key from the frontmatter table, plus optional (see below). Chrome keys, mode, and on_lock_conflict are rejected with a clear error if you try to set them here. Setting optional: true tells Kane CLI that a failure on this step should not fail the overall test. The run continues to the next step.

optional

optional: true marks a step as soft-failing. If the step fails:
  • The overall run does not halt.
  • Subsequent steps still execute.
  • The step is reported as failed in Result.md with an (optional) suffix.
Default is false, in which case any failure stops the run and marks the remaining steps as skipped. optional is allowed on every prose step. On an @import step it is allowed only at the top level of a test file. See Composing tests with @import.

Step body shapes

The body of a step (everything after the optional yaml block) must be exactly one of:
  • A prose objective: one or more lines of natural language describing what the agent should do. A step body follows the same rules as any objective, so end it in a check of the result and phrase actions as goals. See Writing objectives.
  • An @import: a single line of the form @import <path> and nothing else.
Mixing prose and @import in the same body is a parse error.

Replay and Cascade Rule

This is the most important concept in testmd.

Replay

After the first run, each step replays from its cached recording, with no AI agent, no LLM cost, and much faster execution. An authorable step replays only when all of the following are true. (Steps marked replay-only always replay, so these conditions do not apply to them.)
  • A recording for that step exists
  • The step’s prose is unchanged since the recording
  • The step’s yaml block is unchanged
  • No earlier step invalidated it

Cascade

Editing step N re-authors step N and every authorable step after it. Steps marked replay-only still replay. This is because each step starts where the previous step left off (URL, login state, open tabs). If step 3 changes, the state step 4 depends on may be different, so step 4 cannot safely replay from cache.
A one-line tweak at the top of a 20-step test re-authors all authorable steps on the next run. To minimize re-authoring, edit only the steps you need to change.
Useful commands:
Keep the output recordings for steps marked replay-only. Deleting them does not make those steps author again. See Replay policy and completion.
For replay-only heading markers and balanced structured control flow (@if/@end-if, @while/@end-while), see Replay policy and completion.

Reusing Flows with @import

Extract repeating flows (login, setup, cookie banner dismissal) into helper files:
Rules:
  • Helper filename must not end in _test.md. Only _test.md files are valid entry points
  • Path resolves relative to the importing file, not the shell’s working directory
  • The step body must be exactly @import <path> with no mixed prose or extra lines
  • The step’s yaml block may contain only optional
  • optional: true on @import is allowed only at the root file level, not on nested imports
  • Variables and context propagate into helpers automatically
Editing a helper re-authors that step in every test that imports it, plus everything after the import in those tests. The same cascade rule applies.
Full detail: Composing tests with @import.

Variables

Variables let you parameterise objectives with reusable values and secrets. Reference a variable inside any prose objective using its placeholder syntax (default {{name}}):

Defining variables in a test.md

Variables can be set in three places inside a _test.md file, in order of increasing specificity:
Shorthand name: "value" works too: tester_email: "alice@example.com" is equivalent to tester_email: { value: "alice@example.com" }.

Where else variables can come from

Variables defined inline in a _test.md are merged with variables loaded from JSON files and command-line flags. The full pipeline is documented in Variables & Context. One difference matters for test.md authors:
Inside a _test.md file, variables declared in frontmatter or in a step’s yaml block win over --variables and --variables-file for the same key.
Every other flag overrides what is in the file; variables are the exception. The reasoning: the test author’s choice of test data stays close to the test. You can still add new keys from the CLI that the file does not define.

Secrets

Secrets are variables with secret: true. They are masked in displayed output, redacted in Result.md, and routed to TestMu AI’s secrets store instead of being synced as plain Test Manager variables.

Context

Context files are plain Markdown text that the agent reads alongside your objective. Use them for standing instructions: coding conventions, accounts to use, sites to avoid.
You can pass either a file path (resolved at runtime) or inline Markdown text. See Variables and context for the full discovery rules.

Recording a Test from a Live Session

You can author a test.md by hand, or record one from an interactive kane-cli run session. Run an ad-hoc objective with the --name flag to save it as a replayable test file:
On exit, Kane CLI writes the session to <cwd>/.testmuai/tests/amazon-search_test.md. Move that file into your repo and re-run it with testmd run, as any other test. The recorded file is a regular _test.md. It is parsed and executed exactly like a hand-written one, and you can edit it freely.
Without --name, ad-hoc runs are ephemeral and nothing is written to disk.

Commands

Full detail: Running test.md files.

Flags for testmd run

All kane-cli run flags apply (--agent, --headless, --max-steps, --timeout, --variables, etc.), plus these additional flags:
Flag values win over frontmatter for all settings except variables. The file owns variables. You can add new keys via flags but cannot override file-defined ones.
Full flag reference: Running test.md files.

Output Directory

After a run, Kane CLI creates an output directory next to the test file:
output-<stem>/ is commit-safe. Commit it to git so teammates and CI replay the same recordings. For tests using @import, helper recordings land next to the helper file in helper-output-<helper>-<root>-<step>/ directories. These are also commit-safe.

Result.md

After each run, Result.md is generated with:
To check whether a test passed or where it failed, read Result.md instead of re-running the test.

CI/CD Usage

By default, adaptive healing recovers from replay failures. Pass --no-adaptive-heal to turn it off.

Exit Codes


Worked Example

A complete test that signs in, searches, adds to cart, and verifies the badge, using a variable for the search term and a per-step timeout.
The first run of this file authors every step: the agent works out the page and records each step. Later runs replay each passed step from cache in a fraction of the time. If you edit a step’s objective, that step and the steps after it in the same file are authored again on the next run. See Replay vs author for the rules.

Common Parse Errors

The parser catches these mistakes early. A parse error stops the run with exit code 2 before any browser launch, auth call, or upload:

Example: Full Test with Imports

tests/checkout_test.md:
tests/helpers/login.md:
Run:

Next Steps