How to Write a Bug Report
Learn how to write bug reports developers can act on right away — titles, steps, expected vs actual, severity vs priority, evidence for mobile and web, and the bug life cycle — with a full worked example and a hands-on exercise.
You’re testing a shopping app. You add a pair of shoes to the cart, go to checkout, type in a card number, and the Pay button just… stays grey. You found a bugBugA problem in the software that makes it behave differently from what is expected.Open in glossary →. Congratulations!
Now comes the part that decides whether that bug ever gets fixed: writing it down. A great find with a poor report often ends up as “Cannot reproduce” and quietly disappears. A great report gets picked up, fixed and shipped, sometimes the same day.
Why bug reports matter
A bug reportBug reportA clear written description of a bug so developers can reproduce and fix it.Open in glossary → is a written description of a problem, detailed enough that someone else can see the bug for themselves. That last part is the whole point:
Your report is a set of instructions for recreating the problem, plus the evidence that it’s real. It’s read by more people than you might think:
- Developers use it to reproduce and fix the bug.
- The lead or product owner uses it to decide how urgent it is.
- You (or another tester) use it later to retestRetestingRunning the same test again to confirm that a reported bug is really fixed.Open in glossary → the fix.
- Everyone searches old reports months later, when a similar bug shows up.
If you’ve read How to Write Test Cases, a lot of this will feel familiar. A bug report is like a test caseTest caseA written set of steps, data and an expected result used to check one specific behavior.Open in glossary → that failed: preconditions, steps, expected result. It adds what actually happened and the proof.
The essentials
What goes into a bug report
| Field | Example |
|---|---|
| ID | SHOP-1423 (usually created by the tool) |
| Title | Checkout: Pay button stays disabled after entering a valid card on iOS 18 |
| Environment | ShopFast 4.12.0 (build 812), iPhone 15, iOS 18.0.1, Staging |
| Preconditions | Signed in; 1 item in cart; no saved cards |
| Steps to reproduce | 1. Tap Checkout 2. Select Card 3. Enter 4242 4242 4242 4242, 12/30, 123 |
| Expected result | Pay button becomes enabled and places the order |
| Actual result | Pay button stays grey and can’t be tapped; no error shown |
| Severity / Priority | Major / High |
| Reproducibility | 5/5 attempts |
| Attachments | Screen recording, screenshot, device log |
Write a title that says where, what and when
A good title follows a simple formula: Where + What + When.
Checkout: Pay button stays disabled after entering a valid card on iOS 18
Specific
Checkout: Pay button stays disabled after entering a valid card on iOS 18
Vague
Payment broken!!!
Severity vs priority
- SeveritySeverityHow much damage a bug does to the product or the user.Open in glossary → is how badly the bug hurts the product (a crash is worse than a typo).
- PriorityPriorityHow soon a bug should be fixed, based on business needs.Open in glossary → is how soon the team should fix it.
Want the full picture? See Severity vs Priority.
They’re independent. A typo in the app’s name on the home screen is low severity but high priority. A crash in a settings screen almost nobody uses can be high severity but lower priority.
Five habits of great bug reports
- One bug per report. Found two problems? File two reports.
- Exact steps and data. Numbered steps, real values, starting from a known state.
- Expected vs actual. Say what should happen and what you saw instead. Never just “doesn’t work”.
- Count how often it happens. Try again and write “3/5 attempts” or “5/5”.
- Attach evidence. A short screen recording beats a paragraph. Add logs when you can.
A great bug report
Checkout: Pay button stays disabled after entering a valid card on iOS 18
- Severity
- Major
- Priority
- High
- Reproducible
- 5/5 attempts (always)
- Component
- Checkout
- Labels
- iospaymentsregression
- Reporter
- Mia Rossi (QA)
- Assignee
- Unassigned
Environment
- App version:
- ShopFast 4.12.0 (build 812)
- Device:
- iPhone 15
- OS:
- iOS 18.0.1
- Environment:
- Staging
- Network:
- Wi-Fi
- Account:
- mia.qa+1@shopfast.test
Preconditions
- Signed in with a test account that has no saved cards
- Cart contains 1 item ("Trail Running Shoes", $54.97)
Steps to reproduce
- Open the Cart tab and tap "Checkout"
- On the Payment screen, select "Credit or debit card"
- Enter card number 4242 4242 4242 4242, expiry 12/30, CVC 123
- Tap outside the fields to close the keyboard
Expected result
The "Pay $54.97" button becomes enabled (blue) and tapping it places the order.
Actual result
The "Pay $54.97" button stays grey and cannot be tapped. No error is shown under any field. The user cannot pay by card.
Notes
- Does NOT happen on Android 15 (Pixel 8, same build 812).
- Does NOT happen in build 4.11.2 on the same iPhone, so this looks like a regression.
- Workaround: Apple Pay still works.
Attachments
- pay-button-disabled.mp4· 0:21 screen recording
- payment-screen.png
- iphone15-console.log· Xcode console, 14:02–14:04
Switch to Detailed at the top of the page for the full walkthrough: every field explained, the four severity and priority combinations, how to capture evidence on iOS, Android and the web, how to isolate a bug, the bug life cycle, and Jira tips.
Anatomy of a bug report
Teams use different tools (JiraJiraA popular tool for tracking bugs, user stories and tasks.Open in glossary →, Azure DevOps, Linear, GitHub Issues, even a spreadsheet), but good bug reports contain the same building blocks.
| Field | What it means | Example |
|---|---|---|
| ID | A unique reference. Usually generated by the tool | SHOP-1423 |
| Title | One line that says where, what and when | Checkout: Pay button stays disabled after entering a valid card on iOS 18 |
| EnvironmentEnvironmentA setup of servers, data and configuration where the software runs, such as dev, staging or production.Open in glossary → | Where you saw it: app version and buildBuildA specific compiled version of the app or website that is ready to be installed or tested.Open in glossary →, device, OS version, browser and version, test environment (stagingStagingA pre-release environment that mirrors production as closely as possible.Open in glossary → or productionProductionThe live environment that real users use.Open in glossary →), network | ShopFast 4.12.0 (build 812), iPhone 15, iOS 18.0.1, Staging, Wi-Fi |
| PreconditionsPreconditionSomething that must already be true before you can start a test.Open in glossary → | The state needed before step 1: account type, data, settings | Signed in; 1 item in cart; no saved cards |
| Steps to reproduce | Numbered, specific actions with exact test dataTest dataThe input values, accounts and records you use while testing.Open in glossary → | 1. Tap Checkout 2. Select Credit or debit card 3. Enter… |
| Expected resultExpected resultWhat should happen when you perform a test step, according to the requirements.Open in glossary → | What should happen, according to the requirement or common sense | Pay button becomes enabled; tapping it places the order |
| Actual resultActual resultWhat really happened when you performed the test.Open in glossary → | What really happened, described precisely | Pay button stays grey and can’t be tapped; no error shown |
| SeveritySeverityHow much damage a bug does to the product or the user.Open in glossary → | How badly the bug affects the product | Critical / Major / Minor / Trivial |
| PriorityPriorityHow soon a bug should be fixed, based on business needs.Open in glossary → | How soon it should be fixed | Highest / High / Medium / Low |
| Reproducibility | How often the bug happens when you follow the steps | 5/5 attempts (always), 2/10 (intermittent) |
| Attachments | Proof and clues: screenshots, screen recordings, device logs, browser console errors, network recordings (HAR files) | pay-button-disabled.mp4, iphone15-console.log |
| Component / labels | Which part of the product, plus searchable tags | Component: Checkout · Labels: ios, payments, regression |
Writing a great title
The title is the most-read part of your report. It shows up in lists, search results, sprint boards and notification emails. Many people will only ever read the title, so it has to work on its own.
Use the formula Where + What + When:
| Part | Question it answers | Example |
|---|---|---|
| Where | Which screen or feature? | Checkout: |
| What | What goes wrong, as you observed it? | Pay button stays disabled |
| When | Under which condition, trigger or platform? | after entering a valid card on iOS 18 |
Where + what + when
- Checkout: Pay button stays disabled after entering a valid card on iOS 18
- Order tracking: Courier map freezes after switching from Wi-Fi to mobile data
- Search: Results show yesterday’s prices when the cache is not cleared
Vague or emotional
- Payment broken!!!
- Map issue
- Bug in search
- URGENT: app is unusable
A few rules of thumb:
- Describe the symptom, not your guess at the cause. “Pay button stays disabled” is a fact. “Card validation regex is wrong” is a guess, and it might be wrong.
- Skip “Bug:” and “Issue with”. Everyone knows it’s a bug; it’s in the bug tracker.
- Keep it under about 100 characters. Details go in the description.
- No capitals, no exclamation marks. Urgency belongs in the priority field.
Try it yourself:
Title builder
Fill in the three parts. Try an example, then write your own.
Screen or feature
What you see go wrong
Trigger, condition, platform
Your title
Checkout: Pay button stays disabled after entering a valid card on iOS 18
- Says where (screen or feature)
- Says what goes wrong, specifically
- Says when it happens
- Calm, factual tone
- 73 characters, fits in a ticket list
Steps, expected and actual
These three fields are where most reports fall apart.
Steps to reproduce
Write steps as if the reader has never opened the app. Start from a known state (that’s what preconditions are for), use one action per step, name the exact buttons, and give the exact data.
Reproducible
Preconditions: Signed in with a test account that has no saved cards. Cart contains 1 item ($54.97).
- Open the Cart tab and tap Checkout.
- Select Credit or debit card.
- Enter card number
4242 4242 4242 4242, expiry12/30, CVC123. - Tap outside the fields to close the keyboard.
A guessing game
- Go to checkout.
- Try to pay with a card.
- It doesn’t work.
Expected vs actual
Expected is what should happen. Point to the requirement if there is one. Actual is what did happen, described as precisely as a camera would.
Precise
Expected: The “Pay $54.97” button becomes enabled (blue). Tapping it places the order and opens the confirmation screen.
Actual: The “Pay $54.97” button stays grey and nothing happens when tapped. No error is shown under any field.
Useless
Expected: It works.
Actual: It doesn’t work.
“Doesn’t work” could mean the app crashedCrashWhen an app suddenly closes or stops working because of an unhandled error.Open in glossary →, froze, showed an error messageError messageText that tells the user something went wrong and, ideally, how to fix it.Open in glossary →, or did nothing. Each of those points to a completely different cause.
One bug per report
It’s tempting to save time by listing everything you found in one ticket. Don’t.
If two bugs seem connected (for example, the same error appears in two places), file them separately and link them. Let the developer decide whether it’s one cause.
Severity vs priority
These two fields sound similar, and confusing them is one of the most common beginner mistakes (and a very common interview question). Here’s what you need for writing reports; the in-depth lesson, with a triage practice board, is Severity vs Priority.
- SeveritySeverityHow much damage a bug does to the product or the user.Open in glossary → describes the impact on the product or user. It’s mostly a technical judgment, and testers usually suggest it.
- PriorityPriorityHow soon a bug should be fixed, based on business needs.Open in glossary → describes the order of fixing. It’s a business decision, usually made by the product owner or lead.
Typical severity levels (scales vary by team):
| Severity | Meaning | Example |
|---|---|---|
| Critical | Crash, data loss, security hole, or a core flow fully blocked with no workaround. Some teams put a separate blockerBlockerA bug so serious that it stops testing or a release from continuing.Open in glossary → level above it | App crashes on launch; payments fail for everyone |
| Major | A main feature is broken or wrong, but a workaround exists | Card payment fails on iOS, Apple Pay still works |
| Minor | A small feature misbehaves; the user can carry on | Sorting by price ignores items on sale |
| Trivial | Cosmetic: typos, alignment, colors | Button label is 2 px off-center |
The four combinations
Because they measure different things, every combination is possible:
| High priority | Low priority | |
|---|---|---|
| High severity | The app crashes when the user taps Pay. Fix now. | The app crashes when importing contacts from a 15-year-old phone format almost no one uses. Serious, but rare. |
| Low severity | The company name is misspelled on the login screen, or the price shows the wrong currency symbol before a big launch. Harmless technically, embarrassing publicly. | A tooltip in a rarely visited settings screen has a typo. |
Reproducibility
Before you file, follow your own steps again. Then write down how often the bug happened:
- 5/5 attempts (always): great, the developer will see it straight away.
- 3/5 attempts: intermittent. Still very useful; say so honestly.
- 1/10 attempts: rare. Report it anyway, with every detail you have and a recording of the time it happened.
A bug you can reproduce every time is reproducibleReproducibleA bug that happens again every time you follow the same steps.Open in glossary →. Bugs that come and go often point to timing problems such as a race conditionRace conditionA bug that depends on the timing or order of events, so it only happens sometimes.Open in glossary →, slow networks, or leftover data in the cacheCacheStored copies of data or files that make apps and websites load faster.Open in glossary →, so the number itself is a clue for the developer.
Gathering evidence
Evidence turns “I saw something weird” into a fact nobody can argue with. Aim for at least one visual (screenshot or recording) and, when you can, logs.
Screenshot: press the Side button + Volume Up together (Home button + Side button on older iPhones). Use the markup tool to circle the problem.
Screen recording: open Control Center and tap the Screen Recording button. If it’s missing, add it in Control Center settings. Recordings are usually the single most useful attachment: they show the exact taps and timing.
Device logs: connect the iPhone to a Mac. With a debug build running from Xcode, errors appear in the Xcode console. Without Xcode, open the Console app on the Mac, select the iPhone, and filter by the app name. Developers often add their own in-app debug menu too; ask your team.
Crash logs: on the iPhone, go to Settings → Privacy & Security → Analytics & Improvements → Analytics Data and look for files starting with the app’s name.
On the simulatorSimulatorSoftware that imitates a device's behavior on your computer, like Apple's iOS Simulator.Open in glossary →: Cmd + S saves a screenshot. To record video, run xcrun simctl io booted recordVideo bug.mp4 in Terminal (press Ctrl + C to stop).
Screenshot: press Power + Volume Down together.
Screen recording: swipe down twice to open Quick Settings and tap Screen record (Android 11 and newer; the exact name varies by manufacturer). Turn on “Show touches” if offered, so taps are visible.
Device logs with adb logcat: enable Developer options and USB debugging, connect the phone, and use the Android Debug Bridge (adb) on your computer (new to it? see adb Basics):
adb logcat -c # clear old logs
# ...now reproduce the bug on the phone...
adb logcat -d > bug-log.txt # save everything logged so far to a file
adb logcat '*:E' # or: watch only errors, live (quotes stop zsh from expanding *)Crashes usually show up as a FATAL EXCEPTION with a stack trace. Attach the file; the developer will know what to look for.
Full bug report: adb bugreport creates a zip with logs and system info. It’s large, so only attach it when a developer asks.
Open the browser’s DevToolsDevToolsDeveloper tools built into browsers for inspecting pages, network traffic and errors.Open in glossary → with F12 or Ctrl + Shift + I (Windows) or Cmd + Option + I (Mac). DevTools for Testers covers each panel in depth.
Console tab: look for red error messages. Copy them into the report as text (not just a screenshot), so they’re searchable.
Network tab: reproduce the bug with the Network tab open and look for failed requests (status codes in red, like 400, 500, or “failed”). Click one to see the request and response. Tick Preserve log so requests survive page reloads.
HAR file: a HAR file is a recording of every network request on the page. In Chrome, click the download (Export HAR) button in the Network tab. Developers can open it and see exactly what the browser sent and received.
Also note: browser name and version, operating system, window size or device emulation, and whether you were signed in. Try an incognito/private window to rule out extensions and leftover cookiesCookieA small piece of data a website stores in your browser, for example to keep you logged in.Open in glossary →.
Testing a website on an iPhone? Turn on Settings → Apps → Safari → Advanced → Web Inspector on the phone (on iOS 17 and older: Settings → Safari → Advanced), connect it to a Mac, and use Safari’s Develop menu to open Web Inspector for the page on the phone.
Isolating the bug
Finding a bug is step one. Narrowing it down is what makes you a tester developers love. Spend a few minutes asking “does it still happen if…?” Each answer goes into the Notes section of your report.
- Other platform? Does it happen on Android too, or only on iOS? On the website as well as the app? A bug on one platform points to platform code; a bug everywhere points to the server or shared logic.
- Other device or OS version? Only on iOS 18, or also iOS 17? Only on a small screen? Only on a Samsung phone? (Android fragmentationFragmentationThe huge variety of device models, screen sizes and OS versions an app must support.Open in glossary → makes this one important.)
- Other browser? Only in Safari, or also in Chrome and Firefox? This is the heart of cross-browser testingCross-browser testingChecking a website works and looks right in different browsers and browser versions.Open in glossary →.
- Other account or state? Signed in vs signed out? New account vs old account? Empty cart vs full cart?
- Other data? Only with this card, or any card? Only with long names, special characters, or another currency?
- Previous version? Did it work in the last release? If yes, it’s a regression, which is extremely useful to know.
- Clean state? Does it still happen after clearing the app’s cache, reinstalling, or in a private browser window?
Don’t overdo it, though. If the bug is clearly serious, report it quickly with what you have, and add findings as comments.
How to write a bug report, step by step
- Reproduce it. Make the bug happen again from a fresh start. Note the exact steps and data as you go.
- Search for duplicates. Someone may have reported it already. If so, add your details as a comment instead of filing a new one.
- Isolate it. Run through the “does it still happen if…” questions above.
- Capture evidence. Record the screen while reproducing; save logs, console errors or a HAR file.
- Write the title. Where + What + When.
- Fill in the details. Environment, preconditions, steps, expected, actual, reproducibility, severity, component, labels.
- Re-read it as a stranger. Could a developer who’s never seen this screen reproduce it from your text alone? Better still, follow your own steps once more, word for word.
Worked example
Here’s the bug from the start of this lesson, written up properly. Notice how it answers every question before a developer has to ask.
Checkout: Pay button stays disabled after entering a valid card on iOS 18
- Severity
- Major
- Priority
- High
- Reproducible
- 5/5 attempts (always)
- Component
- Checkout
- Labels
- iospaymentsregression
- Reporter
- Mia Rossi (QA)
- Assignee
- Unassigned
Environment
- App version:
- ShopFast 4.12.0 (build 812)
- Device:
- iPhone 15
- OS:
- iOS 18.0.1
- Environment:
- Staging
- Network:
- Wi-Fi
- Account:
- mia.qa+1@shopfast.test
Preconditions
- Signed in with a test account that has no saved cards
- Cart contains 1 item ("Trail Running Shoes", $54.97)
Steps to reproduce
- Open the Cart tab and tap "Checkout"
- On the Payment screen, select "Credit or debit card"
- Enter card number 4242 4242 4242 4242, expiry 12/30, CVC 123
- Tap outside the fields to close the keyboard
Expected result
The "Pay $54.97" button becomes enabled (blue) and tapping it places the order.
Actual result
The "Pay $54.97" button stays grey and cannot be tapped. No error is shown under any field. The user cannot pay by card.
Notes
- Does NOT happen on Android 15 (Pixel 8, same build 812).
- Does NOT happen in build 4.11.2 on the same iPhone, so this looks like a regression.
- Workaround: Apple Pay still works.
Attachments
- pay-button-disabled.mp4· 0:21 screen recording
- payment-screen.png
- iphone15-console.log· Xcode console, 14:02–14:04
The bug life cycle
Once you file a report, it moves through a series of statuses called the bug life cycleBug life cycleThe statuses a bug goes through, from New to Closed.Open in glossary →. Exact names vary by team and tool, but the idea is the same everywhere. For every status, who moves the bug and a hands-on ticket simulator, see the Bug Life Cycle lesson.
Bug life cycle
Click a status to see what it means and who moves the bug on.
Happy path
Other ways a bug can end
NewOwner: Tester
You just filed the report. Nobody has looked at it yet. A lead or triage meeting will review it, check it is not a duplicate, and set priority.
Your job doesn’t end when you click Create. Watch your tickets, answer developers’ questions quickly, and when a bug reaches Retest (many teams call it Ready for test), verify it on a build that contains the fix. While you’re there, check the features around it too; fixes sometimes break neighbors, which is why teams also run regression testingRegression testingRe-running tests to check that new changes didn't break things that used to work.Open in glossary →.
Tone: factual, no blame
Bug reports are read by people who worked hard on the feature. A report that sounds like an accusation makes everyone defensive and slows the fix down. You’ll also be working with these developers every day.
Factual
The Pay button stays disabled after entering a valid card on iOS 18. It worked in build 4.11.2. Recording attached.
Blaming
Payments are broken AGAIN. Did anyone even test this before release?? This is really basic stuff.
- Describe the product, not people. “The button stays disabled”, not “you broke the button”.
- Stick to observations. Leave out sarcasm, guesses about who’s at fault, and words like “obviously” or “again”.
- Be humble about causes. “This might be related to…” is fine; “the developer forgot to…” is not.
- Say thanks when a bug is fixed. It costs nothing and builds trust.
Jira tips
JiraJiraA popular tool for tracking bugs, user stories and tasks.Open in glossary → is the most common bug tracker you’ll meet. A few habits make your reports much easier to work with:
-
Choose the issue type “Bug”, not Task or Story, so it shows up in bug filters and reports.
-
Search before you create. Use the search bar, or JQL (Jira’s query language), for example:
project = SHOP AND type = Bug AND text ~ "pay button" AND statusCategory != Done -
Use the fields your team uses: Affects version (where you found it), Component, Labels, Environment, Priority. Consistent fields make dashboards and filters work.
-
Link related tickets with “relates to”, “is blocked by” or “duplicates”, and link the bug to the story it belongs to.
-
Format for readability. Use numbered lists for steps and code blocks for error messages and log snippets.
-
Drag and drop attachments straight into the ticket, and give them meaningful names (
pay-button-disabled.mp4, notIMG_4821.MOV). -
@mention people only when you need their attention, and watch your own tickets so you see questions.
-
Use a template. Many teams keep a bug description template with the headings Environment, Preconditions, Steps, Expected, Actual, Notes. If yours doesn’t, suggest one.
Practice: fix this bug report
A teammate filed the report below. It has the right idea, but almost every part needs work. Find the problems, then compare it with the improved version.
Fix this bug report
A new teammate filed this ticket. Click every part you think is a problem. Some parts are fine, so think before you click.
- Title
- Reporter
- Component
- Environment
- Steps
- Expected
- Actual
- Severity
- Reproducible
- Attachments
0 of 9 problems found
Before you submit a bug report
Before you submit a bug report
0 of 13 checked
Check your understanding
Quick quiz
0/6 answered · 0 correct
1.Why is the 'steps to reproduce' section so important?
2.Which title follows the Where + What + When formula best?
3.The company logo is misspelled on the home screen the day before launch. What is the most likely classification?
4.While testing checkout you also notice a blurry profile photo. What should you do?
5.A bug happened 3 times out of 5 attempts. How should you report it?
6.Your bug is marked 'Fixed'. What happens next?
Key takeaways
- A bug report exists so someone else can reproduce the bug. If they can’t, it won’t get fixed.
- Use the title formula Where + What + When, and describe symptoms, not guesses.
- Always include environment (app version and build, device, OS, browser), preconditions, exact steps, expected and actual results.
- One bug per report, with a reproducibility rate like “3/5 attempts”.
- Severity is impact; priority is fixing order. They are independent.
- Attach evidence: screen recordings, screenshots, device logs, console errors, HAR files. Remove sensitive data.
- Isolate before you file, keep the tone factual and blame-free, and follow your bug through its life cycle to retest.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡