# DSL reference

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

Scenarios are plain `.js` on a sandboxed engine. The vocabulary mirrors Playwright, so it reads the way you expect. Every executable step lives inside `step("intent", () => { ... })`.

:::tip[Selector priority]
getByTestId → getByRole(name) → getByLabel → getByText → locator(css)
:::

## Navigation

Drive the page: load URLs, go back/forward, and wait for state.

- `goto(url, { waitUntil?, timeout? })` — Navigate to a URL. `waitUntil`: 'load' | 'domcontentloaded' | 'networkidle'. Relative paths resolve against `baseUrl`.
- `reload({ waitUntil?, timeout? })` — Reload the current page.
- `goBack({ timeout? })` — Navigate back in history.
- `goForward({ timeout? })` — Navigate forward in history.
- `waitForUrl(pattern, { timeout? })` — Wait until the URL contains `pattern` (substring match).
- `waitForNavigation({ timeout? })` — Wait for the next navigation event.
- `waitFor(locator, { state?, timeout? })` — Wait for an element state: 'attached' | 'visible' | 'hidden'.
- `waitForText(text, { timeout? })` — Wait until `text` appears anywhere on the page.
- `pause(ms)` — Explicit delay. Discouraged — the linter wants a `// reason:` comment. Prefer a `waitFor*`.

## Locators

Build a locator. Prefer the most stable matcher available — see Stable selectors.

- `getByTestId(id)` — Match by `data-testid`. Most stable — prefer this.
- `getByRole(role, { name?, exact? })` — Match by ARIA role + accessible name. `name` accepts a string or `/regex/`.
- `getByLabel(text, { exact? })` — Match a form control by its associated label.
- `getByText(text, { exact? })` — Match by visible text content.
- `getByPlaceholder(text, { exact? })` — Match an input by its placeholder.
- `getByAltText(text, { exact? })` — Match an image by its `alt` text.
- `getByTitle(text, { exact? })` — Match by `title` attribute.
- `locator(css)` — Match by raw CSS selector. Last resort — the linter warns on deep/brittle CSS.

## Chain refiners

Narrow a locator. Chains desugar to free calls: `getByRole(...).filter(...).first()`.

- `filter(locator, { hasText?, hasNotText?, has?, hasNot? })` — Keep matches by contained text or a nested child locator.
- `first(locator)` — First match.
- `last(locator)` — Last match.
- `nth(locator, index)` — The N-th match (0-indexed). Index-only refinement is fragile (linter info).
- `contentFrame(locator)` — Resolve an `<iframe>` element to its content frame.

## Actions

Interact with elements. Every action takes an options object; e.g. `{ force: true }`.

- `click(locator, { force?, timeout?, noWaitAfter? })` — Click an element.
- `doubleClick(locator, { force?, timeout? })` — Double-click an element.
- `fill(locator, value, { timeout?, noWaitAfter? })` — Clear and type a value into an input.
- `press(locator, key, { delay?, timeout? })` — Press a key (e.g. 'Enter', 'Control+A').
- `check(locator, { force?, timeout? })` — Check a checkbox/radio.
- `uncheck(locator, { force?, timeout? })` — Uncheck a checkbox.
- `hover(locator, { force?, timeout? })` — Hover over an element.
- `selectOption(locator, value, { timeout? })` — Select option(s) in a `<select>`. `value` is a string or string[].
- `scrollIntoView(locator)` — Scroll an element into the viewport.
- `dragAndDrop(from, to, { force?, timeout? })` — Drag one element onto another. Uses synthetic mouse events (not native HTML5 DragEvent).
- `uploadFile(locator, files)` — Set files on a file input. `files` is a path or path[].
- `clipboardPaste(locator, text)` — Paste text. Synthetic, not a native ClipboardEvent.

## Assertions

Polling assertions (default timeout 5000ms). Assertion failures never retry.

- `assertText(locator, expected, { exact?, timeout? })` — Element's text equals/contains `expected`.
- `assertVisible(locator, { timeout? })` — Element is visible.
- `assertHidden(locator, { timeout? })` — Element is hidden or detached.
- `assertValue(locator, expected, { timeout? })` — Input's value equals `expected`.
- `assertCount(locator, expected, { timeout? })` — Number of matches equals `expected`.
- `assertUrl(pattern, { timeout? })` — Current URL contains `pattern`.
- `assertTrue(condition, message?)` — Assert a boolean condition.

## Queries

Read values (non-polling). Use to branch logic in plain JS.

- `count(locator) → number` — Number of matching elements.
- `textContent(locator) → string` — Text content ("" if none).
- `inputValue(locator) → string` — Current input value.
- `isVisible(locator) → boolean` — Whether the element is visible.
- `getAttribute(locator, name) → string` — Attribute value ("" if missing).
- `getInnerText(locator) → string` — Rendered inner text.
- `getInputValue(locator) → string` — Input value (alias of inputValue).
- `getTitle() → string` — Document title of the active page.
- `getUrl() → string` — URL of the active page.

## Storage & cookies

Read/write localStorage and cookies for setup and assertions.

- `setLocalStorage(key, value)` — Set a localStorage item.
- `getLocalStorage(key) → string` — Read a localStorage item ("" if missing).
- `setCookie(name, value, options?)` — Set a cookie (options: path, domain, expires, httpOnly, secure, sameSite).
- `getCookie(name) → string` — Read a cookie value ("" if missing).

## Multi-tab & iframes

Work across tabs and nested frames.

- `setPage(index)` — Switch the active tab/page by index (0-based).
- `enterFrame(locator)` — Scope subsequent calls to an iframe (persists until exitFrame).
- `exitFrame()` — Exit the innermost iframe scope.

## Setup & data (sandbox)

Seed and verify state. Connection details are pinned in config — scenarios cannot redirect them.

- `dbQuery(sql, ...params) → rows[]` — Parameterized SELECT. Dialect from the config `database` URL (postgres/mysql/sqlite).
- `dbExec(sql, ...params) → number` — Parameterized INSERT/UPDATE/DELETE. Returns affected row count.
- `apiCall(method, path, body?, headers?) → { status, body, headers }` — HTTP call. `path` is relative — base is the config `apiBaseUrl`.
- `shell(cmd, ...args) → { stdout, stderr, code }` — Run a binary (execFile, no shell interpretation). cwd from config `shellCwd`.

## Structure & escape hatch

The required step wrapper, logging, and the raw-JS escape hatch.

- `step(label, () => { ... })` — Required around every executable step in a `test_*` function. `label` is the human-readable intent the agent reads to repair the step.
- `log(...args)` — Write to the run trace.
- `evaluate(js, ...args) → any` — Run raw JS in the page context. Last resort — prefer typed helpers. Returns JSON-serializable values only.

## Not supported

- Comparison/logical operators in DSL conditions (`== != < <= > >= && ||`) — use bare truthy variables.
- Control-flow keywords are plain JS around steps, not DSL primitives.
- Regex literals are allowed in matcher args, ES5 flags only (`g i m`); `s u y d`, named groups and lookbehind are rejected at parse time.
- waitForPage() is not shipped yet — use pause(ms) with a `// reason:` comment for tab races.
