> ## Documentation Index
> Fetch the complete documentation index at: https://synthetiq.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Feature tests

> How hero tests work: steps, personas, seeding, and multi-user scenarios

This page describes how feature tests work underneath. For how to review a feature entry in the PRD view, see [Reviewing results](/docs/platform-docs/app-framework/prd/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.

```typescript theme={null}
// prd/tests/tasks.member-can-add.spec.ts
import { definePrdTest, expect } from "@synthetiq/app-framework/prd/harness";

definePrdTest("tasks.member-can-add", async ({ prd }) => {
  const page = await prd.actAs({ scopes: ["tasks:read", "tasks:write"] });

  await prd.step("A member opens their task list", async () => {
    await page.goto("/");
    await expect(page.getByTestId("task-empty")).toBeVisible();
  });
  await prd.step("They type a new task", async () => {
    await page.getByLabel("New task").pressSequentially("Buy milk", { delay: 90 });
  });
  await prd.step("They click Add Task", async () => {
    await page.getByRole("button", { name: "Add Task" }).click();
    await expect(page.getByText("Buy milk")).toBeVisible();
  });
  await prd.verify("The task 'Buy milk' now appears in their list", async () => {
    const task = page.getByText("Buy milk");
    await prd.highlight(task);
    await expect(task).toBeVisible();
  });
});
```

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:

```typescript theme={null}
await prd.seed({
  User: [{ id: "ada" }, { id: "ben" }],
  Task: [
    { title: "Ship the report", userId: "ada", status: "active" },
    { title: "Ben's secret task", userId: "ben", status: "active" },
  ],
});
```

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:

```typescript theme={null}
// A user with an exact permission set
const page = await prd.actAs({ scopes: ["tasks:read"] });

// The presets: all scopes, or none
const admin = await prd.actAs("Admin");
const member = await prd.actAs("Member");

// A specific seeded user, so seeded rows belong to the caller
const ada = await prd.actAs({ id: "ada", scopes: ["tasks:write"] });
```

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:

```typescript theme={null}
await prd.verify("Ada sees her own task", async () => {
  await expect(page.getByText("Ship the report")).toBeVisible();
});
await prd.verify("Ada does NOT see Ben's task", async () => {
  await expect(page.getByText("Ben's secret task")).toHaveCount(0);
});
```

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](/docs/platform-docs/app-framework/prd/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](/docs/platform-docs/app-framework/prd/trust-model). The capture details are in [What gets captured](/docs/platform-docs/app-framework/prd/capture-layers).
