YAML Syntax Cheat Sheet
Quick reference for the TestLab YAML test authoring format (testlab: v1-alpha).
Document Types
| Kind | Purpose | Required key |
|---|---|---|
kind: tck |
Test Collection Kit — groups tests with shared config | tests: |
kind: test |
Single test case | steps: (≥1 step) |
TCK File Structure (index.yaml)
Only TCKs declare env:. Tests inherit it via namespace: matching.
kind: tck
testlab: v1-alpha
id: my-tck
namespace: my-namespace-v1.0
metadata:
name: "My TCK"
version: "1.0.0"
description: "What this TCK certifies"
dataspace_version: saturn
standards:
- id: CX-0135
version: v3.1.0
tags: [tag1, tag2]
env:
variables:
provider_url: ""
timeout_seconds: 300
services:
- name: testlab_connector
uses: service/connector_service
with:
base_url: ${{ env.provider_url }}
management_path: /management/v3
dsp_path: /api/v1/dsp/2025-1
dataspace_version: ${{ metadata.dataspace_version }}
auth:
type: api_key
api_key: "test-api-key"
api_key_header: "X-Api-Key"
returns:
connector_service:
type: class
class: ConnectorService
schemas:
my_schema:
file: my_schema.json
testdata:
sample_body:
file: sample_body.json
tests: # ordered; entries are mappings, never bare strings
- id: test_one.yaml # the file name in tests/
name: What test one proves
- id: test_two.yaml
name: What test two proves
skippable: true # optional; the operator may omit it via skip_tests
skippable defaults to false. The player rejects the whole run when
skip_tests names a test that is unknown or not marked, so only what the author
allowed can ever be skipped.
Test File Structure
Tests have NO env: block — they inherit everything from their parent TCK.
kind: test
testlab: v1-alpha
id: my-test
namespace: my-namespace-v1.0
metadata:
name: "My Test"
version: "1.0"
description: "What this test validates"
setup: []
steps: []
teardown: []
Step Structure
- id: step_id
uses: category/action
name: "Human-readable label"
with:
param: value
ref_param: ${{ steps.other_step.output }}
returns:
output_name:
type: string
class: SemanticClass
validate:
- uses: validate/assert
name: "Human-readable label for this check" # optional
with: { input: output_name, operator: not_null }
if: "${{ success() }}"
timeout_s: 30.0
Field order: id → uses → name → with → returns → validate → if → timeout_s
Variable References (${{ ... }})
| Pattern | Resolves to |
|---|---|
${{ env.provider_url }} |
TCK environment variable |
${{ env.services.name.output }} |
Service output (e.g., .connector_service) |
${{ env.testdata.data_name }} |
Testdata file content |
${{ env.schemas.schema_name }} |
Schema file content |
${{ steps.step_id.output_name }} |
Output from a step in steps: |
${{ setup.step_id.output_name }} |
Output from a step in setup: |
${{ metadata.dataspace_version }} |
Metadata field value |
${{ execution.id }} |
The id of this run (the job id), e.g. to name what a test leaves in a shared system |
Conditionals (if:)
| Expression | Meaning |
|---|---|
${{ success() }} |
All previous steps passed (default) |
${{ failure() }} |
At least one previous step failed |
${{ always() }} |
Always executes |
${{ steps.step_id.outcome == 'success' }} |
Specific step outcome check |
${{ steps.step_id.outcome == 'failure' }} |
Specific step failed |
${{ steps.step_id.outcome == 'skipped' }} |
Specific step was skipped |
Returns Block
returns:
output_name:
type: string # string | integer | object | array | boolean
class: AssetId # Semantic class (for type filtering)
Common classes: AssetId, PolicyId, AgreementId, AuthToken, StatusCode, ResponseBody, ResponseHeaders, Uuid, Url, Bpn, ConnectorService, DataplaneUrl, MockInstance, Policy, String
Validate Block
Assertions reference the step's own output names directly (not ${{ }} for local outputs):
validate:
- uses: validate/assert
name: "the request is accepted" # optional; the report calls the check this
with:
input: status_code
operator: equals
value: 200
- uses: validate/assert
with:
input: "${{ steps.other_step.output }}" # cross-step refs use ${{ }}
operator: not_null
Operators:
| Operator | Description |
|---|---|
equals |
Exact match |
not_equals |
Not equal |
not_null |
Value is not null |
not_empty |
Value is not empty |
contains |
Contains substring/element |
not_contains |
Does not contain |
regex |
Matches regular expression |
status_code |
HTTP status code match |
greater_than |
Numeric > |
less_than |
Numeric < |
greater_or_equal |
Numeric ≥ |
less_or_equal |
Numeric ≤ |
between |
Numeric range (inclusive) |
JSON Schema Validation (validate/schema)
validate/schema validates a value against a full JSON Schema
document — distinct from the scalar operators above, which compare single values.
The schema is normally a reference to a file declared in the TCK env.schemas
block; the step fails (marking the step FAILED) when the payload does not conform,
reporting the offending field paths.
- id: validate_twin
uses: validate/schema
with:
input: "${{ steps.query_dt.response_body }}" # dict, list, or JSON string
schema: "${{ env.schemas.shell_descriptor_schema }}"
An inline schema object is also accepted — useful for a one-off existence check,
e.g. asserting an array contains an element with a given field:
- id: has_submodel_value_endpoint
uses: validate/schema
with:
input: "${{ steps.query_dt.response_body }}"
schema:
type: object
required: [submodelDescriptors]
properties:
submodelDescriptors:
type: array
contains:
type: object
properties:
endpoints:
type: array
contains:
properties:
interface: { const: SUBMODEL-VALUE-3.1 }
Extracting Values (json_path_extract)
json_path_extract reads a value out of a dict/list using a dot-separated path,
storing it in a variable for later steps.
- id: get_asset_id
uses: json_path_extract
with:
source: response_body # variable NAME, or a ${{ }} expression
path: "datasets.0.id" # dot path; numeric segments index lists
store_in_variable: asset_id # optional; step output is the value either way
source— either the name of a context variable (response_body), or a${{ }}expression that resolves to the data itself ("${{ steps.query.response_body }}"). Both forms work.path— dot-separated. Numeric segments index into lists (endpoints.0.href).
Predicate filters — select the first list element whose field matches a value, instead of relying on a positional index:
| Path | Selects |
|---|---|
items[id=abc].value |
first items element with id == abc |
endpoints[interface='SUBMODEL-VALUE-3.1'] |
quote values that contain ., ;, #, etc. |
descriptors.endpoints[interface='…'] |
steps over an intermediate array — no index needed |
descriptors[endpoints.interface='…'].id |
select by a nested field (dotted predicate key) |
Quote a predicate value ('…') when it contains dots or other separators —
interface names and semantic IDs always need quoting. A predicate that matches
nothing raises a clear error rather than returning a wrong value.
Utility Steps
util/parse_kv — parse a delimited key=value string (e.g. an EDC
subprotocolBody) into a dict, or select one key. Each pair is split on the
first = only, so a value may itself contain = (a URL query string, base64).
- id: get_edc_asset_id
uses: util/parse_kv
with:
input: "${{ subprotocol_body }}" # "dspEndpoint=https://…;id=urn:uuid:1234"
select: id # omit to return the whole dict
store_in_variable: edc_asset_id
# pair_separator: ";" (default)
# kv_separator: "=" (default)
util/base64 — encode or decode a string with base64 / base64url. The
AAS DTR requires an aas_identifier to be base64url-encoded before it goes
in a request path, so url_safe: true is the common case. Decoding restores
padding automatically, so an unpadded value round-trips without extra =.
- id: encode_aas_id
uses: util/base64
with:
input: "${{ env.twin_id }}" # "urn:uuid:1234" or a URL
mode: encode # encode (default) | decode
url_safe: true # -/_ instead of +//; needed for DTR
strip_padding: false # drop trailing '=' when encoding
store_in_variable: aas_identifier_b64
util/log — echo a resolved value to stdout and the run log while authoring.
Asserts nothing and always passes; remove once a test is finalised.
- id: show_href
uses: util/log
with:
message: SUBMODEL-VALUE-3.1 href
value: "${{ submodel_value_href }}"
Complex Variables (TCK only)
Typed variables in env.variables cover everything a test needs before its steps
run: runtime inputs and reusable config objects such as policies and assets.
Declare each one once and reference it by its id — ${{ env.<id> }}. Every
variable publishes one value under value, whatever its type, and its uses:
verb decides that type: the compiler refuses a returns: that says otherwise.
env:
variables:
# Runtime input collected from the SUT operator
- id: sut_dsp_url
uses: variable/type/string
name: SUT DSP Endpoint URL
with:
source: input
scope: sut
placeholder: "https://connector.example.com/api/dsp"
returns:
value:
type: string
# Constant value with a default
- id: sut_response_timeout
uses: variable/type/integer
with:
value: 300
returns:
value:
type: integer
# Reusable access policy, referenced wherever a step needs it
- id: usage_policy
uses: config/connector/policy
name: Required usage policy
with:
value:
permissions:
- action: use
constraints:
and:
- left_operand: UsagePurpose
operator: isAnyOf
right_operand: "cx.ccm.base:1"
returns:
value:
type: object
class: Policy
# Asset definition the provider steps provision
- id: api_asset
uses: config/connector/asset
name: API asset
with:
value:
name: CCMAPI Notification Asset
base_url: "https://backend.example.com/ccm"
properties:
dct:type:
"@id": "https://w3id.org/catenax/taxonomy#CCMAPI"
cx-common:version: "3.0"
returns:
value:
type: object
class: Asset
Reference in tests: ${{ env.sut_dsp_url }}, ${{ env.usage_policy }},
${{ env.api_asset }} — the id alone, for every type.
Failure Handling
There is no per-step failure policy. A failed validation (hard assertion) fails its step; a failed step fails the test — execution stops, the remaining steps are skipped, and teardown runs. Teardown steps keep executing even when one of them fails.
Common uses: Handlers
| Prefix | Examples |
|---|---|
mock/ |
mock/api, mock/wait/http_request |
connector/ |
connector/pull_data_filtered, connector/create_asset, connector/health_check |
util/ |
util/generate_uuid, util/log, util/parse_kv, util/base64 |
validate/ |
validate/assert, validate/field, validate/schema |
variable/ |
variable/type/string, variable/type/integer, variable/type/boolean |
config/ |
config/connector/policy, config/connector/asset |
service/ |
service/connector_service |
| (no prefix) | json_path_extract |
See Extracting Values and Utility Steps below for the extraction and helper handlers.
Dataspace Versions
| Version | EDC Compatibility | Protocol |
|---|---|---|
saturn |
EDC v0.11+ | DSP 2025-1 |
jupiter |
EDC v0.8–0.10 | Legacy DSP |
Set in metadata.dataspace_version or per-service via with.dataspace_version.
Variable Resolution Priority
- Runtime variables — CLI
--varflags or API input - TCK environment —
env.variablesin the TCK file - Step outputs — resolved at execution time