In an Allure results directory, where do the bytes of an attached screenshot or log actually live, and what does the matching `Attachment` entry inside a `-result.json` file hold?
answer
- the JSON never carries bytes
- one file per payload
- UUID, then -attachment, then extension
- name, source, type on the entry
- source is the file name on disk
basics
~20 sThe bytes live in a file of their own in the results directory, named with a random UUID, the suffix -attachment, and the file's extension. The JSON entry beside the test holds only a pointer: name, source and type.
solid answer
~40 sAllure never puts binary content inside a result JSON. When a test attaches something, the writer streams the bytes to a separate file in the results directory whose name is a fresh `UUID.randomUUID()`, then the constant `ATTACHMENT_FILE_SUFFIX` (the literal `-attachment`), then the file's own extension — so a screenshot lands as `3f2a…-attachment.png`. It then appends an `Attachment` object to the `attachments` list of that test inside its `-result.json` file. That object carries `name` (the label the report displays), `source` (exactly that generated file name) and `type` (the media type). `size` is a fourth field on the model, but the writer leaves it unset; the report's reader fills it in later by measuring the file it finds. Tooling locates payload files with the glob `*-attachment*`.
code
json · 12 lines{
"uuid": "9a1e0e0d-4f2e-4b6a-9d0c-5f1f2b3c4d5e",
"name": "checkout redirects to the payment page",
"status": "failed",
"attachments": [
{
"name": "Screenshot on failure",
"source": "3f2a17c8-6b41-4c2b-9a55-0d1e2f3a4b5c-attachment.png",
"type": "image/png"
}
]
}go deeper
Be ready to say plainly that the bytes go in their own file and the result JSON only points at it by source. Knowing that much lets you find a payload in a results directory by hand.
Explain the naming recipe — UUID, then -attachment, then an extension derived from the media type — and why the glob needs a wildcard at both ends. Be able to say which fields the writer sets and which it leaves for the reader.
Show that you know what this costs in a pipeline: the JSON and the payload files must travel together, an archive step that globs only JSON silently strips the evidence, and identical payloads are never shared.
Own the tradeoff behind the split — streaming and format neutrality bought with two artefacts to move and no de-duplication — and be able to say when a content-addressed store would be worth the extra machinery.
## Why an attachment is two artefacts A test result in Allure is a JSON document, and JSON has nowhere sensible to put a PNG, a video, or a megabyte of server log. So the writer splits every attachment into two files that sit side by side in the results directory: - **The payload file** — the raw bytes, written as a file of its own. - **The `Attachment` entry** — a small JSON object appended to the `attachments` list of whichever test or fixture produced it, inside that test's `-result.json` file. The entry is a *pointer*, not a container. That is the single most useful thing to know about the format: a results directory is a directory precisely because the bytes will not fit in the JSON. ## How the payload file gets its name The writer composes the file name from three parts: 1. A fresh `UUID.randomUUID()`, minted on each attach call. 2. The constant `ATTACHMENT_FILE_SUFFIX`, whose value is the literal `-attachment`. 3. The file's own extension, normalised so it begins with a dot — or the empty string when there is no extension to use. The extension itself is derived, not guessed. If the caller supplied a file extension explicitly, that wins. Otherwise the writer maps the declared media `type` to a well-known extension. If neither produces anything, the third part is empty and the file name simply stops after `-attachment`. Two consequences follow, and both surprise people the first time: - **The original file name is not preserved.** Whatever the test called the file, the payload file in the results directory is named after a UUID. The human label lives in the entry's `name`, never in the file name. - **There is no de-duplication.** Because the UUID is minted per call, attaching identical bytes twice produces two payload files. The writer is not content-addressed. Tooling that hunts for these files uses the glob `*-attachment*` — a wildcard on **both** sides, because the UUID varies at the front and the extension varies at the back and may be absent entirely. ## What the JSON entry holds | field | what it holds | who fills it in | |---|---|---| | `name` | the human label the report displays for the attachment | the test, when it attaches | | `source` | the payload file's name inside the results directory | the writer, from the UUID recipe above | | `type` | the payload's media type, for example `image/png` | the test, or the writer's default | | `size` | the payload's length in bytes | not the writer — see below | `size` is the field that trips people. It is real, and it is on the model, but the writer does not set it when it appends the entry: it has no reason to stat a file it has just streamed out. The report's reader fills it in at generation time, when it resolves `source` to a file in the results directory and measures what it finds. So a size shown in a generated report describes the bytes that were actually present when the report was built, not what the test believed it attached. ## Reading a results directory by hand Because the two halves are separate files, you can inspect an attachment with no Allure tooling at all: 1. Open the test's `*-result.json` and find its `attachments` array. 2. Take the `source` value from the entry you care about. 3. Open the file of exactly that name in the same directory. That three-step contract is worth internalising, because it also tells you what an archive of a run has to contain. A CI step that collects `*.json` out of the results directory and nothing else produces a report whose attachment entries all point at files that are not there — the JSON survives the trip and the evidence does not. ## What the split buys, and what it costs **What it buys:** - **Streaming.** The writer flushes a large payload straight to disk without holding it in memory or re-encoding it, and a generated report can serve one payload on demand instead of handing every byte to every reader. - **Format neutrality.** A video, an archive and a plain text log are all just files; the JSON schema does not have to grow a case for each new kind of evidence. - **Cheap partial reads.** A tool that only wants the pass/fail picture reads the JSON and never touches a single byte of payload. **What it costs:** - **Two things to move.** Copy, zip or publish the results directory whole, or the pointers dangle. - **Opaque names.** Listing a results directory tells you nothing about what the payloads are; you have to read the JSON to find out which UUID is the screenshot. - **No sharing.** Identical payloads attached many times are stored many times over. The same shape is reused everywhere payloads appear in the format: fixtures carry `attachments` lists too, and a run-level payload gets the same `name`/`source`/`type` object in a file of its own. Learn the pointer once and you can read all of them.
- Two different tests attach byte-for-byte identical screenshots. How many payload files does the writer produce?Two. The `source` name is built from a fresh `UUID.randomUUID()` on every attach call, so identical bytes attached twice yield two files with two names and two entries. The writer does no hashing and no content-addressing, so a suite that screenshots the same unchanged page in many tests stores that page once per test.
- Where does the extension in the payload file name come from when the caller does not supply one?From the declared media type. The writer takes an explicitly supplied file extension if there is one, otherwise it maps `type` to a well-known extension for that media type. If neither yields anything, the extension is empty and the name ends at `-attachment` with nothing after it.
The JSON entry is a coat-check ticket: it tells you the label written on the tag and the number of the peg, but the coat itself is hanging in the back room. Lose the room and the ticket still reads perfectly and buys you nothing.
saying these in an interview costs you the question
- Says the bytes are base64 inside the result JSON
- Thinks source holds the human-readable attachment label
- Expects the payload file to keep its original filename
- Believes the writer records the payload's size