A few years ago, I submitted what I thought was a thorough bug report. It had a title, a description, and a screenshot. The developer’s response came back three hours later: “Can you reproduce this? What was your environment? What exactly did you click?”
I had wasted both our time. The bug was real, but my report wasn’t useful. It gave the developer a mystery to solve before they could even begin to fix anything.
That experience changed how I approach bug documentation. Today, a good bug report isn’t just a record of something going wrong. It’s a complete handoff, everything a developer needs to reproduce, understand, and fix an issue without a single follow-up question.
Here’s exactly what that looks like.
The problem with most bug reports
Before we talk about what good looks like, let’s be honest about what bad looks like. Most bug reports, including many from experienced testers, suffer from the same set of problems.
- What Bad Looks Like
Title: “Checkout is broken”
Description: “When I try to checkout the page shows an error. I tried again and it still didn’t work. Please fix.”
Attachment: screenshot_final_2.png (a blurry mobile screenshot of an error toast)
You’ve probably seen this. Maybe you’ve written something like it. I have. The problem isn’t laziness. It’s that nobody taught us what a bug report is actually for.
A bug report is not a complaint. It’s a technical specification for a problem. Its job is to give a developer enough information to reproduce the issue in their own environment, understand what should have happened, and have a clear starting point for the fix.
Everything that doesn’t serve those three goals is either noise or missing information.
The anatomy of a developer-ready bug report
Here are the eight components that every good bug report needs, and what each one actually accomplishes.
None of these fields are optional. A report missing any one of them is an incomplete report. It’s the QA equivalent of a half-built bridge.
Let’s look at a real example
In 2025, I identified CVE-2025-11733, an unauthenticated stored XSS vulnerability in the Footnotes Made Easy WordPress plugin. Here’s how I documented it, and why each decision was deliberate.
Writing the title
A bad title for this bug would have been: “XSS vulnerability in plugin.”
A good title is specific enough to be useful without reading the full report:
- Good Title
Unauthenticated users can inject arbitrary script via footnote shortcode identifier parameter (Stored XSS)
Writing steps to reproduce
This is where most bug reports fall apart. The goal is to write steps so precise that someone who has never used the product before can follow them and hit the same bug you did.
Three rules for good reproduction steps:
1. Start from a known state
Always specify the starting conditions; which page, which user role, which data is already in place. Never assume the developer’s environment looks like yours.
2. One action per step
“Navigate to the product page, add it to cart, and proceed to checkout” is three steps, not one. Break it down. The more granular your steps, the easier it is to pinpoint exactly where the failure occurs.
3. Include exact data
Don’t write “enter a malicious value.” Write the exact payload: <img src=x onerror=alert(document.cookie)>. A developer needs to paste exactly what you pasted.
Expected vs actual — the most misunderstood fields
Many testers write the actual result and leave the expected result blank, or write something vague like “it should work.” This defeats the purpose entirely.
The expected result is not what you personally hoped would happen. It is what the system is specified or designed to do. For a security bug like CVE-2025-11733, the expected result was clear: the identifier attribute should be sanitized using esc_attr() before output. HTML tags should be stripped. A script tag should never execute.
The actual result was equally specific: the raw value was passed directly into the DOM, and the injected <img onerror> tag executed JavaScript in the visitor’s browser.
-
Pro Tip
If you can’t write a specific expected result, that’s a signal you don’t fully understand what the feature is supposed to do. That’s your cue to check the spec, the documentation, or ask before filing the report.
Environment details
Environment information is where lazy bug reports consistently cut corners. “Latest Chrome on Windows” is not enough. Here’s what to capture:
- Operating system and version (Windows 11, macOS 14.3, Ubuntu 22.04)
- Browser name and exact version number
- Application or plugin version (and the version it was patched in, if known)
- PHP and WordPress versions for WordPress bugs
- Server environment if relevant (Apache vs Nginx, caching plugins active)
- User role that triggered the issue
- Whether you can reproduce on a clean install with no other plugins active
That last point, clean install reproduction, is particularly important for WordPress. A bug that only appears with three other plugins active is a conflict issue, not a core bug. Knowing this upfront completely changes how the developer approaches the fix.
“A bug report is not a complaint.
It is a technical specification for a problem.”
What good attachments look like
Screenshots are the minimum. For bugs that are hard to reproduce or involve a sequence of interactions, a screen recording is worth ten screenshots. For security issues, the raw HTTP request captured in Burp Suite or browser DevTools is often more useful than a screenshot of the error.
For the CVE-2025-11733 report, I attached four things: a screen recording showing the alert dialog firing, a screenshot of the alert with the cookie contents visible, the vulnerable version of the PHP file, and the patched version showing exactly which line changed. That meant a developer could see the problem, understand the fix, and apply it, all from the attachments alone.
The one thing that separates good QA testers from great ones
Everything above covers what to include in a bug report. But there’s one habit that separates good testers from great ones, and it has nothing to do with the fields themselves.
Great testers read their own bug reports before submitting them.
Read it as if you are the developer receiving it. Ask yourself: can I reproduce this from these steps alone, without asking a single question? If the answer is no, if there’s any ambiguity, any missing context, any step that assumes knowledge you haven’t provided, revise it before you submit.
That discipline, applied consistently, is what turns a QA tester into someone developers actually want to work with. It is also, in my experience, what the best clients on platforms like Upwork are looking for when they hire a QA specialist. Not just someone who finds bugs, but someone who documents them in a way that makes fixing them fast.
A quick checklist before you submit
- Title is specific — it names the component, the action, and the failure
- Severity is assigned and justified
- Steps start from a known state and use exact data
- Expected result references actual spec or designed behavior
- Actual result describes the observable failure precisely
- Environment details are complete — no vague "latest version"
- Reproducibility rate is stated
- Attachments include a screen recording, not just a screenshot
- You've read it back as the developer receiving it
Use this list every time. The discipline of running through it, even when you’re tired, even when the bug feels obvious, is what separates a tester who slows teams down from one who makes them faster.