Skip to content

config.yaml

config.yaml at the project root holds workspace defaults; CLI flags win. It travels with the project — in the desktop app, editing a Project setting edits this file, and it ships to the team via Publish.

The full config — flows, includeTags/excludeTags, ai, report — is applied only when you pass it explicitly: tapstep test … --config config.yaml. Without the flag, the nearest config.yaml (walking up from the flow, stopping before your home directory) contributes only env:, plus environments:/defaultEnvironment: for --env-set, exports: for tapstep export and vscode.testspaces for VS Code flows.

flows: [flows/] # where tests live (relative to this file)
includeTags: [smoke] # default tag filter
excludeTags: [wip]
env: # shared: available as ${VAR} inside flows
STAGING_URL: https://staging.example.com
defaultEnvironment: staging # the environment a run uses when it names none
environments: # per-environment values, committed
staging:
BASE_URL: https://staging.example.com
prod:
BASE_URL: https://example.com
ai: # enables assertWithAI / --analyze
endpoint: https://ai.internal:8000/v1
model: qwen3-coder
report: # auto-report after every run
format: html # html | junit | json | allure
output: reports/ # relative to this file
exports: # where `tapstep export` writes
playwright: playwright
extester: ../lcp-ui-tests/src/ui-test
  • flows — patterns relative to the config’s folder: a file, a directory of flows, or a glob with * (one segment) and ** (any depth). On a directory run they replace the directory argument.

  • includeTags / excludeTags — defaults for --tags / --exclude-tags (directory runs; flags win).

  • env — shared variables, ${VAR} inside flows. environments — per environment values, defaultEnvironment — which one a run takes when it names none (below).

  • aiendpoint, model, optional key: any OpenAI-compatible server (Ollama, vLLM, LM Studio, a proxy), or Anthropic with an sk-ant- key. Enables assertWithAI, assertNoDefectsWithAI, extractTextWithAI and --analyze. Without an ai: block the CLI falls back to TAPSTEP_AI_ENDPOINT / TAPSTEP_AI_MODEL / TAPSTEP_AI_KEY.

  • reportformat: html|junit|json|allure and output: (a folder, relative to the config; defaults htmlreports/). Every run then writes a report there without --report; the flag still wins — see Commands.

  • exportsplaywright: and extester:, the folders tapstep export playwright / tapstep export extester write into when the command names no -o (the chat tools and the desktop follow the same setting). Relative to the config’s folder or absolute; outside the project is allowed, which is the point — a QA suite often lives in its own repository. Both kinds may name the same folder (the extensions differ: .spec.ts / .test.ts); the project root, a folder containing it and the flows folder are refused. Read from the nearest config.yaml, no --config needed. Defaults: playwright/ and extester/.

  • platforms: [web, android, ios] — the project’s target platforms; a hint for the desktop’s UI and chat. The CLI accepts and ignores it.

  • app: { port, launch } — a desktop app as the target (test bridge port, how to launch it); read by the desktop, the CLI targets it with --driver app[:port] — see Desktop apps.

  • vscode.testspaces — named VS Code test environments for extension projects; a flow picks one with vscode: { testspace: <name> } and its own vsix/workspace/settings fill any gaps. Paths are relative to the config’s folder; the CLI reads it from the nearest config.yaml above the flow, no --config needed:

    vscode:
    testspaces:
    full:
    vsix: [fixtures/vsix/a.vsix, fixtures/vsix/b.vsix]
    workspace: fixtures/workspaces/w.zip
    settings: { maven.executable.path: /opt/mvn }

The desktop keeps a few keys of its own there too: kind: vscode marks an extension project, ai.provider and ai.privacy configure the AI chat.

A test must not be tied to one environment: you record it once and run it on functional testing today and on integration testing tomorrow. So a project keeps its variables in two localities, each of them either shared or bound to one environment.

committed local (out of git)
shared, every environment config.yaml env: .env
one environment environments.<name> .env.<name>

Real values — credentials, anything that must not be committed — live in the local files (kept in .gitignore) next to the flow or at the project root. Flows and config.yaml are shared, so they carry placeholders. The dotenv syntax: KEY=VALUE per line, # comments, blank lines, an optional export prefix, matching single or double quotes stripped from the value.

defaultEnvironment: staging
env:
RETRIES: "2" # shared by every environment
environments:
staging: { BASE_URL: https://staging.example.com }
prod: { BASE_URL: https://example.com }

tapstep test flows/ --env-set prod picks the environment for the whole run, and without the flag defaultEnvironment does. A name no environments: block declares is an error that lists the ones that exist. The chosen name is a variable of its own — ${TAPSTEP_ENV} — and .env.prod is loaded with it, so the local secrets of an environment travel with it and never reach git.

Values resolve weakest to strongest:

Layer Where it lives
params: defaults the flow’s own header
flow env: the flow’s own header
config env: config.yaml, committed, every environment
environments.<current> config.yaml, committed, this environment — ${TAPSTEP_ENV} is set on this layer too
.env local, every environment
.env.<current> local, this environment
--env-file FILE this run
-e KEY=VAL this run

Two rules produce that order: an environment’s value beats the shared one at the same locality, and a local file beats a committed one. (Before 0.13.3 .env sat below --env-set; local secrets now win over committed values.) --env-set itself is not a layer — it picks which environment <current> is; -e TAPSTEP_ENV=… still overrides the name a step reads, because that name is an ordinary value of the environment layer. Any ${NAME} no layer sets falls back to the process environment.

The nearest config.yaml and .env are found by walking up from the flow; with --config, that file’s env: and environments: replace the nearest one’s. A missing .env is silent, a missing --env-file is an error. tapstep test applies the whole chain; the MCP run_flow resolves it for the project’s defaultEnvironment.

The environment a run worked against goes into its report: the JSON field environment, JUnit’s <property name="environment">, the Allure label environment, and the HTML report header. tapstep validate warns about a ${VAR} an environment leaves open and names which — the check that catches a test recorded on one environment before the other one runs it. Helpers are not checked: they run only inside a test, and their variables come from the caller’s runFlow env:.

A value may hold a JS expression — EMAIL: "qa+${Date.now()}@example.com" — and it is evaluated once, before the first step, then stored: fresh on every run, the same in every step that reads it. A runFlow env: resolves the same way once per block; a broken expression fails the run and names the key.

env.STAGING_URL is the address the desktop’s Record uses as the project’s Staging target: recorded flows start with openLink: ${STAGING_URL}, so repointing staging is a one-line change for every test at once.