A scenario is a plain .js file in unotest/e2e/. It exports test_*
functions, and every executable step lives inside a step() block.
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.
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.
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.