Web フロントエンドの UI 開発では、私の中では Storybook は、ほぼ必須なツールです。
UI カタログとして使うだけでなく、 ビジュアルリグレッションテスト(VRT) にも活用したいと常々思っています。
VRT が必要になる場面としては、たとえば次のようなケースがあります。
Storybook の以下のドキュメントでは、Visual Tests は Chromatic の利用が紹介されています。
ただ、さまざまな理由から Chromatic に依存したくないこともあります。
ここで役に立つのが、Vitest に追加された Browser Mode です。
ヘッドレスブラウザ(Chromium など)上での UI テストが可能になりました。
さらに、 toMatchScreenshot により、スクリーンショットを比較する VRT ができるようになりました。
Storybook には composeStory(および composeStories)というAPIがあり、
Storybook の Story オブジェクトをテストコード内で利用できます。
これを使うことで、
をそのままつなげて、Chromatic なしで Storybook の Story を VRT できるようになります。
実際にこの方法を試したコミットはこちらです。
上記コミットには、Vitest 側の不具合修正を早めに取り込みたかったため、patch を当てています。
使用した環境は次のとおりです。
@storybook/react-vitevitest-browser-react当初は @storybook/nextjs-vite で試していたものの、Next.js の next/navigation 周りの mock エラーに苦戦し、一旦断念しました。
再挑戦すれ解決できる可能性はありますが、今回は @storybook/react-vite を使用しています。
// my-input.tsx
import { Input } from "<path to shadcn input component>";
import { ComponentProps } from "react";
type Props = ComponentProps<typeof Input>;
export const MyInput = ({ ...rest }: Props) => {
return <Input {...rest} />;
};// my-input.stories.tsx
import type { Meta, StoryObj } from "@storybook/react-vite";
import { MyInput } from "./my-input";
const meta = {
component: MyInput,
} satisfies Meta<typeof MyInput>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};// my-input.spec.tsx
import { composeStories } from "@storybook/react-vite";
import { describe, expect, it } from "vitest";
import { render } from "vitest-browser-react";
import * as stories from "./my-input.stories";
const { Default } = composeStories(stories);
describe("MyInput", () => {
it("matches Default story screenshot", async () => {
const { getByTestId } = await render(
<div data-testid="test">
<Default />
</div>,
);
await expect(getByTestId("test")).toMatchScreenshot();
});
});このテストを実行すると、Default Story のスクリーンショットが保存され、
次回以降はその差分が検知されるようになります。
composeStories と toMatchScreenshot を組み合わせることで、
という、シンプルな構成で VRT ができそうです。
Storybook と Vitest という、どちらもメジャーな OSS だけで実現できるため、外部依存を減らせる点も魅力的です。
@storybook/nextjs-vite でもスクリーンショット VRT が動作することを確認できました。
ポイントは、以下で紹介されている vite-plugin-storybook-nextjs を導入する必要があったことです。
私の環境では monorepo を採用しているため、vite.config.ts に次のような設定を追加しました。
// vite.config.ts
import nextjs from "vite-plugin-storybook-nextjs";
export default defineConfig({
plugins: [nextjs({dir: "../../apps/user"})],
});テストの書き方ですが、上記の紹介した形式だと、以下のエラーが発生しました。
SB_FRAMEWORK_NEXTJS_0002 (NextjsRouterMocksNotAvailable): Tried to access router mocks from "next/router" but they were not created yet. You might be running code in an unsupported environment.
そこで、以下のようなテストの書き方に変更したら動作しました。
// my-input.spec.tsx
import { composeStories } from "@storybook/react-vite";
import { describe, expect, it } from "vitest";
import { render } from "vitest-browser-react";
import * as stories from "./my-input.stories";
const { Default } = composeStories(stories);
describe("MyInput", () => {
it("matches Default story screenshot", async () => {
await Default.run();
await expect(document.body).toMatchScreenshot();
});
});修正したコミットは以下になります。
-
タグ「フロントエンド」の記事
最近、iframeを使っています。 クライアントサイドで埋め込む想定で、iframeを使おうとしています。 色々と苦労したことがあったので、書いて残しておこうと思います。 レスポンスヘッダー 前提として、ウェブアプリケーションをプロダクショ
紙を積んだイラストをSVGで書こうとしていました。 (当たり前ですが)図形を表現するためには数学の知識が必要で、学生の頃の記憶を思い出したので疲れました。 所感について、諸々書こうと思います。 成果物 実際に完成したのは、以下の画像ができま
2026年02月17日
以下の記事で書いた CSSをテストする方法について、試してみました。 https://zenn.dev/silverbirder/articles/df6752b230f04c ソースコードは、以下に置いています。 https://gith
タグ「テスト」の記事
以下の記事で書いた CSSをテストする方法について、試してみました。 https://zenn.dev/silverbirder/articles/df6752b230f04c ソースコードは、以下に置いています。 https://gith
Web のフロントエンド実装において、次のようなミスによってデザイン崩れを起こしてしまったことはありませんか。 flex-shrink の指定を忘れて、要素が押しつぶされてしまった z-index の指定を間違えて、要素が意図せず前面(また
AIの進化によって、プロダクションコードに対するテストコードは、以前と比べて格段に書きやすくなったと感じています。 単体テストに関する基本的なお作法については、以前に以下の記事で整理しました。 興味があれば、参考として読んでもらえると嬉しい
2026年01月09日