From frontend
Configures Vitest test.projects for node/browser splits, browser mode with Playwright factory, optimizeDeps for flake reduction, and separate vitest.config.ts from vite.config. Use for config edits, flaky tests, upgrades, or Storybook integration.
npx claudepluginhub fubits1/svelte-skills --plugin frontendThis skill uses the workspace's default tool permissions.
- **Docs:** if unsure about an API, fetch `https://vitest.dev/llms.txt`. Do not guess.
Suggests manual /compact at logical task boundaries in long Claude Code sessions and multi-phase tasks to avoid arbitrary auto-compaction losses.
Share bugs, ideas, or general feedback.
https://vitest.dev/llms.txt. Do not guess.vitest.config.{ts,mts,js,mjs} for test / test.projects. Vitest-only-in-vite.config merges badly with multiple projects and Storybook.vitest.config.* but vite.config has test: (or no Vitest file) → ask before adding projects or Storybook whether to introduce vitest.config.ts and move test out of Vite.page.viewport(width, height) — import from vitest/browser.test.projects for mixed node/browser and Storybook. Either extends: true (inherit merged root — Storybook Vitest example with mergeConfig(viteConfig, …)) or extends: './vite.config.ts' per project; put test + plugins on each project (storybook-vitest skill).vitest-browser-svelte/pure when you need no auto-cleanup; unique data-testid + document.querySelector where needed.@vitest/* together.browser.provider: import { playwright } from '@vitest/browser-playwright' → provider: playwright() (factory, not a string).import { page } from 'vitest/browser' — not @vitest/browser/context.test.projects, plugins / resolve / optimizeDeps belong inside each project, not only at root — projects do not inherit root plugins.optimizeDeps.exclude: Svelte 5 runes in .svelte.js (e.g. Melt UI) so vite-plugin-svelte handles them, not esbuild.optimizeDeps.include: deps that trigger mid-test optimization (e.g. minisearch) to reduce flakes.vitest.config.ts + storybookTest + browser playwright() — storybook-vitest skill, manual setup.Browser/MSW/storybook suites are inherently flaky on a single run. Before any "green" claim:
lsof -ti :<test-server-port> — kill any holder.rm -rf node_modules/.vite node_modules/.cache/storybookIf any of (3)(4)(5) diverge: the suite is flaky. Investigate the flake itself (missing optimizeDeps.entries, race in async teardowns, MSW handler order) BEFORE claiming any fix works.
A single passing run on this kind of suite tells you nothing about whether your fix worked. It tells you only that ONE possible execution order happened to pass.
Vitest 4.1+ supports tags for filtering tests at runtime. Tags must be defined in config before use — using an undefined tag throws.
Config (root test block, inherited by all projects):
test: {
tags: [
{ name: 'ci-skip', description: 'needs recorded fixtures or live backend' }
],
}
Test file:
describe('my suite', { tags: ['ci-skip'] }, () => { ... })
// or on individual tests:
it('needs backend', { tags: ['ci-skip'] }, () => { ... })
CLI filter:
vitest --tags-filter='!ci-skip' # exclude
vitest --tags-filter='unit || e2e' # include
vitest --tags-filter='(unit || e2e) && !slow' # combine
Critical: tags only skip the test body — the file is still imported. If the import itself throws (e.g. MSW handlers throwing on missing fixtures), the tag won't help. Module-level code must be import-safe (warn, don't throw).
Critical: pnpm test -- --tags-filter='!ci-skip' does NOT work. The -- makes vitest treat --tags-filter as a positional arg. Use a dedicated script: "test:ci": "vitest --run --tags-filter='!ci-skip'".
locators.extend).locator(selector) is intentionally protected on vitest's Locator type (vitest-dev/vitest#7969). The official escape hatch is locators.extend (since Vitest 3.2):
// tests/browser/setup-locators.ts
import { locators } from "vitest/browser";
locators.extend({
css(selector: string) {
return selector;
},
});
declare module "vitest/browser" {
interface LocatorSelectors {
css(selector: string): Locator;
}
}
Wire it via setupFiles in the browser project config. Then .css() is properly typed — no @ts-expect-error needed.
Critical: do NOT name the extend function locator — it shadows the internal protected locator() method and causes infinite recursion (RangeError: Maximum call stack size exceeded). Use css or another name.
Vitest 4.0+ includes toMatchScreenshot() natively in browser mode. No extra packages needed — it's built into @vitest/browser with the Playwright provider.
import { expect, test } from "vitest";
import { page } from "vitest/browser";
test("component looks correct", async () => {
await expect(page.getByTestId("my-component")).toMatchScreenshot(
"my-component",
);
});
__screenshots__/ next to the test file*-actual.png + *-diff.png on failurevitest --updatemy-component-chromium-darwin.png)test: {
browser: {
expect: {
toMatchScreenshot: {
comparatorName: 'pixelmatch',
comparatorOptions: {
threshold: 0.2,
allowedMismatchedPixelRatio: 0.01,
},
},
},
},
}
await expect(element).toMatchScreenshot("name", {
screenshotOptions: {
mask: [page.getByTestId("timestamp")], // mask dynamic content
},
comparatorOptions: {
allowedMismatchedPixelRatio: 0.01,
},
});
Use toMatchScreenshot instead of manual Playwright MCP screenshots + eyeballing for CSS/layout verification. It provides programmatic pixel-level diffing with actual diff images — not subjective "looks the same" claims. Works in both browser and storybook test projects.