# Upgrade to scenarios on V8

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

Since 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

```sh
cd unotest
npm i -D @unotest/web@latest
npx @unotest/web lint --fix
```

Run 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:

```js
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

```sh
cd unotest && npx @unotest/web collection regress
```

Use 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:

```js
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`:

```js
rows.sort((a, b) => a.order - b.order);             // numbers
rows.sort((a, b) => a.name.localeCompare(b.name));  // strings
```

For `{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](/concepts/scenarios/#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.
