DEV Community

Gabe Garcia
Gabe Garcia

Posted on

Acceptance criteria examples developers can test

Acceptance criteria are the conditions a change must meet before anyone calls it done. When they're good, QA, product and engineering all agree on what "done" means before the first commit. When they're bad, that argument happens in code review.

This post is a set of acceptance criteria examples in the two formats most teams use, a few ambiguous tickets rewritten into testable ones and a sketch of how criteria turn straight into automated tests.

Two formats, two jobs

Scenario style uses Given, When and Then. It fits behavior with a clear trigger and outcome, like a checkout flow or a permission rule.

Rule style is a plain checklist. It fits constraints that don't have a natural sequence, like field limits, layout rules or performance budgets.

Scenario style examples

Here's a password reset feature written in Gherkin.

Feature: Password reset

  Scenario: Reset link sent for a known email
    Given an account exists for "ana@example.com"
    When I request a password reset for "ana@example.com"
    Then a reset email is sent to "ana@example.com"
    And the link in the email expires after 30 minutes

  Scenario: Same response for an unknown email
    Given no account exists for "ghost@example.com"
    When I request a password reset for "ghost@example.com"
    Then I see the message "If that address has an account, we've sent a link"
    And no email is sent

  Scenario: Expired link
    Given I have a reset link that is 31 minutes old
    When I open the link
    Then I see "This link has expired"
    And I am offered a button to request a new one
Enter fullscreen mode Exit fullscreen mode

A few things make these testable. Every Then is observable. Numbers are explicit. The second scenario pins down a security rule, so nobody has to guess whether to reveal which emails exist.

Rule style examples

A profile form, written as a checklist.

- [ ] Display name is required, 2 to 40 characters
- [ ] Leading and trailing spaces are trimmed before saving
- [ ] Bio is optional, max 280 characters, with a live counter
- [ ] Avatar accepts PNG or JPEG up to 5 MB
- [ ] Saving shows a "Profile updated" toast and keeps the page scroll position
- [ ] All fields are reachable and operable by keyboard alone
Enter fullscreen mode Exit fullscreen mode

Each line can be checked as true or false.

Ambiguous tickets, rewritten

These are the kinds of lines that cause rework. Each rewrite says the same thing in a way a test can check.

Before: Search should be fast.
After:  Search results render within 300 ms at p95 for a catalog of 50k items.

Before: Handle errors gracefully.
After:  If the payment API returns 5xx, show "Payment didn't go through, you
        weren't charged" and keep the cart intact.

Before: The table should work on mobile.
After:  At widths under 640px, the table collapses into stacked cards with
        the order number as each card's heading.

Before: Admins can manage members.
After:  Admins can invite, remove and change the role of members.
        Removing the last admin is blocked with an inline message.

Before: Support bulk upload.
After:  CSV upload accepts up to 10,000 rows. Rows with errors are skipped
        and listed in a downloadable report with row number and reason.
Enter fullscreen mode Exit fullscreen mode

The pattern is always the same. Replace adjectives with numbers, replace verbs like "manage" with a list of actions and name what the person sees when things fail.

Turning criteria into tests

Good criteria map almost line for line onto tests. Here's the expired link scenario as a Playwright sketch.

import { test, expect } from "@playwright/test";
import { createResetToken } from "./helpers/auth";

test("expired reset link offers a new one", async ({ page }) => {
  const token = await createResetToken("ana@example.com", { ageMinutes: 31 });

  await page.goto(`/reset?token=${token}`);

  await expect(page.getByText("This link has expired")).toBeVisible();
  await expect(
    page.getByRole("button", { name: "Send a new link" })
  ).toBeVisible();
});
Enter fullscreen mode Exit fullscreen mode

And one profile rule as a unit test.

import { describe, it, expect } from "vitest";
import { validateProfile } from "../src/profile";

describe("display name", () => {
  it("trims spaces before checking length", () => {
    expect(validateProfile({ displayName: "  Al  " }).ok).toBe(true);
    expect(validateProfile({ displayName: " A " }).ok).toBe(false);
  });

  it("rejects names over 40 characters", () => {
    const long = "x".repeat(41);
    expect(validateProfile({ displayName: long }).errors).toContain("displayName.max");
  });
});
Enter fullscreen mode Exit fullscreen mode

If you can't sketch a test for a criterion in a minute or two, the criterion probably isn't specific enough yet.

Don't forget the non-functional lines

Teams often write criteria for the happy path and skip everything else. Add a line for each of these when it matters.

  • Accessibility. Focus order, labels and color contrast.
  • Performance. A budget with a percentile, like p95 under 300 ms.
  • Security. Who can see or do this, and what happens if they can't.
  • Analytics. The event names and properties that should fire.
  • Empty and error states. What shows when there's no data or the call fails.

Common mistakes

  • Writing the implementation. "Use a Redis cache" is a design choice. State the outcome and let engineers choose.
  • Untestable words. Fast, intuitive, user-friendly and robust can't fail a test.
  • Criteria written after the code. They turn into a description of what was built, not what was needed.
  • Too many scenarios. If a ticket has fifteen, it's probably two or three tickets.

Before the ticket moves to ready

Read each criterion and ask whether a new teammate could write a failing test for it today. If not, rewrite it.

Long criteria often hide a simple rule. A short guide on the habit you need here, simplify text without changing meaning, shows how to pull it out and keep every condition.

When a whole block of criteria reads dense, Rephrase AI can reword it in plainer sentences, and then a side by side read confirms every number and edge case survived.

Clear criteria won't write the code for you. They do make the review a lot shorter.

Top comments (0)