Skip to content
v0.6.9: frontend tests without a browser

One addition in v0.6.9, for frontend tests.

The harness used to need a browser ​

The frontend test harness serves pages from the dev server. And the dev server always renders on the client: it sends a shell, and the page appears only once its JavaScript runs.

So a frontend test had nothing to look at without a real browser, Playwright or Puppeteer, to fetch the shell and render the page. Even for a test that only wanted to check a heading.

frontend.test.ssr ​

Now a folder with SSR enabled can ask the harness to serve it with SSR:

kosmo.config.ts
ts
export default defineConfig({
  frontend: {
    stack: "react",
    base: "/",
    ssr: true,
    test: {
      ssr: true,
    },
  },
});

The response is then the server-rendered HTML itself, so a test can fetch it and parse it with something light like Cheerio:

ts
import { load } from "cheerio";
import { describe, expect, test } from "vitest";

import { prepareHarness } from "_/test/pages";

const { page, route } = await prepareHarness("account");

describe(route, () => {
  test(route, async () => {
    const $ = load(await fetch(page.href()).then((r) => r.text()));
    expect($("h1").text()).toBe("Account");
  });
});

No browser to install, launch or wait for.

Off by default ​

test.ssr defaults to false, and that is the right default for most suites.

The dev server starts much faster, and a browser renders the same page from either server, so a test that drives Playwright gains nothing from SSR.

Turn it on when the server-rendered HTML is what you want to assert on. It only has an effect on a folder with SSR enabled.

Vue: hydration that did not hydrate ​

A server-rendered page is meant to be hydrated in the browser: Vue takes over the markup the server sent, instead of rendering it again.

That only happens for an app created with createSSRApp. The client router created it with createApp, and for such an app mount(root, true) quietly ignores the true and renders from scratch.

So every SSR page in a Vue folder was rendered twice: once on the server, then again in the browser, throwing the server's markup away. The page looked right, which is why it went unnoticed, but the work SSR saved was done anyway, and slow devices could see the content flash.

The client router now uses createSSRApp, so hydration actually hydrates.

Vue: a root route made of optional params ​

A top-level route with only optional params, say pages/{filter}/index.vue, should match both / and /active.

With no params given, its path compiles to an empty string. The Vue router runs with strict: true, and a strict route with an empty path does not match /. So /active worked, and the home page did not.

Strictness is now relaxed for that one route, so it matches / again, and every other route is ranked as strictly as before.

Released under the MIT License.