Software Testing
How to Write a Bug Report Developers Actually Act On
The anatomy of a bug report that gets fixed: a precise title, minimal repro steps, evidence, environment and a defensible severity, with bad-to-good rewrites.
Table of contents
Finding a bug is half the job. The other half is getting it fixed, and that depends almost entirely on what happens when a developer opens your ticket. If they can understand the problem in thirty seconds and reproduce it in five minutes, it gets picked up. If they have to message you for the steps, guess the environment or squint at a cropped screenshot, it drifts down the backlog, regardless of how serious it is.
A good bug report is not longer. It is more precise. This article covers what to include, how to make a bug reproducible, how to attach evidence that answers questions before they are asked, and how to argue severity and priority without starting a fight. It finishes with three rewrites that take a report from vague to actionable.
Who reads a bug report
Before the anatomy, consider the audience. A bug report is read by at least three people with different questions:
- The developer wants to know: can I reproduce it, where in the code is it likely to be, and what is the expected behaviour?
- The product owner or triage lead wants to know: how many users does this affect, how badly, and does it need to go into this sprint?
- Future you, or another tester, wants to know: is this a duplicate, was it fixed, and how do I verify the fix?
Every section below serves at least one of these readers. If a section serves none of them, leave it out.
The anatomy of a good report
Title: what is broken, where, under which condition
The title is what appears in triage lists, search results and release notes. It should let someone decide whether to open the ticket.
A useful pattern is [Area] Symptom when condition:
- Bad: “Checkout not working”
- Bad: “Bug in payment”
- Good: “Checkout: ‘Place order’ button stays disabled after applying a 100% discount code”
- Good: “Search API returns 500 when query contains an emoji”
Avoid words like “broken”, “issue” or “problem” on their own. They describe your feelings, not the behaviour.
Summary: one or two sentences of context
Restate the problem in plain language, plus the impact if it is not obvious. “Customers using a full-discount code cannot complete checkout, so free-trial redemptions are blocked.” That second clause is what helps triage.
Steps to reproduce
Numbered, minimal and starting from a known state. Each step should be a single action a person can perform without interpretation.
- Log in as a standard customer (test account:
qa-customer-07). - Add any single product to the basket.
- Go to checkout and apply the code
FREE100. - Observe the “Place order” button.
Rules that make steps reliable:
- Start from a known state. “Logged in as an admin with an empty basket” beats “go to the page”.
- Remove steps that do not matter. If you can reproduce without step 3, delete step 3. Minimising the steps is often where you discover the real trigger.
- Name the exact data. The product, account, file or input value. “Some long text” is not reproducible; a 256-character string is.
- Include the reproduction rate. “Reproduced 5 out of 5 times” or “3 out of 10 times” tells the developer whether to expect a deterministic failure or a race.
Expected and actual results
State both, separately, and make them specific enough to assert on.
- Expected: The order total shows 0.00 and “Place order” is enabled.
- Actual: The order total shows 0.00 but “Place order” remains disabled. No error is displayed.
Where does “expected” come from? Cite the source when you can: an acceptance criterion, a spec, a design, previous behaviour, or a consistency argument (“every other zero-value flow allows submission”). This prevents the “works as designed” back-and-forth.
Environment
Include what is needed to rule things in or out, and nothing more:
- Build, version or commit, plus environment name (for example, staging build 2026.10.3)
- Browser and version, operating system, device for UI bugs
- Account type, role, feature flags and locale if they affect behaviour
- API endpoint, method and relevant request headers for API bugs
If you have checked other environments, say so: “Also reproduces on Firefox; does not reproduce on production build 2026.10.1.” That narrows the search considerably and hints that this is a regression.
Evidence
Evidence should answer questions before anyone asks them.
- Screenshot with the relevant area visible, including the URL bar when it matters. Annotate if the problem is subtle.
- Screen recording for anything involving timing, animation or a sequence of actions. Keep it short.
- Console errors from browser DevTools, copied as text, not as a screenshot of text.
- Network request and response for the failing call: method, URL, status, and the response body. A HAR file is helpful for complex flows (scrub tokens and personal data first).
- Logs and correlation IDs. If the response includes a request ID or trace ID header, include it. It lets a developer jump straight to the server logs.
- For API bugs, a runnable request. A
curlcommand is the most portable evidence there is:
curl -i -X POST 'https://staging.example.com/api/v2/search' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <redacted>' \
-d '{"query": "coffee ☕"}'
Severity, priority and impact
Covered in detail below. At minimum, state the severity you believe is correct and one sentence explaining why.
Optional but valuable
- Workaround, if one exists. It directly affects priority.
- First seen / regression information. “Worked in 2026.09.4” lets developers bisect.
- Related tickets, including duplicates you checked and dismissed.
- Your hypothesis, clearly labelled as one. “Possibly the button’s enabled state checks
total > 0.” It can save time, but never present a guess as a finding.
Making bugs reproducible
Non-reproducible reports are the ones that get closed with “cannot reproduce”. Before filing, try to reduce the bug to the smallest reliable recipe.
Isolate the variables
Change one thing at a time and note what affects the outcome:
- Different user, role or account age
- Different data (another product, a shorter input, a different currency)
- Different browser, viewport or device
- Fresh session versus existing session
- Clean cache or incognito window
- Fast versus slow network (DevTools throttling)
When the bug disappears after a change, you have found a condition. Put it in the title.
Intermittent bugs
Some bugs really are intermittent. Do not let that stop you filing them, but frame them honestly:
- Give the reproduction rate and the number of attempts.
- Describe what you have ruled out.
- Attach evidence from a failing occurrence (timestamps, request IDs, recordings).
- Note any pattern, however weak: “only seen after the session has been open for more than 30 minutes”.
An intermittent bug with good evidence is far more actionable than one described as “sometimes it fails”.
Severity versus priority
These two fields cause more arguments than anything else in a bug tracker, mostly because people use them interchangeably.
- Severity is the impact of the defect on the system or user. It is mostly a technical judgement and is usually set by the tester.
- Priority is the order in which it should be fixed. It is a business decision, usually set or confirmed by the product owner, and it considers severity alongside how many users are affected, deadlines, workarounds and risk.
They often align, but not always:
| Example | Severity | Priority |
|---|---|---|
| Payment fails for all card users | Critical | Highest |
| Company name misspelled on the home page | Low | High (visible, reputational, trivial fix) |
| Crash in an admin report used once a year, with a CSV export workaround | High | Low |
| Tooltip clipped on one rarely used settings page | Low | Low |
The common scale is something like Critical, Major (or High), Minor (or Medium) and Trivial (or Low). The labels matter less than having written definitions your team agrees on. A useful set of definitions:
- Critical: core functionality unavailable, data loss or corruption, security exposure, no workaround.
- Major: important functionality broken or wrong for a significant group of users; a workaround exists but is painful.
- Minor: functionality works but incorrectly in edge cases, or a reasonable workaround exists.
- Trivial: cosmetic issues with no functional impact.
If you want a structured way to reason through it, the bug severity and priority helper walks through impact, reach and workaround questions.
Bad to good: three rewrites
Rewrite 1: the vague UI bug
Before
Title: Login broken
Login doesn’t work on staging. Please fix ASAP.
After
Title: Login: “Sign in” returns to the login page without an error when the email contains uppercase letters
Steps
- Open the staging login page in a fresh incognito window.
- Enter
QA.User@example.testand the correct password.- Select “Sign in”.
Expected: User is signed in and lands on the dashboard (emails are case-insensitive per the account spec). Actual: The page reloads to the empty login form. No error message. Network shows
POST /api/sessionreturning 401. Reproduction: 5 out of 5. Signing in withqa.user@example.test(lowercase) works. Environment: staging build 2026.10.3, latest stable Chrome and Firefox on Windows 11 (exact versions in the attached system info). Evidence: recording attached; response body and request ID attached. Severity: Major. Users who registered with mixed-case emails cannot sign in; workaround is typing lowercase, which users will not know.
The rewrite contains the discovery that makes it fixable: the uppercase condition. That came from isolating variables before filing.
Rewrite 2: the API bug without a request
Before
Title: Search API error
Search gives 500 sometimes.
After
Title: Search API returns 500 when the query contains characters outside the Basic Multilingual Plane (for example, emoji)
Request: curl command attached (token redacted). Expected: 200 with results, or 400 with a validation error if such characters are unsupported. Actual: 500 with a generic error body. Response header
x-request-id: 7f3c...for log lookup. Reproduction: Always, with any emoji. Accented Latin characters work. Severity: Minor to Major depending on product decision. A 500 suggests unhandled input, which is worth checking for other unvalidated characters.
Rewrite 3: the “everything is wrong” report
Before
Title: Profile page issues
The profile page has lots of problems: the avatar upload fails, the date is wrong, and the save button is in the wrong place.
After: three separate tickets. One defect per report. Bundled reports cannot be prioritised, assigned or closed cleanly, and the cosmetic issue ends up blocking the closure of the functional one.
Making it fast to write
Good reports take discipline, not time, once you have a template. A consistent structure also makes your reports easier to scan in triage. The bug report generator produces a structured report from a short form, and the Jira ticket generator formats it for copying into Jira with the right markup. Adapt the template to your team’s fields rather than adding sections nobody reads.
Before you submit, run a quick self-check:
- Could someone who has never seen this feature reproduce it from the steps alone?
- Does the title say what, where and when?
- Are expected and actual both specific?
- Is the evidence attached as text where possible, with secrets removed?
- Is this one defect, and have you searched for duplicates?
Key takeaways
- Write for three readers: the developer, the triage lead and whoever verifies the fix.
- Titles follow area, symptom, condition. Steps are minimal, numbered and start from a known state.
- Separate expected from actual and cite where “expected” comes from.
- Evidence should be complete, textual where possible and redacted.
- Severity is impact; priority is order. Argue with concrete reach and workarounds.
- One defect per report, always.
#bug-reporting#manual-testing#defect-management#communication
Practise it
Related interview questions
SDETRole Editorial Team
Practising SDETs and QA engineers who write, test and review every SDETRole article and lab. Code samples are run against current tool versions before publishing.