This page defines how normalize-tests turns one ui-tests.json into ui-test-catalog.json: grouping and order,
envelope, warnings and lineage. Developers who change the command, and readers of its output, read it.
Read before: normalize-docs · Next: coverage-matrix
living-doc-service-normalize-tests turns one ui-tests.json, as a collector wrote it, into ui-test-catalog.json,
the file a generator reads.
| Module | What it holds |
|---|---|
living_doc_service_normalize_tests.builder |
build_ui_test_catalog(tests, *, generated_at): the input's contract model to the output's. It reads and writes no file. |
living_doc_service_normalize_tests.lineage |
LINEAGE_TABLE, the command's lineage table |
living_doc_service_normalize_tests.service |
normalize_tests(input_path, output_path), the command, and the runtime field-loss check |
- Both files are
living-doc-utilitiescontracts,ui-tests-v1.0.0andui-test-catalog-v1.0.0; this package keeps no copy of either - The CLI registers it as
living-doc normalize-tests, and the action runs it ascommand: normalize-tests→ CLI, Commands
A run takes these steps; nothing is written when one fails:
- The input goes through the shared input validation,
validate_inputs("normalize-tests", …), and nothing else reads it (Shared input validation). A failing step stops the run before anything is built. - The builder groups the validated scenarios and builds the output model; the command sets its
metadata.runto the CI run. - The runtime field-loss check compares the input's selected occupancy with the sum of all three output groups (Lineage); a lost field stops the run.
write_artifactvalidates the catalog, then writes it.
The builder groups each scenario by feature file and by every entity it links:
- There is one
feature_files[]entry per feature file- Its key and name are the scenario's
scenario_idup to its last/. That prefix must end with/<source_ref.native_id>, after exactly two non-empty path components (<org>/<repo>). - Otherwise the key and name are
source_ref.native_id. - Why exactly two: a
metadata.source.repositories[]entry isorg/repoororg/project; another length names no repository. org/repois GitHub's form,org/projectAzure DevOps' (artifact rules, § The metadata envelope).
- Its key and name are the scenario's
- The repository-qualified name relies on
living-doc-collector-gh'sscenario_idshape, which no contract defines; other id shapes use the native path- The final repository identifier belongs in a future contract change, once a contract field names the repository.
- A
US-acceptance-criterion link groups its scenario under the User Story id without the final-<n>;FUNC-links do the same under a Functionality- A scenario linking both appears in both groups, and several links to one entity place it there once.
- A scenario with no links goes in
unlinked[]. Its completeScenariomodel, includingacceptance_criteria[]andtags[], is carried unchanged. - Feature files follow their first scenario in the input. Groups follow an entity's first link; scenarios within a group follow input order. Nothing is sorted.
The fields normalize-tests fills its own way; every command fills the rest the same way, as the core's Shared envelope says:
| Field | Value |
|---|---|
metadata.source |
the input's, copied |
metadata.stats |
from the output's own groups |
metadata.source_inputs[] |
one entry for the input: its schema_version, producer, run, source, and stats; selected_stats is computed over all input scenarios, with no view filter |
document.view |
inner; the catalog has no view-dependent field |
- The input's own
runstays insource_inputs[0].run - The view follows the rendering page → rendering
Every input warning reaches the output through the core's forwarded, in input order, its context starting with source_input='ui-tests-v1.0.0'.
LINEAGE_TABLE maps each of the 12 ui-tests-v1.0.0 scenario paths to its twin under
feature_files[].linked_to_user_story[].scenarios[].
- The table is declared here, and
assert_complete(LINEAGE_TABLE, "ui-tests-v1.0.0")runs in this package's tests only - At run time,
check_no_field_losscomputes the catalog's own stats aswrite_artifactwill. It adds the occupancy underlinked_to_functionality[].scenarios[]andunlinked[]to each mapped User Story path, then callscheck_field_loss- Only the stats passed to the check are aggregated; the written file keeps its own counts.
- Why: scenarios are split among three groups, so one group alone reports a false loss when it lacks the field.
- For example, the fixture's four
acceptance_criteria[].aspectvalues are all on Functionality links. Summing the groups lets the fixture and the utilities full sample pass.
- A field lost from one group while another still has it is a partial drop; this check does not detect it, because R11 compares aggregate occupancy
FIELD_LOSSremains a hard error when the input has data and the union of the three groups has none- The runtime check is R11; this package's tests carry R12 checks 2 and 3 → component checks
- The fixture command output is compared with the committed golden, masking only
metadata.generated_at - Built inputs cover multiple entities, cross-group and duplicate links, unlinked scenarios, input order, and repositories sharing a native path
full_sample("ui-tests-v1.0.0")is written withwrite_artifactand run through the command, shared input validation included