What Are Reproduction Steps? How to Write Steps That Actually Repro
Reproduction steps are the ordered actions that reliably trigger a bug from a known starting state. Here is the anatomy of steps that actually repro, and why steps alone still fail on some bugs.
Definition
Reproduction steps are the ordered, numbered actions that reliably trigger a bug, written from a known starting state so anyone can recreate the failure. They are paired with the expected result and the actual result observed at the point things break.
You will also see them as repro steps, steps to reproduce, or the acronym STR. The core is always the same triad: a numbered path of actions, what you expected to happen, and what actually happened. GitHub bakes exactly these three fields into its issue-form templates — 'Steps to reproduce', 'Expected behavior', 'Actual behavior' — making the structure the de facto standard enforced at file time across millions of repositories. Atlassian's bug-report template uses the same spine and adds the operational fields — environment, severity, and evidence — that turn a description into something a team can act on. This page defines STR, breaks down its anatomy, and is honest about where steps stop working; for the full six-field report it lives inside, see the bug report template guide.
The anatomy of steps that actually repro
Most reports fail not because the writer left something out, but because they described only the clicks. A reproduction that survives handoff to a stranger has five distinct parts, and each one closes a specific gap where reproducibility leaks away.
1. Preconditions (the entry state.) Where does the reader start? 'Logged out, on /checkout, cart empty' removes the guesswork about setup. Software Testing Help is explicit that you should start from a clean, known state and never assume or skip a reproducing step — the person following your report should be able to complete each line without inventing context. 2. Numbered atomic actions. One action per line, in order, with no 'and then' chains that hide a branch. Atlassian calls the numbered, explicit list of actions the single most vital section of the report. 3. Test data. Name the exact values: 'enter -1 in the Quantity field', not 'add some items'. The triggering value is frequently the whole bug. 4. Expected vs actual, stated at the failing step, because ambiguous expectations are not cosmetic: the data-fusion study of 576 non-reproducible reports found ambiguous or outdated expected behavior drove about 8% of non-reproducibility on its own. 5. Environment. OS, browser, build, and account. A step that fails on Chrome and passes on Safari is only reproducible once the reader knows which one you were on — which is why Atlassian treats environment, severity, and frequency as first-class fields rather than optional notes.
A bad report, rewritten
The gap between a report that bounces and one that gets fixed is almost always concreteness. Here is the same bug written twice. The first version is what triage teams see most often; the second is what Mozilla, Atlassian, and every QA authority are actually asking for.
# BAD — a description, not a reproduction
Title: Checkout is broken
Steps: I tried to buy something and it didn't work.
Add some items and check out, you'll see the error.
# GOOD — a recipe anyone can re-run
Title: Checkout returns 500 when cart quantity is set to a negative number
Preconditions: Logged in as a standard user, on /checkout, one item in cart.
Environment: Chrome 138, macOS 15.4, staging (app.example.com), build 2.4.1.
Frequency: 5/5 attempts (always).
Steps:
1. In the cart, click the Quantity field for "Blue Mug".
2. Type -1 and press Tab.
3. Click "Place order".
Expected: Inline validation error — "Quantity must be at least 1".
Actual: Page goes blank. POST /api/orders returns 500.
Console logs "TypeError: total is NaN".The bad version is not lazy so much as under-specified: it names no entry state, no value, no environment, and no expected result, so the developer has to reconstruct all four before they can even start. The good version is not longer for the sake of it — every added line kills a specific question a triager would otherwise have to ask. Note the discipline Mozilla's Bug Writing Guidelines preach: minimize to the shortest sequence that still triggers the bug. Three atomic steps beat a paragraph. The -1 is the test data that is the actual trigger; the 500 and the NaN in the console are the actual result made unambiguous.
Why reproducibility drives fix time
Mozilla's guidelines are blunt about the stakes: 'Steps to reproduce are the most important part of any bug report. If a developer is able to reproduce the bug, the bug is very likely to be fixed.' The same page warns of the inverse — 'if the steps are unclear, it might not even be possible to know whether the bug has been fixed.' Reproduction is the gate. Triage priority, root-cause analysis, and the regression test that stops the bug coming back all depend on a developer being able to make the failure happen on demand.
The cost of missing that gate is measurable. The 'Works for me!' empirical study of Firefox and Eclipse quantified it: non-reproducible reports are about 17% of all bug reports and stay active roughly three months longer than reproducible ones. The reason those reports bounce is structural — steps capture actions, not state. The same five clicks can pass or fail depending on the API response, a feature flag, timing, cached data, the viewport, or the account. The data-fusion follow-up found roughly 14% of Eclipse's non-reproducible reports simply lacked the information required to reproduce. Tellingly, 66% of the non-reproducible reports that were eventually fixed had in fact been reproduced once enough information finally arrived — proof that the missing ingredient is almost always state, not effort.
Intermittent bugs and heisenbugs
The hardest reproductions are the ones that only fail sometimes — heisenbugs, in the folklore, because looking too closely makes them vanish. These are not rare edge cases; they are endemic even in mature, heavily automated systems. Google's Testing Blog reported that around 1.5% of all test runs are flaky and nearly 16% of their tests exhibit some level of flakiness — a test that both passes and fails against the same version of the code. The dominant causes are exactly the state variables that prose steps cannot capture: asynchronous timing and waits, concurrency and resource contention, and test-order dependence.
The reporting discipline for an intermittent bug is different in two concrete ways. First, record the frequency — '3 of 10 attempts', not just 'sometimes' — because a reader who does not know a bug is flaky will run your steps once, see it pass, and close the ticket as non-reproducible. This is why Atlassian's template makes frequency (always / occasionally / rarely) an explicit field. Second, stop trying to re-describe the failure and capture one. A session replay of a single real failure — with the console output and the network timing frozen at the moment it broke — is worth more than ten prose attempts to characterize a bug that only appears under conditions you cannot reliably re-create by hand. For a flaky bug, the replay is the reproduction.
| Feature | Trait | Weak repro steps | Strong repro steps |
|---|---|---|---|
| Entry state / preconditions | — | Unstated — reader guesses | Named: logged-out, /checkout, empty cart |
| Actions | — | Prose paragraph, 'and then' chains | Numbered, one atomic action per line |
| Test data | — | 'add some items' | Exact trigger value ( -1 in Quantity ) |
| Expected vs actual | — | 'it didn't work' | Expected error vs actual 500 + console NaN |
| Environment | — | — | OS, browser, build, account |
| Frequency (for intermittent bugs) | — | 'sometimes' | 5/5 (or 3/10) + a replay of one failure |
Caveats: when steps aren't enough
Perfect steps do not guarantee a reproduction, and pretending otherwise sends people chasing a wording problem that is really a state problem. Some bugs are environment-specific — they need a particular GPU driver, a corporate proxy, a specific browser build, or a locale, and no amount of clarity in the numbered list will surface them on a developer's clean laptop. Some are data-specific — they only fire for a row that already exists in one user's account, so 'reproduce with a fresh account' quietly hides the bug. Others are genuinely non-deterministic race conditions where the trigger is timing you cannot type into a step. In all three cases the steps are correct and the bug is still non-reproducible, which is exactly why the empirical data shows information — not effort — is the missing ingredient two-thirds of the time.
This is where capturing state alongside the steps stops being a nice-to-have. BugMojo's browser extension records the failure with the surrounding state attached — an rrweb session replay of exactly what the user did, the console output, and the network request that fed the data — so 'Step 3: click Place order, expected validation error, actual blank page' sits next to the precise POST /api/orders response and the NaN that produced the 500. For an environment- or data-specific bug, that bundle carries the account, build, and request the developer's clean machine does not have. State also matters once the reader is an agent: Cursor shipped a /debug command in April 2026 for bugs 'hard to reproduce or understand', where the agent forms hypotheses and uses runtime information to localize the fault — and an agent reading prose steps alone still guesses at the triggering state. Handing it the steps plus the replay, console, and network is the difference between 'reproduce this for me' as a hunt and the same request as a deterministic task.
Frequently asked questions
Frequently asked questions
Sources
- Bug Writing Guidelines — Steps to reproduce are 'the most important part of any bug report' — Mozilla / Bugzilla (2025)
- Free Bug Report Template — steps to reproduce, expected vs actual, environment, severity — Atlassian (2026)
- How to Write a Good Bug Report — reproducibility, minimal steps, expected vs actual — Software Testing Help (2026)
- Flaky Tests at Google and How We Mitigate Them — ~1.5% of runs flaky, ~16% of tests — Google Testing Blog (2016-05)
- Works for me! Characterizing non-reproducible bug reports (Firefox + Eclipse empirical study) — Mozilla Foundation / Empirical Software Engineering (2022)
- Why are Some Bugs Non-Reproducible? An Empirical Investigation using Data Fusion — arXiv (ICSME 2020) (2021)
- Configuring issue templates — Steps to reproduce / Expected / Actual fields — GitHub Docs (2026)
- CLI Debug Mode and /btw Support — /debug for bugs 'hard to reproduce or understand' — Cursor / Anysphere (2026-04-14)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

