In an Allure `-result.json` file, a test result carries both a `historyId` and a `testCaseId`. What does each of those keys identify, and what goes wrong if a tool treats them as interchangeable?
answer
- two stable keys, not one
- case identity versus series key
- one value per method, many per row
- only one of them eats the parameters
basics
~20 stestCaseId identifies the logical test case - one value for a method however many rows it runs. historyId identifies one trend series: that case plus the parameter values it ran with. Two rows share testCaseId but not historyId.
solid answer
~40 sAllure's writer model puts two separate string keys on a `TestResult`. `testCaseId` is the identity of the logical case: the JUnit Platform adapter derives it from the identifier that framework assigns each test, with the parameterised-invocation segments stripped off, and the TestNG adapter from the class and method name, so every row of a data-driven test carries the same value. `historyId` is the key that result carries for its trend series, derived from `testCaseId` together with the parameter values the run actually used, so each row gets its own series. Substituting one for the other collapses a parameterised test's rows into a single interleaved trend, or forks one case into as many trends as it has rows. A third field, `uuid`, identifies only this execution and is regenerated every run.
code
json · 14 lines{
"uuid": "c0b7563c-07a8-4803-beac-efc9ed45a554",
"historyId": "b198d36a585bdff2dc4ac9210b80da6f",
"testCaseId": "3f1b9c0d4a7e2b615c8d0f39a24e7b81",
"fullName": "io.qameta.allure.CartTest.appliesDiscount",
"name": "appliesDiscount[GOLD]",
"status": "passed",
"stage": "finished",
"parameters": [
{ "name": "tier", "value": "GOLD" }
],
"start": 1710000054000,
"stop": 1710000056200
}go deeper
Be ready to say which key is per case and which is per trend series, and that the rows of a data-driven test share one of them but not the other.
Explain where each value comes from: case identity from the test's coordinates in the code, the series key from that identity plus the run's parameters, and why sorting those parameters matters.
An interviewer will expect you to read a broken report and name which field was misused - one trend covering a whole parameterised class, or a fresh series per row per build.
Own the call on whether identity is derived from code coordinates at all or assigned externally, and price what each choice costs a large estate when tests are refactored.
## Three identifiers, not one Allure's writer library `allure-java` produces one `*-result.json` file per executed test, and the `TestResult` model behind that file carries three distinct string identifiers. They are routinely confused because in a real file all three are opaque hex strings that look alike: - **`uuid`** -- the identity of *this execution*. The adapter generates a fresh value every time the test runs, and the result file is named after it. It answers *which record is this*. - **`testCaseId`** -- the identity of *the logical test case*. Stable across runs, and shared by every invocation of a data-driven test. It answers *which test is this*. - **`historyId`** -- the key of *one trend series*. Stable across runs like `testCaseId`, but distinct per set of parameter values. It answers *which line on the trend chart does this outcome extend*. The same result also carries `fullName` and `name`. `fullName` is the fully qualified reference to the test; `name` is the display title, and on a parameterised row it usually shows the row's values in brackets. Neither is an identity key. `name` in particular is free text that an annotation can rewrite without anything else changing. ## Where the two stable values come from `testCaseId` is assigned by the adapter from the test's coordinates in the code. The JUnit Platform adapter takes the identifier the JUnit Platform assigns each test and removes the parameterised-invocation segments, so a template method and every one of its invocations reduce to the same value. The TestNG adapter builds it from the test class name and the method name and hashes that. Both are functions of *where the test lives*, which is exactly why moving or renaming the test moves the value. `historyId` is derived one level above that: from `testCaseId` together with the parameter name and value pairs the run actually recorded. Two rows of one parameterised test therefore agree on `testCaseId` and disagree on `historyId` -- which is the whole point, because you want each row's pass/fail history kept apart. | field | scope | same across a parameterised test's rows? | changes when | |---|---|---|---| | `uuid` | one execution | no | every run, by design | | `testCaseId` | one logical case | yes | the class or method is renamed or moved | | `historyId` | one trend series | no | `testCaseId` changes, or the run's parameter values change | ## What conflating them actually costs 1. **Using `testCaseId` where `historyId` belongs.** Every row of a parameterised test writes into one series, so the trend interleaves outcomes from unrelated data rows. A chart that alternates pass and fail then looks like an unstable test when it is really two stable rows, one green and one red. 2. **Using `uuid` where `historyId` belongs.** Every build opens a brand-new series holding exactly one point. The trend never accumulates, and everything the report derives from a stored series has nothing to compare against. 3. **Using the display `name` as identity.** Presentation is not identity. Renaming a test purely for readability silently forks its history, and no message is emitted. ## How the two Allure majors spell it Both majors read the same written file, so the writer side is the stable part to learn. Allure 2's reader model keeps a `testCaseId`. Allure 3's reader model has no field of that name: it takes `testId` from the raw result and exposes case identity on the model as `testCase.id`, next to a nested case object carrying its own `name` and `fullName`. `historyId` survives unchanged in both. So the *idea* -- case identity underneath, series key above it -- is what transfers; the reader-side spelling does not. ## What to check in a real results directory - Open two `*-result.json` files from one run, for two rows of the same parameterised test. The healthy shape is: same `testCaseId`, different `historyId`, different `uuid`. - Open one row's file from two consecutive runs. The healthy shape is: same `testCaseId`, same `historyId`, different `uuid`. - If a result has no `testCaseId` at all, the writer has nothing to derive a series key from, and that result joins no trend. The short version an interviewer wants back: `uuid` is per execution, `testCaseId` is per case, `historyId` is per case-plus-parameters. Three scopes, three fields, and each one is the wrong answer to the other two's question.
- What is `uuid` on that same result, and why can it never serve as the trend key?`uuid` identifies one execution's record, and the result file is named after it, so the adapter mints a fresh value on every run. Filing a trend under it would open a new one-point series each build. `testCaseId` and `historyId` are stable across runs by construction; `uuid` is deliberately not.
- Allure 3's result model has no `testCaseId` field. What does it use for case identity instead?Allure 3 takes `testId` from the raw result and exposes case identity on its model as `testCase.id`, alongside a nested case object carrying its own `name` and `fullName`. `historyId` survives unchanged, so the series key is the same idea under both majors and only the case-identity field was respelled.
testCaseId is the recipe; historyId is the recipe plus the exact ingredients you used. You compare this week's loaf against previous loaves baked the same way, not against every loaf the recipe has ever produced.
saying these in an interview costs you the question
- Says historyId and testCaseId are one field under two names
- Treats the test's display name as its identity key
- Expects every row of a parameterised test to share one trend
- Thinks uuid identifies the case across runs
- Assumes the report reconciles identity by matching titles