skip to content

On a test result in an Allure 2 report, what do the `retriesCount` and `retriesStatusChange` fields say, and what does `retriesCount` deliberately not include?

level: juniorimportance: must knowfreq 50%

answer

  1. a count and a boolean
  2. counts the other attempts only
  3. off by one from executions
  4. the flag just compares statuses

basics

~20 s

retriesCount is how many earlier attempts were folded into this result, so it excludes the attempt on screen; a test executed twice reads one. retriesStatusChange is true when any earlier attempt ended in a different status.

solid answer

~40 s

Both fields are written by Allure 2's `RetryPlugin` onto the one attempt it leaves visible. The plugin builds a list of every attempt in the group **except** the survivor, then calls `setRetriesCount(...)` with that list's size — so the field is always one less than the number of attempts the runner made, and a test executed three times reads two. `setRetriesStatusChange(...)` takes the statuses of those same earlier attempts, discards any equal to the survivor's, and sets the flag to true if anything is left. It is a plain boolean: it says the outcome moved across the attempts, not which way it moved and not which attempt moved it. A nonzero count with a false flag therefore means the test was repeated and came out the same way every time.

go deeper

for a junior

Recall that retriesCount counts the earlier attempts only, so a test executed twice reads one, and that retriesStatusChange is a plain true or false rather than a status of its own.

for a middle

Explain where both values come from: one list of every attempt except the survivor, sized for the count, and filtered against the survivor's status for the flag. Say why a count of zero can never accompany a true flag.

for a senior

Be able to say what a reader may and may not conclude from the pair alone - that a nonzero count with a false flag means the outcome never moved, and that neither field says which way it moved or which attempt moved it.

for a principal

Decide what your teams are allowed to read off these fields. A count of repeated attempts is a workload signal, it names no tests, and any summary built on it needs the underlying results reachable beside it.

## Where the two fields come from When a runner repeats a test, Allure 2's generator ends up with several results for it. `RetryPlugin` groups them, keeps the attempt with the newest start time as the visible one, and hides the rest. Two fields are then set on that survivor to describe what was folded into it: `retriesCount` and `retriesStatusChange`. Neither is written by the test runner and neither exists in the results a runner produces — they are **derived at report-generation time** and appear on the reader-side result model. The plugin computes them from one list. It takes the results in the group, orders them, drops the survivor, and maps what is left into the entries of the `retries` block. Everything else follows from that list: - `setRetriesCount(...)` receives the list's **size**. - `setRetriesStatusChange(...)` receives whether the statuses in that list contain anything the survivor's status is not. ## The off-by-one, and why it is not a bug The single most common misreading is treating `retriesCount` as the number of times the test ran. It is not. Because the list excludes the survivor, the field is the number of *repeats*, not the number of *executions*. | the runner executed the test | `retriesCount` | |---|---| | once | 0 | | twice | 1 | | three times | 2 | The naming is consistent once you read it as "how many retries were needed" rather than "how many attempts exist". It also means the field is a workload figure — the amount of repeated execution the run absorbed for this test — and it says nothing on its own about whether the repetition helped. A count of two accompanied by a visible failure means the test was tried three times and failed at the end. ## What the boolean does and does not claim `retriesStatusChange` is computed by collecting the statuses of the earlier attempts, filtering out every status equal to the survivor's, and setting the field to true when the remaining set is non-empty. Three consequences follow directly, and all three are worth being able to state: 1. **It is symmetric.** A test that failed and then passed sets it. A test that passed and was then re-executed into a failure sets it just the same. The flag records that the outcome moved, not the direction of travel. 2. **It is not an eventually-passed marker.** A test whose earlier attempts were all failures and whose survivor is also a failure leaves the flag false, because nothing differed. Reading a false flag as "this test is fine" gets it exactly backwards in the worst case: a test that failed on every attempt has a nonzero count and a false flag. 3. **It does not name anything.** The boolean identifies neither which attempt differed nor what status it carried. That detail lives in the `retries` block beside it, where each entry carries its own status and status message. ## Reading the pair together The two fields are only useful as a pair, and the four combinations divide cleanly: - **count 0, flag false** — the test executed once. Nothing was folded in. - **count > 0, flag false** — the test was repeated and every attempt landed on the same status as the one shown. Repeated work, no disagreement. - **count > 0, flag true** — the test was repeated and at least one attempt disagreed with the visible outcome. This is the combination worth a reader's attention, in both directions. - **count 0, flag true** — cannot occur. With no earlier attempts there are no statuses to compare, so the filtered set is necessarily empty. ## Where they live, and where they do not Both fields sit on the **surviving** result only. The hidden attempts are ordinary results carrying their own status and their own timings; they are not annotated with counts of their own, and reading one of them directly tells you nothing about how many siblings it had. The fields also travel into the report's serialised data for the visible result and into the leaf entries the trees are built from, which is how a tree row can show a retry marker without opening the test's page. One more boundary is worth stating plainly. Neither field says anything about *why* the repetition happened, and neither is a judgement. `retriesCount` counts what the runner did; `retriesStatusChange` compares statuses. Any interpretation beyond that — whether the repetition was policy or accident, whether a moved outcome should be treated as noise or as a defect — is a conversation the report supports rather than one it settles.

  • A test ran three times in one execution. What does `retriesCount` read on the row you see?
    Two. The plugin builds its list from every attempt except the survivor and then sets the count to that list's size, so the field is always one less than the number of executions the runner actually performed.
  • Can `retriesStatusChange` be true when the visible attempt failed?
    Yes. The flag only asks whether an earlier attempt carried a status the survivor does not. A test that passed first and failed on a later attempt sets it exactly as a test that failed first and passed later does — the flag reports movement, never direction.

saying these in an interview costs you the question

  • Reads retriesCount as the total number of executions
  • Thinks retriesStatusChange means the test eventually passed
  • Assumes a nonzero count implies an earlier failure
  • Expects both fields on every attempt, not just the survivor