Skip to content

Why untestutils

The problem

Shipping software is not “a component in happy-dom”. It is a built app, a running process on a real URL, and side effects that only appear after prepare/start (i18n merges, cookies, sitemap, Nitro routes, …).

If the suite never walks that path, you test a parallel universe. Failures show up in staging instead.

Most “test utils” help you write more tests. untestutils changes what you test: the same prepare → start → URL path users hit — shared across Vitest and Playwright.

Contract

text
Recipe  →  prepare?  →  start  →  { url } and/or { dir }
Harness →  useHarness('id')  /  Playwright test.use({ harness })

You describe how the real target comes up. Specs assert against that live target.

Speed (CI)

“Test like production” only works if prepare is not paid on every file.

ApproachCost
Rebuild / remock per fileSlow; CI scales with file count
Shared prepare (identity hash → .untestutils/builds)Prepare once, many specs / workers reuse

That is the main time win vs per-file setup (including typical @nuxt/test-utils e2e patterns). Cache .untestutils/ in CI when inputs are stable; use prewarm so cold start is not on the first test alone.

This is not “faster than Vitest/Playwright” — those are runners. untestutils makes live e2e affordable.

Vs usual helpers

Usualuntestutils
Restart / remock the app per fileOne prepare, many files
Framework APIs that hide the URLSame recipe ids in Vitest and Playwright
Assert on stubs onlyAssert on live $fetch, page, SEO, cookies, redirects
Hand-rolled globalSetup / webServerDrivers (nuxt, staticDir, command, host, …)

Utils (untestutils/utils) help with real-runtime checks — they are not a second testing philosophy.

Vs @nuxt/test-utils

Clean break, not a drop-in. Keep Vitest/Playwright skills; change how apps are prepared and shared. See Migrate from @nuxt/test-utils.

Next

Released under the MIT License.