Skip to Content
ConceptsRequirements

Requirements

Requirements answer the question: what does the system need to do?

Before you build, requirements capture what you’re trying to achieve. After you build, they serve as a reference for what the system is supposed to do — useful for onboarding, debugging, and deciding whether a change is a bug fix or a new feature.

What to Capture

Not every detail needs to be a requirement. A good rule of thumb: if it’s important enough to validate, it’s worth capturing as a requirement.

Requirements work well for:

  • User-facing behavior (“when a user does X, Y happens”)
  • Business rules (“orders over $100 get free shipping”)
  • Edge cases that matter (“expired links show an error, not a blank page”)

Requirements are less useful for:

  • Implementation details (“use a hash map for lookups”)
  • Obvious behavior (“the page loads”)
  • Things that will change frequently during development

Structure

A requirement block has two parts: a header and children.

LOGIN-1: A registered user, Jamie, can log in to their account 0. → When Jamie provides valid credentials, they are authenticated 1. → When Jamie provides invalid credentials, they see an error message

The header (LOGIN-1: A registered user...) corresponds to the “story” or “use case” level. It contains:

  • A unique key (LOGIN-1) — used to reference this requirement in tests
  • A summary — describes the overall behavior

Children (the indented lines) correspond to the “acceptance criteria” or “example” level. They break down the requirement into specific criteria:

  • A position (0., 1.) — defines the hierarchy. Because these are primarily used as a programmatic reference, they are visible in markdown, but not in the web composer.
  • An optional label (like Important, Given, When, Then)
  • Content — the specific criterion or example

Children can be nested:

CHECKOUT-1: A customer, Alex, can complete a purchase 0. Given → Alex has items in their cart 1. When → Alex submits payment with a valid card 2. Then → The order is confirmed 2.0. And → Alex sees a confirmation page with an order number 2.1. And → Alex receives a confirmation email

The Markdown Format

Requirements are written in fenced code blocks with the dotrequirements language identifier:

```dotrequirements KEY: Summary text 0. Label → Content 1. → Content without label 1.0. → Nested content ```

This format is:

  • Human-readable — easy to scan and understand
  • Machine-parseable — tools can extract and validate requirements
  • Editor-friendly — works in any text editor, renders on GitHub

Labels

Labels are optional. They appear before the arrow delimiter ( or ->):

REFUND-1: A customer can request a refund 0. Given → The customer purchased an item within 30 days 1. When → The customer submits a refund request 2. Then → The refund is processed within 5 business days

Common labels include Given, When, Then, And, and AC (acceptance criteria), but you can use any label that makes sense for your team. Labels are not required — unlabeled children work fine:

SEARCH-1: A user can search for products 0. → Entering a search term shows matching results 1. → Searching for a nonexistent term shows "no results" message 2. → Search results can be filtered by category

Keys and Paths

Every requirement has a key that uniquely identifies it within your project.

Key format: DOMAIN-N where DOMAIN is a short prefix and N is sequential.

  • LOGIN-1, LOGIN-2, LOGIN-3
  • CHECKOUT-1, CHECKOUT-2
  • AUTHN-1, AUTHZ-1

Paths reference specific parts of a requirement:

PathWhat it references
LOGIN-1The requirement header
LOGIN-1.0First child (by position)
LOGIN-1.1Second child
LOGIN-1.2.0First child of third child (nested)
LOGIN-1.importantFirst child labeled “Important”
LOGIN-1.then.andFirst “And” under first “Then”

Label paths are case-insensitive and zero-indexed. If you have multiple children with the same label, use #N to disambiguate: LOGIN-1.important#1 for the second “Important”.

Referencing in Tests

Use the requirement() function to reference requirements in your test descriptions:

import { requirement } from '@popoverai/dotrequirements/test' describe(requirement('LOGIN-1'), () => { // Set up valid and invalid test credentials it(requirement('LOGIN-1.0'), async () => { // Test: valid credentials → authenticated }) it(requirement('LOGIN-1.1'), async () => { // Test: invalid credentials → error message }) })

The requirement() function returns the requirement text, so your test output shows meaningful descriptions instead of cryptic IDs (both in the CLI test runner and in the IDE test explorer). It also stays up to date if a requirement’s language is updated.

Next Steps

  • See requirements in contextDocuments explains how requirements fit into documents
  • Track what’s testedCoverage Tracking shows how to see which requirements have tests
Last updated on