This documentation is available as Markdown. For the complete index, see llms.txt. Skip to content

DSL reference

For the complete documentation index, see 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", () => { ... }).

This page lists the vocabulary one function at a time. For what a few of them build together — one test over a table of cases, each row tagged and allowed to fail on its own (readJsonLine + step.soft + {tag}) — see Scenarios, which walks a whole data-driven gate.

Documented for @unotest/web@0.33.0 — signatures and descriptions come from the package itself, the same source your editor shows on hover.

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: DOM presence (‘visible’ default, ‘hidden’, ‘attached’, ‘detached’) or interaction (‘enabled’ / ‘disabled’ — same probe as isEnabled, so aria-disabled counts).
  • waitForText(text, { first?, timeout? }) — Wait until text appears anywhere on the page. first: true waits for the first VISIBLE occurrence when the text is duplicated (otherwise duplicates fail strict mode).
  • waitForCount(locator, count, { exact?, timeout? }) — Poll until the locator matches AT LEAST count elements ({exact: true} for exactly). Default timeout 5000 ms.
  • 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. The chain form and the free-call form are the same call — getByRole(...).filter({...}).first()first(filter(getByRole(...), {...})); the recorder saves the chain form.

  • 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).
  • randomNth(locator) — A random one of the matches — for flows where any item will do.
  • contentFrame(locator) — Resolve an <iframe> element to its content frame.
  • loc.getByRole(...) / loc.locator(css) / … — Every builder also works as a chain step — the receiver scopes the query to its subtree (Playwright semantics).

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?, position?, timeout? }) — Hover over an element. position is an {x, y} offset inside it — for a menu that only appears at one edge of a tall 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.
  • assertNeverAppears(locator, { withinMs }) — Negative window assert: FAIL the moment the locator becomes visible; pass when the whole window elapsed clean. withinMs required; the step always costs that long.
  • assertValue(locator, expected, { timeout? }) — Input’s value equals expected. On a checkbox/radio/switch: its checked state — pass ‘true’ or ‘false’.
  • assertCount(locator, expected, { timeout? }) — Number of matches equals expected.
  • assertUrl(pattern, { timeout? }) — Current URL contains pattern.
  • assertJudge(locator, rubric) — LLM-judge assert for free-form text (chat replies, generated content): the element’s rendered text + a natural-language rubric go to the configured judge (UNOTEST_JUDGE_MODE); a fail verdict fails the step with the judge’s reasoning. Use where substring/regex asserts are too brittle.
  • 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.
  • isDisabled(locator) → boolean — Whether the element is disabled (native :disabled incl. fieldset, or aria-disabled).
  • isEnabled(locator) → boolean — Negation of isDisabled.
  • getAttribute(locator, name) → string — Attribute value ("" if missing). Boolean attributes (disabled, readonly) read "" whether present or absent — use isDisabled / hasAttribute for those.
  • hasAttribute(locator, name) → boolean — Whether the attribute is present at all — the honest probe for boolean attributes.
  • 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. Multipart: pass upload(…) as the body.
  • upload(path, options?) → UploadRef — Multipart marker for apiCall’s body (options: field, fields). Path is relative to the project root / config uploadDir.
  • shell(cmd, ...args, options?) → { stdout, stderr, code } — Run a binary (execFile, no shell interpretation). cwd from config shellCwd. Non-zero exit FAILS the step unless {allowNonZero: true}; wall-clock budget {timeoutMs} (default 120s).
  • waitForFile(path, pattern?, options?) → string — Poll a file (relative to config shellCwd) until it exists / contains a substring or regex, then return its content. options: {timeoutMs} (default 20s). The waitForText of background processes.
  • assertNoFile(path, pattern?, { withinMs }) — Mirror of waitForFile with inverted polarity: FAIL the moment the file exists (and matches pattern); pass when the window elapsed clean. withinMs required.
  • waitForJsonLine(path, filter, options?) → object — Wait for a LINE of a JSONL file that matches every key of filter (strict equality, dot paths for nesting), then return it parsed. What a substring cannot express: JSON key order is not guaranteed. options: {timeoutMs} (default 20s).
  • assertNoJsonLine(path, filter, { withinMs }) — Negative twin of waitForJsonLine: FAIL the moment a matching line appears; pass when the window elapsed clean. withinMs required.
  • readJsonLine(path, filter) → object — First matching line of a JSONL file that already exists, no wait: one read, same key filter as waitForJsonLine. FAILS at once when the file is missing or no line matches — use it for fixtures and finished exports, waitForJsonLine for a file still being written.
  • waitForFileCount(path, pattern, count, options?) → number — Wait until AT LEAST count lines match, then return how many there are. pattern is a substring, a regex or a JSON key filter. options: {timeoutMs} (default 20s).
  • assertFileCount(path, pattern, count) — Snapshot count of matching lines, strict equality — ‘exactly N happened’. Pair it with waitForFileCount, which awaits the window.

Generators & text helpers

Random fixture data, time values and text probes — no imports, no Date/Math in scenarios.

  • randomFirstName() → string — Random first name for fixture data.
  • randomLastName() → string — Random last name for fixture data.
  • randomEmail() → string — Random unique email address for fixture data.
  • randomText(length) → string — Lorem-style prose cut to length characters.
  • randomWord(length?) → string — Random lowercase letters (default 8) — digit-free unique markers.
  • json(value) → string — Serialize any value to a JSON string — readable assert messages for API/data steps: assertTrue(res.status == 202, json(res.body)).
  • textContains(haystack, needle) → boolean — Substring probe for strings (shell stdout, api bodies): assertTrue(textContains(out.stdout, ‘ready’), out.stderr).
  • textJoin(parts) → string — Concatenate an array of parts (numbers coerced). + with a string literal also works; only literal-free a + b string addition is rejected (looks like arithmetic).
  • nowMs() → number — Current epoch milliseconds.
  • today() → string — Local date as YYYY-MM-DD.
  • daysFromNow(n) → string — Local date n days ahead (negative = past) as YYYY-MM-DD.

Structure & escape hatch

The required step wrapper, diagnostics, 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. Forms since 0.31.0: step(label, {tag: expr}, () => { ... }) names the case a data-driven step is on; step.soft(label, [{tag}], () => { ... }) records a failure inside and lets the run continue (the test still ends failed) — inside test_* only.
  • log(...args) — Write a line to the runner’s output AND to the run journal, under the current step — visible in the viewer’s System pane after the run too.
  • note(label, value) — Attach a labelled value to the current step: shown in the viewer under the step (also after the run) and kept in the run journal. Any value — a string, a number, an object (recorded as JSON). Secrets are masked; long values are cut at 4 KB. Use it for the question a data-driven case asked and the answer it got.
  • screenshot(name?, { fullPage? }) → string — Capture the active page into the run’s artifact dir; returns the project-relative path. For a PNG after EVERY step without calling it, set UNOTEST_STEP_SCREENSHOTS=1 in the run’s environment.
  • evaluate(js, arg?) → any — Run raw JS in the page context. Last resort — prefer typed helpers. Returns JSON-serializable values only. Extra arguments reach the body as: none → nothing, exactly one → the value itself, two or more → ONE array (destructure ([a, b]) => …).

Expressions & operators

  • Comparison: == / != (loose — numeric strings coerce, "1" == 1 is true), === / !== (strict — type AND value, 1 === "1" is false), < <= > >=.
  • Logical: && / ||.
  • DOM reads (textContent, getInputValue, getAttribute) return strings — compare against numbers with ==, not ===.

Not supported

  • 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.