Writing Acceptance Criteria That Prevent Bugs Before They Ship
Vague acceptance criteria are future bugs in disguise. Here is how to write Given/When/Then scenarios, cover the negative and edge cases everyone skips, and turn every criterion into a pass/fail QA test before any code is written.

Most bugs do not start in code. They start in a sentence like "the form should handle errors gracefully." That line passes review, ships into a sprint, and three weeks later a customer pastes a 4,000-character note into a field nobody bounded, the request times out, and their draft vanishes. Nobody wrote that bug. They wrote an ambiguous acceptance criterion and let the runtime fill in the blanks.
Acceptance criteria (AC) are the conditions a feature must satisfy to be accepted as complete. Done well, they are the cheapest defect prevention you have, because they catch the ambiguity while it still costs a comment instead of a rollback. This guide covers the Given/When/Then pattern, the negative and edge cases teams skip most, how a fuzzy adjective becomes a production defect, and how to turn finished criteria into a QA checklist that exists before the feature is built.
What makes acceptance criteria actually prevent bugs?
Acceptance criteria prevent bugs when each one is specific, testable, and written before code. The Given/When/Then format forces a known starting state, a single action, and one observable outcome that maps directly to a pass/fail test. Pairing every positive scenario with a negative scenario closes the gaps where defects hide. Ambiguity, not complexity, is the top cause of requirement-driven failure.
The mechanism is boring and reliable: a testable criterion has exactly one answer to the question "did this pass?". The official Gherkin reference defines the three clauses precisely. Given steps put the system in a known state before interaction. When steps describe a single event or action. Then steps describe an expected outcome that you verify by asserting the actual observable result against the expected one (Cucumber Gherkin reference). That structure is not ceremony. It is what makes a criterion checkable by a human or a machine without debate.
The stakes are not theoretical. CISQ estimates the cost of poor software quality in the US at a minimum of $2.41 trillion, with accumulated technical debt accounting for roughly $1.52 trillion of it (CISQ, 2022). A 2025 comparative study in the Ain Shams Engineering Journal, reviewing 57 papers plus an industrial survey, found ambiguous requirements to be the top cause of prolonged analysis and project failure (ScienceDirect, 2025). Ambiguity is the disease; testable criteria are the cure.
The Given/When/Then pattern, with copy-paste examples
Use one action per scenario. If you find yourself writing a second When, you have two scenarios. Keep the Then to observable output, not internal state you cannot see from the outside. Here is a positive scenario plus the negative scenarios that the happy-path version usually leaves out.
Feature: Account login
# Positive / happy path
Scenario: Member signs in with valid credentials
Given a verified member account exists for "ana@acme.com"
And the member is on the sign-in page
When she submits the email and the correct password
Then she lands on the dashboard within 2 seconds
And a session cookie is set with a 24-hour expiry
# Negative: wrong password (the message must NOT leak which field is wrong)
Scenario: Sign-in fails with an incorrect password
Given a verified member account exists for "ana@acme.com"
When she submits the email with the wrong password
Then she stays on the sign-in page
And sees "Email or password is incorrect"
And the entered email remains pre-filled
# Edge: rate limit after repeated failures
Scenario: Sign-in is throttled after 5 failed attempts
Given 5 failed sign-in attempts in the last 10 minutes for "ana@acme.com"
When she submits any credentials
Then she sees "Too many attempts. Try again in 15 minutes."
And no authentication request is sent to the backendFeature: Apply discount code at checkout
Scenario: Valid code reduces the order total
Given a cart with a subtotal of $100.00
And an active code "SAVE20" for 20% off
When the shopper applies "SAVE20"
Then the displayed total becomes $80.00
And the code appears as a removable line item
Scenario: Expired code is rejected without changing the total
Given a cart with a subtotal of $100.00
And a code "SPRING" that expired yesterday
When the shopper applies "SPRING"
Then the total stays $100.00
And an inline error reads "This code has expired"
Scenario: A second code cannot stack on an applied code
Given "SAVE20" is already applied to the cart
When the shopper applies "SAVE10"
Then the total still reflects only "SAVE20"
And a message reads "Only one code can be used per order"Not every criterion needs a scenario. Atlassian and AltexSoft both document two standard formats: scenario-oriented (the Gherkin blocks above, for behavior with user interaction) and rule-oriented (a bulleted list of constraints) for simple or technical requirements (Atlassian; AltexSoft). A password policy is fine as a rule list:
- Password must be at least 12 characters.
- Must contain one number and one symbol.
- The last 5 passwords cannot be reused.
- Submitting an invalid password shows the specific failed rule, not a generic error.
Mix them freely. Rules capture the constraints; scenarios capture the behaviors that need step-by-step verification.
The negative and edge cases everyone skips
Happy-path criteria are easy to write and easy to pass, which is exactly why they hide bugs. The defects live in the cases nobody bothered to specify. Treat the list below as a checklist: for each positive scenario, ask which of these applies and write the matching negative scenario with its expected error state.
- Empty / missing input — required field left blank, null payload, no file selected.
- Boundary values — 0, 1, the maximum, and max + 1. Off-by-one lives here.
- Invalid format — malformed email, negative quantity, wrong file type.
- Duplicate / double-click — same form submitted twice, idempotency of payments.
- Permissions changed mid-action — session expired, role revoked between page load and submit.
- Network failure / timeout — request never returns; what happens to the user's entered data?
- Concurrency — two users editing the same record; last-write-wins or a conflict prompt?
Turning acceptance criteria into a QA test checklist
This is the payoff. Because each scenario is already structured as setup / action / assertion, it converts to a test case with zero translation: the Given is your setup, the When is the step, the Then is the assertion. Build a checklist where every row is one scenario with an explicit expected result and a pass/fail box. Crucially, the checklist exists before the feature is built, so QA and developers agree on done up front instead of arguing about it at review.
- One row per Given/When/Then scenario, positive and negative alike.
- Each row states the exact expected result (the
Then), not a vague "works." - Cross-check rows against boundary, permission, and failure conditions before marking the story ready.
- Link each failing row to evidence: a reproduction, the console error, and the network call at the moment the assertion broke.
For the full pre-ship pass, hand this checklist to your manual QA testing checklist, and when a row fails, capture it using the steps-to-reproduce guide so the expected-vs-actual gap is unambiguous.
| Feature | BugMojo | Cucumber / BDD runner | Jira + Jama / TestRail |
|---|---|---|---|
| Stores Given/When/Then scenarios | As attached AC on a bug | Yes, native .feature files | Yes, native fields |
| Executes scenarios as automated tests | No — not a test runner | Yes, core capability | Via TestRail integrations |
| Captures rrweb replay + console + network on a failed Then | Yes, one-click | No | No |
| Exposes AC-linked failure evidence to an AI agent over MCP | Yes — unique | No | No |
| Formal requirements / test-management administration | Minimal | N/A | Yes — their core |
Here is the part no BDD tool, error monitor, or tracker does. When a Then clause fails during a QA pass, the failure is usually just a sentence in a ticket and a screenshot. BugMojo's browser extension captures the rrweb session replay, console logs, and network requests at the moment the assertion broke, and its MCP server exposes that bundle to an AI coding agent such as Claude Code or Cursor. The agent reads the failure with full context, maps the broken assertion back to the exact Given/When/Then scenario, and proposes a fix, instead of a human re-typing the criterion into a ticket.
Be clear about the trade: BugMojo is not a test runner and not a requirements-management suite. Cucumber executes scenarios; Jama and TestRail own formal requirements and test administration. BugMojo's wedge is the loop from failed criterion to AI-readable evidence, and that loop is the one no competitor offers.
BugMojo captures the rrweb replay, console logs, and network requests the moment a Then clause breaks, then hands the bundle to your AI agent over MCP. Map the failure back to the exact scenario and ship the fix.
Try BugMojo freeFrequently asked questions
Frequently asked questions
Sources
- Gherkin Reference — Given/When/Then keywords and scenario structure — Cucumber (SmartBear) (2024)
- The Cost of Poor Software Quality in the US: A 2022 Report — Consortium for Information & Software Quality (CISQ) (2022-11)
- A Guide to Poor Requirements: Causes, Repercussions, and How to Fix Them — Jama Software (2024)
- Acceptance Criteria for User Stories in Agile: Purposes, Formats, Examples, and Best Practices — AltexSoft (2024)
- What is Acceptance Criteria? Definition, Examples, & Tips — Atlassian (2024)
- Ensuring quality in software requirement engineering process: A comparative study — Ain Shams Engineering Journal (ScienceDirect) (2025-08)
Get bug-tracking insights, weekly.
Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.

