sdods.project.yaml and sdods.workspace.yaml
Field-by-field reference for the project file and the workspace file.
Every field of sdods.project.yaml and of the root sdods.workspace.yaml, its type, default and
purpose, derived from the Zod schemas in @sdods/contracts.
sdods.project.yaml
Identity and hierarchy
Prop
Type
Layers, browsers, routes, tags, environments
Prop
Type
Modules and processes
Prop
Type
Data and authentication
Prop
Type
Screenshots, healing, timeouts, retries, performance, accessibility
Prop
Type
Canvas
Selectors for the canvas steps. The defaults match the DOM React Flow renders; override only the keys your editor does differently. Each value is a CSS selector template: {id}, {handle}, {source}, {target}, {field} and {badge} are replaced by the step argument, escaped for a quoted CSS string, so keep them inside quotes. {testIdAttribute} is replaced by the project's testIdAttribute. A template without its required placeholder, or with an unknown one, fails config validation.
Prop
Type
A workflow builder that marks its blocks with its own attributes overrides only those keys:
canvas:
field: '[data-subblock-id="{field}"]'
badge: '[{testIdAttribute}="block-{badge}"]'With that block, Then the node "agent-1" should show the "error-badge" badge looks for [data-testid="block-error-badge"] inside the node .react-flow__node[data-id="agent-1"].
Load profiles are not part of this file: each lives in projects/<slug>/load/<name>.yaml and runs with sdods load. The field reference is in Load testing.
Integrations, MCP, agents, schedules, reports
Prop
Type
Traceability
@req:<id> tags link scenarios to requirements; this block makes the ids checkable and feeds sdods report traceability. Every key is optional, and without the block any id is accepted.
Prop
Type
traceability:
requirements: requirements.yaml # or requirements.csv with an id,title header
link: https://jira.example.com/browse/{id}
require: false# requirements.yaml: a list, a map (AUTH-1: Title), or either under `requirements:`
- id: AUTH-1
title: A registered user can sign in
- id: AUTH-2
title: A locked account cannot sign inThe demo project
slug: demo-shop
name: Demo Shop
layers: [ui, api, hybrid, recorded]
browsers: [chromium, firefox, webkit]
testIdAttribute: data-test
routes:
{ login: /, inventory: /inventory.html, cart: /cart.html, checkout: /checkout-step-one.html }
tags:
suites: [smoke, regression, sanity]
extra: [mock, visual, a11y, perf, data-driven, pool, hybrid-demo]
roles: [standard, problem, performance, locked]
envs: { default: staging, available: [local, staging] }
data:
sources:
users: { type: csv, path: 'data/{env}/users.csv', fallback: data/common/users.csv }
products: { type: json, path: data/common/products.json }
factories: ./data/factories.ts
userPool: { dataset: users, roleColumn: role, leaseStore: file, leaseTtlMs: 600000 }
auth:
strategy: form
form:
{
loginPath: /,
usernameSelector: '#user-name',
passwordSelector: '#password',
submitSelector: '#login-button',
readyUrl: /inventory.html,
}
screenshots:
policy:
{
default: on-failure,
'@smoke': scenario,
'@regression': step,
'@sanity': scenario,
'@visual': visual,
}
mask: ['[data-test="shopping-cart-badge"]']
heal: { enabled: true, primaryTimeoutMs: 3000, probeTimeoutMs: 1500, minScore: 0.6 }
retries: { ci: 2, local: 0, byTag: { '@flaky': 2 } }
evidence: { trace: retain-on-failure, video: retain-on-failure }
perf: { budgets: { pageLoadMs: 4000, lcpMs: 3000, apiP95Ms: 1500 } }Setup scenarios
setup: { tags: '@setup' } runs the scenarios matching tags before everything else in the run and
skips the rest when one of them fails, instead of letting every scenario fail on its own against a
broken environment or a login that does not work.
- Every run target gets a companion target named
<target>--setup(for exampledemo-shop--ui--chromium--setup,demo-shop--api--setup) that runs that layer's setup scenarios in the same browser, and the target depends on it (Playwright projectdependencies). When a setup scenario fails, Playwright reports the target's scenarios as "did not run". - Setup scenarios are selected by
setup.tagsalone:--tags,--grep,--featureand--modulenarrow the target's own scenarios, never the setup tier. They are excluded from the target, so they run once. - Setup scenarios are ordinary scenarios: they still carry one layer tag and one suite tag. Declare
the tag under
tags.extraso lint does not warn about it. - A process can use other setup tags (
setup: { tags: '@probe or @auth' }) or none (setup: false). - Recorded specs (
layer: recorded) have no tags and get no setup tier.
tags: { extra: [setup] }
setup: { tags: '@setup' }
processes:
- { name: release-gate, fullyParallel: false }
- { name: quick, setup: false }sdods.workspace.yaml
The root file names the organization and workspace every project in the repository belongs to and holds defaults.
Prop
Type
organization: { slug: sdods, name: SDODS, url: https://example.com/sdods }
workspace: { slug: default, name: Default workspace, organization: sdods }
defaults:
browsers: [chromium, firefox, webkit]
suites: [smoke, regression, sanity]
testIdAttribute: data-testid
processes:
- {
name: pr-check,
trigger: pr,
tags: '@smoke',
browsers: [chromium],
harMode: replay,
gates: { minPassRate: 100 },
}
- {
name: nightly-regression,
trigger: nightly,
tags: '@regression',
browsers: [chromium, firefox, webkit],
notify: [github],
schedule: '0 2 * * *',
}
- {
name: release-gate,
trigger: release,
tags: '@smoke or @regression',
failOnFlaky: true,
gates: { minPassRate: 100, maxFlaky: 0, perfBudgets: true, a11y: true },
}roles.matrix.yaml
An optional file next to sdods.project.yaml, validated by RolesMatrixFileSchema in @sdods/contracts. It declares role × surface matrices that sdods matrix expand turns into the Examples of @matrix:<name> outlines. The format and workflow are in Role matrices.
Prop
Type