Allure stores fixtures in a container rather than in the test result, so how does the generated report end up showing setup and teardown on an individual test case's page?
answer
- generation is a join, not a copy
- the key is the case uuid
- three bands on the case page
- beforeStages, testStage, afterStages
- a broken join shows as a missing band
basics
~20 sThe reader joins on uuid. Every container's befores and afters are folded onto each child uuid it names, so the report's test result gains beforeStages, testStage and afterStages built from those fixtures plus the case's own steps.
solid answer
~40 sGeneration is a join, not a copy. The reader loads every `-container.json` and every `-result.json`, then for each container walks its `children` uuids — following nested containers transitively — and attaches that container's `befores` and `afters` to each case it reaches. The report-side result is reshaped into three slots: `beforeStages` (from the fixtures that ran before), `testStage` (the case's own `steps` and `attachments`), and `afterStages` (the teardown). Each stage carries its own steps, attachments and status, which is why a fixture's nested steps appear on the case page exactly like body steps, but in a separate band. Because the join key is the uuid, a container whose `children` never names a case contributes nothing to it — silently, with no error.
go deeper
Know that the report you read is generated, not written: the setup shown on a case page came from a separate container file, matched to that case by uuid.
Walk the join out loud — children uuids resolved transitively through nested containers, fixtures folded into beforeStages and afterStages, the case's own steps becoming testStage.
Diagnose the silent cases: a killed run with results but no containers, an adapter registering an empty children list, or a merged parallel directory missing one file glob.
Own the tradeoff behind the join: storing a shared fixture once preserves the fact that it ran once, at the price of a membership link that can resolve to nothing without any error.
## Two different shapes: what is written and what is read The writer side and the reader side of Allure do not use the same shape, and the mismatch is the point of this question. **What is written** is flat and scattered: `-result.json` files each holding one `TestResult`, and `-container.json` files each holding one `TestResultContainer` with `befores`, `afters` and a `children` list of uuids. **What is read** is per-case and assembled: the report model's test result carries three stage slots — `beforeStages`, `testStage` and `afterStages` — each a stage object with its own steps, attachments and status. Nothing on disk has that shape; the generator builds it. | aspect | on disk (writer) | in the report (reader) | |---|---|---| | fixture location | `befores` / `afters` on a container | `beforeStages` / `afterStages` on the case | | case body | `steps` on the `TestResult` | `testStage` | | membership | `children` uuid list | already resolved into each case | | shared fixture | stored once | shown on every case it covered | ## The join, step by step 1. **Load everything.** All result files and all container files are read into memory, keyed by uuid. 2. **Walk each container's `children`.** Each entry is a uuid. If it names a result, that case is a member of the scope. If it names another container, the walk descends, so a class-scoped container reaches method-scoped results transitively. 3. **Attach the fixtures.** The container's `befores` become part of every reached case's before-stages, and its `afters` become part of its after-stages. The fixture object is not modified and not duplicated on disk — it is simply attributed to more than one case. 4. **Wrap the body.** The case's own `steps` and `attachments` become the test stage. 5. **Render.** The case page shows three bands in run order: setup, body, teardown. Because fixtures keep the same field set as steps — `name`, `status`, `statusDetails`, `stage`, `steps`, `attachments`, `parameters`, `start`, `stop` — a fixture with nested steps renders exactly like a body step, just in the setup band rather than the body band. ## Ordering and nesting Several containers can cover the same case at once: a suite scope, a class scope, a method scope. Each contributes its own `befores` and `afters`, so the setup band on one page can hold fixtures from several scopes. Their `start` and `stop` timestamps are what make the band readable in the order things actually happened — the container gives membership, the timestamps give sequence. ## When the join silently produces nothing This is where real debugging happens, because a failed join is not an error — it is an absent band. - **The container was never written.** A run killed before the scope closed leaves the results on disk (they were flushed as each case finished) and no container. Every case renders with a body and no setup. - **The `children` list is empty or wrong.** An adapter that starts a scope but never registers the case's uuid produces a container that covers nobody. The fixture is on disk and appears nowhere. - **The case result is missing.** A container names a uuid that never got a `-result.json`. There is no page for the fixture to land on; the fixture is unreachable. - **A directory merged from parallel workers is incomplete.** Containers and results are separate files, so copying only one glob out of a worker workspace loses one half of the join. The diagnostic is always the same: take the case's `uuid` from its `-result.json`, grep the results directory for that string, and see which containers list it. ## Why this design, rather than inlining fixtures Inlining setup into every result would make each file self-contained, but it would also duplicate a shared fixture once per covered case, force the writer to know all members of a scope before writing the first case, and destroy the record that the fixture **ran once**. The container keeps one fixture object with one pair of timestamps and lets the reader decide how to present it. The cost is exactly the failure mode above: a join that can quietly resolve to nothing.
- Two containers cover the same case. What decides the order the fixtures appear in?Membership comes from each container's `children`, but sequence comes from the fixtures' own `start` and `stop` timestamps. Nothing in the container expresses ordering between scopes, so a reader that ignores timestamps can show an outer suite fixture below an inner class fixture.
- A fixture is clearly in a `-container.json` but never appears in the report. Where do you look first?At that container's `children`. If it is empty, or lists uuids that have no `-result.json`, the fixture has nothing to attach to and the generator reports no error. Confirm by grepping the case's uuid across the results directory.
saying these in an interview costs you the question
- Thinks the writer copies fixtures into each result
- Expects an error when a container names no children
- Confuses testStage with the container's befores
- Assumes only one container can cover a case
- Believes fixture ordering comes from the children list