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 filterexcludeTags: [wip]
env: # shared: available as ${VAR} inside flows STAGING_URL: https://staging.example.com
defaultEnvironment: staging # the environment a run uses when it names noneenvironments: # 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). -
ai—endpoint,model, optionalkey: any OpenAI-compatible server (Ollama, vLLM, LM Studio, a proxy), or Anthropic with ansk-ant-key. EnablesassertWithAI,assertNoDefectsWithAI,extractTextWithAIand--analyze. Without anai:block the CLI falls back toTAPSTEP_AI_ENDPOINT/TAPSTEP_AI_MODEL/TAPSTEP_AI_KEY. -
report—format: html|junit|json|allureandoutput:(a folder, relative to the config; defaultshtml→reports/). Every run then writes a report there without--report; the flag still wins — see Commands. -
exports—playwright:andextester:, the folderstapstep export playwright/tapstep export extesterwrite 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 nearestconfig.yaml, no--configneeded. Defaults:playwright/andextester/. -
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 withvscode: { testspace: <name> }and its ownvsix/workspace/settingsfill any gaps. Paths are relative to the config’s folder; the CLI reads it from the nearestconfig.yamlabove the flow, no--configneeded:vscode:testspaces:full:vsix: [fixtures/vsix/a.vsix, fixtures/vsix/b.vsix]workspace: fixtures/workspaces/w.zipsettings: { 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.
Variables and environments
Section titled “Variables and environments”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: stagingenv: RETRIES: "2" # shared by every environmentenvironments: 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.