How to write a bug report: a worked example on a web form
A bug report has one reader, the person who will fix it, and one job: let that person see the bug without you in the room. The examples below are from a ticket checkout form, because forms are where most practice exercises put their bugs; the shape is the same for an API or a mobile screen. The reports are constructed for this page, not taken from anyone's tracker.
The four lines that do the work
Bug trackers offer many fields. Four of them carry the report; the rest can be short or empty.
| Line | What goes in it | What it is for |
|---|---|---|
| Steps | Every action from a fresh page to the moment the bug shows, with the exact values typed. The last step is the action that triggers it. | The developer repeats them. If a step is missing, they cannot reproduce the bug. |
| Expected | What should have happened, and where that rule comes from: the requirements or the label next to the field. | It marks the difference between a bug and a wish. "Expected: 10 tickets is the maximum, says the rule above the field" is harder to argue with than "should not allow that". |
| Actual | What happened, in the words and numbers you saw. Copy the message; copy the total. | Lets the developer match it to the code. "Wrong total" matches nothing; "₪104.9" matches a formatting call. |
| Where you saw it | On screen, in the confirmation, in the browser console, in the network request, in a second tab, on the server's response. | Many bugs worth reporting are not on the screen. If you found one in the console, say so, or the developer will look at the screen, see nothing, and close the report. |
Title, environment and severity are real fields, but they are derived from the four above. The title is the actual result in one line. The environment matters when the bug depends on it, which for a web form is rare; write the browser once at the top of the whole report. Severity is your estimate, and the guide on ordering findings covers how to set it.
Five reports, rewritten
Each pair below is a weak first version and the same report after a second pass. The form in the examples sells tickets at ₪119.90, charges a ₪5 service fee per order, gives students 20% off and the promo code SAVE10 10% off, with only the larger discount applied.
1. "The total is wrong"
Before: "The total is calculated wrong when I use a discount."
After: "Steps: 4 tickets, tick Student, promo SAVE10, Calculate total. Expected: ₪388.68 (479.60 minus 20% is 383.68, plus the ₪5 fee; the rule says only the larger discount applies). Actual: ₪350.31, which is both discounts applied one after the other (479.60 × 0.8 × 0.9 + 5). Seen on screen and in the order confirmation."
The second version names the input, shows the arithmetic for both numbers, and says which rule is broken. A developer can open the discount code and look for the place where the two discounts multiply. The first version sends them to try discounts at random.
2. "Doesn't accept my email"
Before: "The form doesn't accept my email address."
After: "Steps: 1 ticket, today's date, email [email protected], Buy. Expected: order placed, since the address is valid. Actual: the button goes grey for a second and nothing happens; no message, no confirmation. The request in the network tab returns 400 with {"error":"invalid email"}. The same order with [email protected] goes through, so the failure is capital letters."
The value is written out, so the reader does not have to ask which address. The silent failure is described as silent: no message is a separate problem from a wrong message. The last sentence reports the one experiment that narrows the cause without claiming to know the code: "the failure is capital letters" is something you saw, "the regex is case-sensitive" would be a guess.
3. "Date validation is broken"
Before: "Date validation is broken, I could enter old dates."
After: "Steps: event date 2026-03-14 (eight months ago), 1 ticket, valid email, Calculate total, then Buy. Expected: the field refuses a past date, as the rule 'today or later' says. Actual: the total is shown and the order is placed with that date in the confirmation. A date last year is refused with 'Please choose a date from today onwards', so the check looks at the year, not the full date."
"Old dates" could mean yesterday, last year or 1970, and these are different bugs with different fixes. The rewrite gives one date that fails and one that passes, which is what a developer will ask for first anyway.
4. "Clicked twice, got two orders"
Before: "If you click Buy twice you get two orders. Should be fixed, this is a common bug."
After: "Steps: 2 tickets, valid email and date, click Buy twice quickly (within about half a second). Expected: one order. Actual: two confirmations, orders #2041 and #2042, same email and date, total ₪244.80 charged twice. Clicking twice with a pause of two seconds gives one order and a 'you already ordered' message, so the guard exists but only takes effect after the first response arrives."
The first version was almost right; it lost the order numbers and the timing.
5. "Security issue in login"
Before: "Security issue: the login is not secure. High priority."
After: "Steps: open the sign-in form, enter [email protected] and the password, click Log in, then look at the page address. Actual: the address bar shows /api/login?email=anna%40example.com&password=Secret123, so the password is in the URL and will appear in the browser history and the server's access log. Expected: credentials sent in the request body (POST), nothing in the address."
A developer cannot reproduce "not secure". They can reproduce "the password is in the address bar". Here Actual comes before Expected; the report still has all four parts.
One bug per item
An item that says "the total is wrong and the date field accepts old dates" is two items. They have different causes, will be fixed by different people on different days, and one of them will be closed with the other still open. Split them. The opposite mistake is three items with one cause: a missing second decimal on the total, on the confirmation and in the email is one bug, formatting, reported once with the three places listed.
Before sending a list, read it once for pairs that are the same bug in different clothes. Merging them can shrink a list of twelve to nine, and the developer will thank you for the three.
What not to put in
- A guess at the cause, unless it is marked as a guess. "The server trusts the total from the browser" is a finding if you changed the total in the request and the server charged it. It is a guess if you only saw a wrong total on screen.
- "Sometimes" without a count. "Fails sometimes" sends the developer to try it twice and close the report. "Failed 3 times out of 10, all three when I clicked within a second of the page loading" is a reproducible bug.
- The fix. "Add a check for capital letters" narrows what the developer looks at. Describe the symptom; the right fix may be somewhere else.
Whether to list everything you tried that worked is a judgement call. Keep the one passing case that marks the boundary, as in examples 2 and 3; the other forty passing inputs usually belong in your notes, though a developer chasing an intermittent bug may ask for all of them.
The short form
In a chat or a practice report, where no one fills in a tracker form, one line per bug is enough if the line keeps the four parts:
| One line per bug |
|---|
| 4 tickets + Student + SAVE10 → ₪350.31 shown, expected ₪388.68 (only the larger discount should apply); on screen and in the confirmation. |
| Email [email protected] → Buy does nothing, no message; network tab shows 400 "invalid email"; lowercase address works. |
| Event date 2026-03-14 accepted and ordered; a date last year is refused, so the check is by year only. |
| Two quick clicks on Buy → orders #2041 and #2042, ₪244.80 charged twice; two seconds apart → one order. |
| Log in sends email and password in the URL (/api/login?email=…&password=…); should be in the request body. |
Each line can be expanded into the long form on request, and each one can be checked by someone who was not there.
Before you submit
- Could a developer who has never seen the form repeat every step from your text alone?
- Are the values you typed in the report, not described ("a long name") but written out?
- Is every number you quote one you saw, and is the expected number one you calculated?
- For each item, did you say where you saw it: screen, console, network, server response?
- Is anything in the list a guess, an opinion or a duplicate?
A report written by an AI agent can fail the first question in a particular way: the agent filled the form from a script, and the steps it writes are what the script did, not what a hand does. The guide on reviewing an agent's report goes through that case.
Open the practice form
More guides: which bugs to report first · silent failures and stale screens · all guides