Reuse flows & seed data
For the complete documentation index, see llms.txtKeep scenarios short and reliable by extracting repetition into helpers.
Extract a flow
Move a repeated journey into a flow_* helper:
function flow_signin(email, password) { goto("/login"); fill(getByLabel("Email"), email); fill(getByLabel("Password"), password); click(getByRole("button", { name: "Sign in" }));}Call it from a scenario:
function test_orders() { step("Sign in", () => { flow_signin(TEST_USER_EMAIL, TEST_PASSWORD); }); step("Orders page loads", () => { assertVisible(getByRole("heading", { name: "Your orders" })); });}Seed data with mocks
Put the backend into a known state before the test, and clean up after:
function seed_order(userId) { dbExec("INSERT INTO orders (uid, status) VALUES ($1, 'paid')", userId);}dbQuery / dbExec use the database URL from config; apiCall uses
apiBaseUrl; shell runs a binary. These are pinned in
config — scenarios can’t point them elsewhere.
File oracles
A background process — a bot, a worker, a webhook receiver started with
shell() — often leaves its evidence in a file. For the file as a whole,
waitForFile(path, pattern?, {timeoutMs}) polls until it exists (and
matches a substring or regex) and returns its content — the waitForText of
background processes — and assertNoFile(path, pattern?, {withinMs}) is its
inverted twin. Four more helpers wait on and count lines of a JSONL or log
file, so the test asserts on what happened instead of on a pause:
step("the webhook was delivered", () => { line = waitForJsonLine("out/events.jsonl", { type: "delivered", "order.id": id }); assertTrue(line.attempts == 1, json(line)); assertNoJsonLine("out/events.jsonl", { type: "retry" }, { withinMs: 3000 });});waitForJsonLine(path, filter, {timeoutMs})— wait for a line of a JSONL file matching every key offilter(strict equality, dot paths for nesting) and return it parsed; what a substring cannot express, since JSON key order is not guaranteed. Default timeout 20 s.assertNoJsonLine(path, filter, {withinMs})— the negative twin: fail the moment a matching line appears, pass when the window elapsed clean;withinMsis required.waitForFileCount(path, pattern, count, {timeoutMs})— wait until at leastcountlines match (patternis a substring, a regex or a JSON key filter) and return how many there are.assertFileCount(path, pattern, count)— a snapshot count, strict equality: “exactly N happened”. Pair it withwaitForFileCount, which awaits the window.
Uploads and readable assert messages
apiCall sends a JSON body. For multipart/form-data pass upload() as
the body: apiCall("POST", "/documents", upload("fixtures/doc.pdf", {field: "file"}))
— the path is relative to the project root (or the config’s uploadDir),
and you never set the Content-Type yourself. A JSON body that merely has a
file key goes out as plain JSON; the linter says so
(lint:api-call-file-body).
json(value) serializes any value for an assert message, so a failing
assertTrue(res.status == 202, json(res.body)) shows the body instead of
false.