Glossary

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.

ManviManvi9 min read
A horizontal rail of five numbered step-nodes threaded by a lime tracer line, the last node cracking into a failure burst that continues into a console, network, and replay state cluster read by an AI agent glyph

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.

repro-steps.txttext
# 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.

A horizontal reproduction recipe: five numbered step-nodes connected by a lime tracer line, the fifth cracking into a failure burst, then continuing into a state cluster of console, network, and replay icons that an AI-agent hexagon reads over a dotted MCP connector
A reproduction recipe: steps 1-5 are the actions; the failure burst is where expected and actual diverge; the state cluster (console, network, replay) is what turns the recipe from 'works for me' into a deterministic repro.

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, meaning 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.

FeatureTraitWeak repro stepsStrong repro steps
Entry state / preconditionsNoUnstated, so the reader guessesNamed: logged-out, /checkout, empty cart
ActionsNoProse paragraph, 'and then' chainsNumbered, one atomic action per line
Test dataNo'add some items'Exact trigger value ( -1 in Quantity )
Expected vs actualNo'it didn't work'Expected error vs actual 500 + console NaN
EnvironmentNoNoOS, browser, build, account
Frequency (for intermittent bugs)No'sometimes'5/5 (or 3/10) + a replay of one failure
Weak vs strong reproduction steps: the same bug, and why one gets fixed on the first read.

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 requests around it), so 'Step 3: click Place order, expected validation error, actual blank page' sits next to the POST /api/orders request that came back 500. For an environment-specific bug, that bundle carries the browser, OS, viewport, URL and requests 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.

Ship the steps with the state that produced them
See how session replay works

Frequently asked questions

Frequently asked questions

Sources

  1. Bug Writing Guidelines: Steps to reproduce are 'the most important part of any bug report', Mozilla / Bugzilla (2025)
  2. Free Bug Report Template: steps to reproduce, expected vs actual, environment, severity, Atlassian (2026)
  3. How to Write a Good Bug Report: reproducibility, minimal steps, expected vs actual, Software Testing Help (2026)
  4. Flaky Tests at Google and How We Mitigate Them: ~1.5% of runs flaky, ~16% of tests, Google Testing Blog (2016-05)
  5. Works for me! Characterizing non-reproducible bug reports (Firefox + Eclipse empirical study), Mozilla Foundation / Empirical Software Engineering (2022)
  6. Why are Some Bugs Non-Reproducible? An Empirical Investigation using Data Fusion, arXiv (ICSME 2020) (2021)
  7. Configuring issue templates: Steps to reproduce / Expected / Actual fields, GitHub Docs (2026)
  8. CLI Debug Mode and /btw Support: /debug for bugs 'hard to reproduce or understand', Cursor / Anysphere (2026-04-14)
Share:

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam. Unsubscribe anytime.