Requirements
- Node 20+ (for
npx; you don’t need a Node project). - Web: any OS. iOS: macOS with Xcode + the iOS Simulator.
Set up a project
# webnpx @unotest/web initnpm install --prefix unotest
# iOSnpx @unotest/mobile install /path/to/Your.app --update-envinit / install scaffold the unotest/ layout, write unotest.config.*, and
wire MCP config (.mcp.json / editor settings) so your agent finds the server
automatically.
The tests are their own package
unotest/ is a separate npm package — its own package.json, lockfile and
node_modules:
your-project/├── package.json ← yours. init never touches it└── unotest/ ├── package.json ← pins @unotest/web and whatever your helpers import ├── unotest.config.mjs └── e2e/The runner is never a dependency of your application. Upgrading unotest cannot collide with your app’s dependency graph, and your app’s peer conflicts cannot break a test run — the two trees never meet.
npm specifically, and a committed unotest/package-lock.json: a box installs a
pushed bundle with npm ci. Your application is free to use pnpm, yarn or bun —
that half never travels and is never installed.
Where to run commands
From inside unotest/, where npx finds the pinned binary in
unotest/node_modules/.bin and no version is ever guessed:
cd unotestnpx @unotest/web e2e welcomeThey work from the project root too — the CLI locates the root by looking for
unotest/e2e, the way git finds a repository. If you run them often from the
root, one line in your own scripts saves the cd, and adds no dependency:
"scripts": { "e2e": "npm --prefix unotest exec unotest-web --" }Choosing a browser (web)
During init you pick how Chromium is provided:
- System Chrome / Edge — zero download. Sets
channel: "chrome"(or"msedge") inunotest/unotest.config.mjs. - Bundled Chromium — Playwright’s build (~150 MB). Run
npx @unotest/web install-chromiumif you didn’t during init.
You can also run Firefox and WebKit — set browsers in
config. Single browser in dev; CI can run all three.
Wire your editor
unotest is an MCP server. After init, these editors pick it up automatically:
- Claude Code — via
.mcp.jsonand project settings. - Cursor / Codex — via their MCP config.
See Connect your editor for details.
Verify
# webcd unotest && npx @unotest/web e2e welcome
# iOSnpx @unotest/mobile doctorIf something’s off, the CLI prints a single actionable line — no stack traces in
normal operation. Set UNOTEST_DEBUG=1 for full diagnostics.
Upgrading
-
0.27 → 0.28: the suite became a package.
unotest/holds its ownpackage.json, lockfile and config. Migrate with:Terminal window npx @unotest/web initgit mv unotest.config.mjs unotest/unotest.config.mjsnpm install --prefix unotestand drop
@unotest/webfrom your application’spackage.json. A config left at the project root is refused with these instructions rather than ignored. Two things to check once it runs: aunotest/jsconfig.jsonincludewritten for the old layout points at../node_modules(make it./node_modules/), and a config that derives paths from its own location (dirname(fileURLToPath(import.meta.url))) now sits one level deeper, so those paths need a... -
0.19: run history moved to date shards. The first run of a new version migrates the old flat
unotest/.runs/<runId>/layout by itself. To look first,npx @unotest/web runs migrate --dry-runreports and changes nothing;UNOTEST_RUNS_LEGACY=keepre-files old runs into date shards instead of removing them. -
Playwright. After an upgrade that moves the pinned Playwright (
@unotest/weband@unotest/viewershare it), runnpx playwright installonce if your browser cache predates it.