# Scenarios & step()

> For the complete documentation index, see [llms.txt](/llms.txt)

A scenario is a plain `.js` file in `unotest/e2e/`. It exports `test_*`
functions, and **every executable step lives inside a `step()` block**.

```js
function test_checkout() {
  step("Add the first product to the cart", () => {
    goto("/products");
    click(getByRole("button", { name: "Add to cart" }));
  });

  step("Cart shows one item", () => {
    assertText(getByTestId("cart-count"), "1");
  });
}
```

## Two layers

Each `step()` carries two layers at once:

- **Intent** — the string label, plain English. Reads like a checklist, even to
  a non-engineer.
- **Execution** — the DSL calls inside the closure. One step can be several
  calls.

This is why repair is precise: the agent knows **what** a step is meant to do
(its label) and **how** it does it (the calls), so it rewrites only the broken
part — it doesn't guess. The same duality helps you: collapse a step to see the
logic, expand it to see the exact commands.

:::note[step() is required]
The validator requires every direct child of a `test_*` body to be a `step()`
call. Helpers (`flow_*`, `snake_case`) are exempt. The older `//@collapse`
comment form has been removed.
:::

## The DSL in one breath

Navigation (`goto`, `waitForUrl`), locators by stability (`getByTestId` →
`getByRole` → `getByLabel` → `getByText` → `locator`), actions (`click`, `fill`,
`press`, `selectOption`…), assertions (`assertText`, `assertVisible`…), chaining
(`getByRole(...).filter(...).first()`), multi-tab and iframes, and sandbox
helpers (`dbQuery`, `apiCall`, `shell`). See the [DSL reference](/reference/dsl/).

## What's not in the DSL

Comparison/logical operators in conditions aren't supported — use bare truthy
variables. Loops and branching are plain JS *around* steps. Regex literals are
allowed in matcher args (ES5 flags only). See [DSL → Not supported](/reference/dsl/).
