Skip to main content
This page describes how feature tests work underneath. For how to review a feature entry in the PRD view, see Reviewing results. A feature entry is backed by one hero test: a Playwright spec in prd/tests/ that drives the real app in a real browser and records everything. The test is written as a story: it seeds a starting state, acts as a specific user, performs the flow one step at a time, and asserts the specific outcomes the requirement claims.
Two primitives structure both the test and the recording:
  • prd.step(label, fn) — one user action, ending on its visible result. Each step becomes a beat in the recording: the timeline highlights it, and playback holds on the result before moving on.
  • prd.verify(label, fn) — one assertion, labeled with the acceptance criterion in plain language. It shows as ✓/✗ in the timeline, and prd.highlight spotlights the element being confirmed.
Steps and verifies are siblings, never nested: a sequence of actions, followed by the assertions on the result.

Seeding the starting state

Most behavior only shows up against specific data, and clicking through the app to build that data would bury the flow under test, so the test seeds it directly:
Rows only need the fields the test cares about. Required columns are auto-filled with deterministic values from the schema, while relationships and foreign keys are always yours to set, so the seeded world stays meaningful. Shared shapes live in prd/fixtures/ so e2e and API specs seed from the same definitions. Every run resets the database first; seeds never leak between tests. Seeding is also what makes assertions honest. A test of “users see only their own tasks” against a database with nothing to exclude passes vacuously. The rule is to seed both sides of every boundary: the data the user should see and adjacent data they shouldn’t, with a recognizable marker, then assert both directions.

Acting as different users

prd.actAs creates a signed-in user for the test. Many requirements are about who sees what, so most tests use it:
Acting as a seeded user connects the persona to the data: Ada owns “Ship the report”, Ben owns his task, and the test can assert the complete visibility story in one recording:
A test calls actAs more than once to compare permission levels in the same story: the member who can’t see the admin panel, then the admin who can. Each step shows the current user and their permissions, so the reviewer always knows which user is on screen. This composes with everything else on this page. A single hero can seed two users with different roles and different data, run a workflow that processes both, and prove each user sees exactly their own result. See Services and workflows for the workflow end of that story.

Assertions must be able to fail

The harness enforces structure, but an entry is only as good as its assertions. The test for every entry: if this feature were broken, would this test fail?
  • A test seeds the preconditions that make the behavior happen, then asserts the specific items, values, and states the user should see. “1 of 6 scores submitted”, not “the page loads”.
  • Assertions must be unconditional. A check wrapped in “if the element exists” passes when the element is missing.
  • “No error page” proves nothing. A page rendering the wrong data renders without errors.
  • After seeding, the test asserts the seeded values actually surfaced. This catches a query that silently filters everything out.
  • The verify label is the requirement, and the assertion inside must check exactly that.

When a test fails

A failing feature still produces the complete evidence package: both theme videos, the timeline with the failing step in red and unreached steps in gray, the data snapshots, and every service call up to the failure. In practice failures reach the agent through the build, not the reviewer; see The trust model. The capture details are in What gets captured.