AgentStack
SKILL verified MIT Self-run

Storybook Vitest

skill-fubits1-svelte-skills-storybook-vitest · by fubits1

Svelte CSF (@storybook/addon-svelte-csf) + @storybook/addon-vitest: .stories.svelte as Vitest browser tests with play functions and tags. Use when wiring or debugging Svelte Storybook tests in Vitest/CI, not for React/Vue/CSF3 .ts story files.

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-fubits1-svelte-skills-storybook-vitest

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Storybook Vitest? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Storybook Vitest + Svelte CSF

Stories are .stories.svelte with defineMeta and `** from @storybook/addon-svelte-csf`. Official reference: Vitest addon, use it for plugin, browser mode, CLI, tags, debugging, CI**; ignore non-Svelte story file examples there. Manual setup · Example configuration files · Options

How it works

  • Plugin storybookTest turns stories into Vitest tests via portable stories (Vitest). No Storybook server required for the test run.
  • Each `: smoke render; a **play`** function runs as the interaction test (interaction testing, asserting with expect).
  • Console errors fail the test. MSW in .storybook/preview applies when configured (v2+ if you use MSW).
  • Test coverage: see Storybook’s coverage doc; vitest --coverage --project … only covers projects you pass (many pipelines omit storybook on purpose).
  • With the addon, snapshot tests are not supported (unlike the test runner; comparison).

.stories.svelte + play

Use storybook/test (expect, within, userEvent, waitFor) in play. Scope queries with canvasElement to within(canvasElement).

EVERY story MUST have meaningful interaction tests. A smoke render (``) proves the component mounts without crashing, nothing else. For every story that involves user-interactive elements (inputs, autocompletes, buttons, forms):

  1. Interact: click, type, select, submit
  2. Assert the result: verify the selected value, the changed

text, the callback data, the visual state change

  1. Never silently skip: no if (items.length > 1) guards.

If the dropdown should have options, ASSERT it does.

A play function that opens a dropdown but never selects a value is not a test. A play function that selects a value but never checks WHICH value was selected is not a test. Example: stories that "tested" autocomplete interaction by opening a dropdown and checking "something was selected", this proved nothing and missed a real bug (onChange returning objects instead of strings).

Tags: default storybookTest({ tags: { include: ['test'], … } }) (API), only tagged stories run. Add other tags to include when needed (e.g. autodocs if those stories should run under Vitest). Set tags on defineMeta / stories or adjust include / exclude / skip (exclude wins if the same tag is both included and excluded).


  import { defineMeta } from "@storybook/addon-svelte-csf";
  import { expect, within, userEvent } from "storybook/test";
  import MyComponent from "./MyComponent.svelte";

  const { Story } = defineMeta({
    title: "MyComponent",
    component: MyComponent,
    tags: ["test"],
  });

 {
    const canvas = within(canvasElement);
    const button = canvas.getByRole("button");
    await userEvent.click(button);
    await expect(canvas.getByText("Clicked")).toBeVisible();
  }}
/>

Test name in output: use name on `` (custom name FAQ).

Gotchas (FAQ)

  • CLI / Vitest vs Storybook Interactions panel can disagree: different environments (docs).
  • Vitest internal errors: widget + console; Vitest common errors.
  • Non-default public dir: set publicDir (FAQ).
  • Vitest failed to find the current suite: caused by optimizeDeps reload mid-test (look for ✨ new dependencies optimized: in output). Fix: add the newly-discovered deps to optimizeDeps.include on the storybook project config itself. Common culprits: msw-storybook-addon, svelte-tippy, @storybook/addon-svelte-csf, @storybook/addon-docs. (FAQ). Any fix for this error is UNVERIFIED until proven by the flake-hygiene protocol below. A single green run tells you nothing: this suite produces different counts between invocations on the same code. Do not recommend a fix until the flake-hygiene protocol has validated it.
  • Single-run bias: this addon is PARTICULARLY prone to producing inconsistent counts between invocations on the same checkout. A "Test Files N passed (N)" line on one run does NOT prove the suite is healthy. Never claim "fixed" or "green" based on a single run on this suite. If the user shows a failing screenshot and your run goes green, the FIRST move is to acknowledge you cannot reproduce their failure and ask for their log or reproduction conditions, not to re-run hoping for another green. See frontend:validate flake rules and frontend:vitest flake-hygiene section.
  • Do not enshrine unverified approaches as "better fixes" in this skill. If you encounter or propose a new approach to a storybook/vitest problem, it must go through the full flake-hygiene protocol on a real failure before being written down as a recommendation. Declaring an approach "worked" from a single background run while the user reproduces 31 failures on the same code is not verification. The approach may or may not be correct, but unverified does not go in the skill.
  • CI: dynamic import / iframe: test.isolate: false and/or --shard=i/n (FAQ, sharding).
  • Isolation: if vite.config defines test, it merges into configs that extend that Vite file and can break Storybook tests: move test to vitest.config (FAQ). mergeConfig(viteConfig, defineConfig({ test: … })) in vitest.config.ts is the documented Vitest 4 pattern as long as Vite does not own test.
  • Playwright (WebGL / maps / Canvas): optional browser.provider: playwright({ launchOptions: { args: … } }) per Vitest browser: not in Storybook’s doc, but common for headless Chromium.

asChild, decorators, and context

Two problems interact here:

  1. Decorator setContext doesn’t propagate to the story component in vitest headless mode (works in browser UI). Components needing Svelte context must get it from an asChild wrapper.
  1. Never combine asChild with a decorator. DecoratorHandler renders ` inside the decorator: this instantiates the component WITHOUT the props passed manually in the asChild content. The component renders twice (once broken via decorator, once correct via asChild), and the broken render crashes with e.g. Cannot read properties of undefined`.

Fix: put both context and visual wrapping (CardWrapper etc.) inside the asChild wrapper component, never as a decorator:


  import { setContext } from "svelte";
  import { writable } from "svelte/store";
  import CardWrapper from "$lib/storybook-util/CardWrapper.svelte";
  setContext("myContext", writable(null));

  const { Story } = defineMeta({
    title: "MyComponent",
    component: MyComponent,
    tags: ["autodocs"],
  });

  
    
  

Related

  • Vitest/browser projects: frontend:vitest skill.
  • Svelte tests outside Storybook: svelte-5:testing-svelte skill.
  • Decorator rendering internals + props_invalid_value traps: references/storybook-decorator.md.

Vitest config file

  • Use a dedicated vitest.config.{ts,mts,js,mjs} at the package root (per monorepo package). Put storybookTest and test.projects here.
  • Storybook docs recommend a separate test project for Storybook vs other tests when using Vitest ≥ 4.0 (manual setup).
  • Do not define test in vite.config if that config is extended/merged for Vitest: the FAQ’s merge problem is Vite’s test field, not mergeConfig itself.
  • extends: true on a project inherits the merged root Vitest config (matches official example). Alternative: root defineConfig({ test: { projects: [{ extends: './vite.config.ts', … }] } }) with no test in Vite: same isolation goal.
  • Gate: if there is no vitest.config.* and the only Vitest config is vite.config to test: (or there is no Vitest file), ask before adding the addon whether to introduce vitest.config.ts and move test out of Vite.

Setup (@storybook/addon-vitest)

Prefer pnpm exec storybook add @storybook/addon-vitest (automatic installation). Playwright Chromium is required for default browser mode, install browsers if prompted (Playwright browsers).

Manual wiring follows example configuration files. Vitest 4 example uses mergeConfig(viteConfig, defineConfig({ test: { projects: […] } })) and a Storybook project with extends: true.

  • setupFiles: since Storybook 10.3 the plugin applies preview annotations automatically — it always injects @storybook/addon-vitest/internal/setup-file, and injects setup-file-with-project-annotations only when you have not supplied a setProjectAnnotations setup file. So a manual .storybook/vitest.setup.ts is not required unless you have custom per-test setup beyond preview.ts; if you keep one that calls setProjectAnnotations, the plugin defers to it. Before 10.3 that file, referenced via setupFiles, was required — which is why the doc example still shows it.
  • storybookScript: docs: “This should match your package.json script to run Storybook” (e.g. pnpm storybook --no-open). You may prefix the same command with setup steps (i18n mocks, env) so watch-mode debugging matches dev.
  • storybookUrl: default http://localhost:6006: must be reachable for failure links (debugging). For CI, set the full URL of the published Storybook (including path prefix if hosted under a subpath) so output links work (CI, Testing in CI).
  • storybookScript behavior: in watch mode, the plugin starts Storybook via this script only if nothing is already available at storybookUrl (API).

Vitest 4 shape (aligned with Storybook doc; add sibling projects for node/browser/etc.):

import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig, mergeConfig } from "vitest/config";
import type { ConfigEnv, UserConfig } from "vite";
import { playwright } from "@vitest/browser-playwright";
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
import vite from "./vite.config";

const dirname = path.dirname(fileURLToPath(import.meta.url));

// If vite.config exports a function, resolve it:
function resolveViteConfig(env: ConfigEnv): UserConfig {
  return typeof vite === "function" ? vite(env) : vite;
}

const testConfig: UserConfig = {
  test: {
    projects: [
      // … other projects (node, browser) …
      {
        extends: true,
        plugins: [
          storybookTest({
            configDir: path.join(dirname, ".storybook"),
            storybookScript: "pnpm storybook --no-open",
          }),
        ],
        // Pre-bundle deps that trigger optimizeDeps reload mid-test
        // (causes "Vitest failed to find the current suite" error)
        optimizeDeps: {
          include: [
            "msw-storybook-addon",
            "svelte-tippy",
            "@storybook/addon-svelte-csf",
            "@storybook/addon-docs",
          ],
        },
        test: {
          name: "storybook",
          // no setupFiles: since Storybook 10.3 the plugin auto-applies preview annotations (see setupFiles note above)
          browser: {
            enabled: true,
            headless: true,
            provider: playwright({}),
            instances: [{ browser: "chromium" }],
          },
        },
      },
    ],
  },
};

// Function export handles vite.config that exports defineConfig(({ mode }) => …)
export default defineConfig((configEnv) =>
  mergeConfig(resolveViteConfig(configEnv), testConfig),
);

CLI & scripts

vitest CLI, default watch; use vitest run in CI.

Docs example: vitest --project=storybook (CLI). Many repos run --project storybook only from a dedicated script and keep test / validate on node + browser, that is a pipeline choice, not required by the addon.

{
  "scripts": {
    "test": "vitest run --project node --project browser",
    "test-storybook": "vitest run --project storybook",
    "test:story": "node --experimental-strip-types scripts/bin/test-story.ts"
  }
}

--silent: MSW logs flood storybook test output. ALWAYS use --silent when running storybook tests, or use pnpm test:story which includes it. Without --silent, grep for errors is impossible, the output is 90% MSW request/response bodies.

Debugging failures: When a storybook test fails, ALWAYS open the story in the Storybook browser UI via Playwright FIRST. The browser console shows the actual Svelte error with component stack trace. vitest only shows "test failed" without details. Decorators (CardWrapper etc.) swallow errors, the real error is only visible in the browser console.

Common Svelte 5 error: props_invalid_value, happens when bind:prop={undefined} targets a prop with $bindable(fallback). Fix: add explicit null default: prop = $bindable(null).

Vitest IDE can run/debug these tests from the editor.

Plugin options (quick)

| Option | Notes

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.