Upgrade to scenarios on V8
For the complete documentation index, see llms.txtSince 0.52.0 a scenario runs on V8, the engine of Node and Chrome, as the JavaScript it is. Most suites move with one command and one run. This page is the whole list of what to check.
1. Update and fix
cd unotestnpm i -D @unotest/web@latestnpx @unotest/web lint --fixRun these from inside unotest/, the suite’s own package. Its
package.json pins the version the suite runs on, here and on a box.
From the project root, npm i writes the new version into the root
package.json (and creates one if there is none), the suite stays on
the old version, and lint warns that it runs a version the suite does
not pin. Every command on this page runs from unotest/.
lint --fix writes the one edit the upgrade needs everywhere: a let
for every variable assigned without a declaration. In JavaScript such a
name is a global, shared by every test of the run, so it is now a lint
error (validator:implicit-global). The let goes at the start of the
test_*, flow_* or helper, where every step sees the name:
function test_count() { let n; // written by lint --fix step("Count the rows", () => { n = textContent(getByTestId("rows")); }); step("Check the count", () => { assertEqual(n, "3"); });}Commit what it wrote, then run cd unotest && npx @unotest/web lint again: what it
still reports is what --fix cannot decide for you. The next sections
cover each case.
2. Run the suite
cd unotest && npx @unotest/web collection regressUse the collection that holds all your scenarios, or
cd unotest && npx @unotest/web e2e <scenario> for one file. A test that passed
before and fails now names its line; the cases below are the ones to
look for.
What to edit by hand
Map and Set keys are iterators. keys(), values() and entries()
of a Map or a Set, and matchAll, now give iterators, as in JavaScript.
for…of and a spread read them as before; an array method needs the
spread first:
const names = [...seen.keys()].map((k) => k.toUpperCase());assertEqual([...seen.keys()], ["a", "b"]);With typescript installed, lint warns on an array method called on an
iterator, and a DSL function
given one fails naming the line and the spread to write.
No async / await. DSL calls return their result; the runner
does the waiting. An async function returns at its first await, and
what follows runs after the test is over — its checks included. Remove
both words.
No count of statements. A test is cut only by its time budget —
setTestTimeout(ms) in the test, else testTimeoutMs in the config or
UNOTEST_MAX_DURATION_MS. A long loop that ends is never cut; an
endless one fails on the time budget, naming its line.
Continue on a failure finishes the test. In a debug run, Continue
on a failure pause ends the test as failed: no statement after the
failure runs, the teardown(...) blocks play. To see what follows a
failure, fix the scenario and run it again.
sortBy is gone. It came with the old engine; JavaScript’s arrays
have no such method, so rows.sortBy('order') fails with
TypeError: rows.sortBy is not a function. Use sort:
rows.sort((a, b) => a.order - b.order); // numbersrows.sort((a, b) => a.name.localeCompare(b.name)); // stringsFor {desc: true}, swap a and b.
Errors read as JavaScript’s. A name nothing declares fails as
ReferenceError: <name> is not defined (with typescript installed, lint names it first), and a
member read on null or undefined fails with JavaScript’s
TypeError.
Inherited keys fail the step. An object handed to a DSL function
with a key it only inherits — written under __proto__: or made by
Object.create(…) — fails the step instead of being sent without that
key. Write the keys on the object itself.
UNOTEST_ENGINE is gone. There is one engine. A value left set is
ignored; drop it from your environment and CI.
AstExecutor is no longer exported. A test rig that built it runs
scenarios through npx @unotest/web e2e or the MCP run_test tool
instead.
Lint rule names in the config. If linter.rules in
unotest.config.mjs names one of these, update it:
| Was | Now |
|---|---|
validator:implicit-declaration | validator:implicit-global |
validator:unsupported-statement | validator:top-level-statement |
validator:out-of-language, validator:library-member, validator:unsupported-string-member | drop the entry |
A config that still names an old rule loads, and lint warns about the
entry; for a renamed rule it names the new id.
What you can now write
Classes, getters, generators, Date, Symbol, instanceof and the
built-in objects work as in JavaScript. What a scenario does not have is
the APIs of Node and of the browser — see
The language is JavaScript.
A scenario runs with the rights of the user who started it, as a Playwright test does: it is not a sandbox.