The Bug Feedback Loop (Capstone)
Finding a bug is the start, not the finish. A bug you can't reproduce is a rumor. A bug report that makes an engineer come ask you questions has failed at its one job. And a bug that gets fixed but comes back six months later means the loop was never closed.
The full loop is: reproduce → report → fix → regression test. You own three of those four steps. In this capstone we'll walk the whole loop using a genuine, reproducible bug you can turn on in ShopKart: the lastname-swap scenario.
Step 1: Reproduce it reliably
Log into ShopKart, add any item, and open the checkout form with the bug on — [/shopkart/checkout?bugs=lastname-swap](https://resources.criodo.com/shopkart/checkout?bugs=lastname-swap). Type a first name, then a last name. Watch what happens.
Here's a test that captures the actual behavior. It passes against the live app — meaning it documents a bug that is really there:
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { InventoryPage } from '../pages/InventoryPage';
import { CheckoutPage } from '../pages/CheckoutPage';
test('BUG-001 repro: checkout last-name field overwrites the first name', async ({ page }) => {
const login = new LoginPage(page);
const inventory = new InventoryPage(page);
const checkout = new CheckoutPage(page);
await login.goto();
await login.login();
await inventory.addToCart('mi-usb-c-cable');
// A bug scenario is turned on by the ?bugs= URL param, so we navigate
// straight to the checkout form with it set (the cart already has an item).
await page.goto('/shopkart/checkout?bugs=lastname-swap');
await checkout.firstName.fill('Ada');
await checkout.lastName.fill('Lovelace');
// The bug: typing the last name overwrote the first name,
// and the last-name field stayed empty.
await expect(checkout.firstName).toHaveValue('Lovelace');
await expect(checkout.lastName).toHaveValue('');
});
That's a real bug: with lastname-swap on, the checkout's last-name field writes into the first-name field, so the customer can never enter a valid last name and can't complete their order.
Before you report anything, nail down reproduction:
- Does it happen every time? Run it a few times. (This one does — 100% with the scenario on.)
- What's the minimal path? You don't need to browse ten products. Log in → one item → checkout → type two names. Strip the report down to that.
- What's the boundary? Remove the
?bugs=lastname-swapparam and the same steps work fine. That contrast (bug on fails, bug off works) is gold for whoever fixes it: it isolates the trigger precisely.
> *A bug you can only reproduce "sometimes" isn't ready to report — it's ready to investigate. Intermittent repro usually means you haven't found the real trigger yet (a specific state, a timing window, a specific input). Find the trigger first. "Happens 100% with lastname-swap on, never with it off" is a finding; "happens sometimes" is a to-do.
Step 2: Write the report
A good bug report answers every question an engineer would ask, before they ask it. The non-negotiable fields:
BUG-001 — Checkout: last-name field overwrites first name
Severity: High Priority: P1
Environment: resources.criodo.com/shopkart, Chrome 120, 2026-07-27
Scenario: bugs=lastname-swap (does not occur with the scenario off)
Steps to reproduce:
1. Log in as student / crio123
2. Add "Mi USB-C Cable" to the cart
3. Open /shopkart/checkout?bugs=lastname-swap
4. In "First Name" type: Ada
5. In "Last Name" type: Lovelace
Expected:
First Name = "Ada", Last Name = "Lovelace"; order can proceed.
Actual:
First Name = "Lovelace", Last Name = "" (empty).
The last-name input writes into the first-name field. The customer
cannot enter a valid last name and is blocked from completing checkout.
Evidence: trace.zip + screenshot attached (from the failing run).
Frequency: 100% reproducible with the scenario on; 0% with it off.
What makes this report actionable:
- Expected vs Actual are both explicit. Never make the reader infer what "correct" was. State both.
- Steps are minimal and numbered. Anyone can follow them in 30 seconds. No "browse around and then...".
- The contrast is called out (fails with the scenario on, works with it off) — it hands the engineer a head start on the cause.
- Evidence is attached — the trace and screenshot from Lesson 4's config, so they see it without reproducing it themselves.
- Frequency is quantified — "100% / 0%", not "often".
Step 3: Severity vs Priority — they are not the same
These two get conflated constantly, and getting them right is what makes a report credible.
- Severity = how bad the impact is, technically. A property of the bug.
- Priority = how soon we should fix it. A business decision about scheduling.
| Low priority | High priority | |
|---|---|---|
| High severity | Data-corruption bug on a page no one uses yet | Checkout is broken — fix now |
| Low severity | Typo in the footer | Typo in the CEO's name on the launch-day homepage |
Our bug blocks checkout — that's lost revenue — so it's High severity, P1 priority. Compare it to ShopKart's broken-images scenario: every product shows the same broken image. That's very visible, but the customer can still buy — so it's typically Medium severity and a lower priority than a checkout blocker. Ranking bugs by real user and business impact, not by how annoying they are to you, is a core QA judgment.
Step 4: Close the loop with a regression test
The bug gets fixed. You're not done. A bug that was worth reporting is worth a permanent test, or it will quietly come back the next time someone refactors that form.
Write the test that asserts the correct behavior — the one that's red while the bug exists and green once it's fixed. This passes today with the scenario off (where the behavior is already correct) and stands guard forever:
// Regression guard for BUG-001. Fails if the last-name field ever
// starts overwriting the first name again. Note: no ?bugs= param.
test('BUG-001 regression: checkout fields persist independently', async ({ page }) => {
const login = new LoginPage(page);
const inventory = new InventoryPage(page);
const checkout = new CheckoutPage(page);
await login.goto();
await login.login();
await inventory.addToCart('mi-usb-c-cable');
await inventory.goToCart();
await page.locator('[data-test="checkout"]').click();
await checkout.firstName.fill('Ada');
await checkout.lastName.fill('Lovelace');
await expect(checkout.firstName).toHaveValue('Ada');
await expect(checkout.lastName).toHaveValue('Lovelace');
});
Notice the two tests do different jobs, and you want both for a while:
- The repro test documents the broken behavior as it exists now — it's your proof and your "is it fixed yet?" check.
- The regression test asserts the correct behavior — it goes green the moment the bug is fixed, then guards against its return forever.
Once the fix ships and the repro test flips to failing (because the bug is gone), you delete the repro test and keep the regression test. The loop is closed.
The capstone challenge
Now do the whole loop yourself, on your own machine, against the ShopKart Test Range. Download the [Playwright starter kit](https://resources.criodo.com/shopkart/shopkart-playwright-starter.zip) — it comes with the page objects and a challenges.spec.ts where this exact task is stubbed out. Your target is the broken-images bug:
- Reproduce: open the store with the bug on — [
/shopkart/inventory?bugs=broken-images](https://resources.criodo.com/shopkart/inventory?bugs=broken-images) — and confirm every product shows the same broken image. Write a test that asserts the broken state. (Hint: collect thesrcof every[data-test="inventory-item-img"]and check they all point at the one404.svgasset instead of distinct images.) - Report: write the full report — severity, priority, minimal steps (including the
?bugs=broken-imagesscenario link), expected vs actual, evidence. - Regress: turn the bug off and write the regression test that asserts the correct behavior (each product has a distinct, non-404 image) — it passes on the clean app and guards the fix.
Then repeat the loop for the other four scenarios in the control panel (cart-total, lastname-swap, slow-inventory, addtocart-fail). If you can do that cleanly, you can do the core job of a QA automation engineer: not just finding that something's broken, but turning it into something a team can fix and keep fixed.
Key takeaways
- The loop is reproduce → report → fix → regression test — and you own three of the four steps.
- Reliable reproduction comes first. "Sometimes" means you haven't found the trigger; quantify frequency and find the minimal path and the boundary case.
- A great bug report answers every question up front: minimal numbered steps, explicit expected vs actual, the working/broken contrast, attached evidence, quantified frequency.
- Severity ≠ priority — impact vs scheduling. Rank by real user and business cost (a checkout blocker beats a cosmetic glitch).
- Every reported bug earns a regression test that asserts the correct behavior, so the fix is proven and the bug can't silently return.
Course wrap-up
You started this course able to drive a tool. You're leaving it able to do the job*: decide what's worth automating, turn requirements into cases, build a suite that survives change, kill flakiness, run it in CI as a real deploy gate, and close the bug loop. That judgment — not any single framework — is what makes a QA automation engineer. Finish with the quiz to lock it in.