7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

StorybookとLost Pixel を用いたビジュアルリグレッションテスト

Last updated at Posted at 2024-10-31

viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方はこちらまで。

はじめに

Storybookを用いたビジュアルリグレッションテスト(VRT)はChromaticやStorycap + reg-suitなどを用いることが多いかと思います。
しかし、ChromaticはGitHub Enterprise Serverを利用している場合は、Enterpriseプランの契約が必要となり、reg-suitは外部のクラウドストレージが必要です。

上記の制約がある中、ゆるくVRTを検証してみたかったので、リポジトリにベースイメージを保存してVRTを実行できるLost Pixelを使用してみました。

Lost Pixelとは

Lost PixelはOSSのビジュアルリグレッションテストツールで、Storybook、Ladle、Historie、ページスクリーンショット、Playwrightなどを用いたカスタムスクリーンショットをサポートしています。

OSS版の他に有料版のLost Pixel Platformというマネージドサービスも提供されています。
他のビジュアルリグレッションテストツールとは違い、リポジトリにベースイメージのスクリーンショットを保存して比較するのが特徴です。

使用環境

以下ライブラリのバージョンを使用します。

  • typescript: 5.6.3
  • storybook: 8.3.6
  • lost-pixel: 3.21.0

導入

次のコマンドを実行します。

npx lost-pixel init-ts
npm i -D lost-pixel

プロジェクトルートにlostpixel.config.tsが作成されます。

import type { CustomProjectConfig } from "lost-pixel";

export const config: CustomProjectConfig = {
  storybookShots: {
    storybookUrl: "examples/storybook-build/storybook-static",
  },
  generateOnly: true,
};

これだけでLostPixelの導入は完了です。
プロジェクトの要件に応じて設定を変更してください。

ベースラインイメージの作成とリグレッションテスト

事前にStorybookをビルドします。

npx storybook build

現在のストーリを正としたいため、まずは次のコマンドでベースラインイメージを作成します。

npx lost-pixel update

コマンドを実行すると.lostpixel/baselineにベースラインイメージが作成されます。
例えば次の画像をベースラインイメージとします。
components-button--vrt.png

ボタンのテキストをButtonからボタン変更してみます。

次のコマンドを実行すると、現在のスクリーンショットを .lostpixel/currentに出力し、差分が発生した場合は .lostpixel/differenceに差分を出力します。

npx lost-pixel

components-button--vrt.png

差分の画像はこちらです。
components-button--vrt.png

差分が意図したものである場合は、npx lost-pixel updateを実行してベースラインイメージを更新します。

FlakyなUIをマスクする

FlakyなUIでVRTの実行毎に差分が発生する場合は、特定のUIをマスクすることが可能です。
https://docs.lost-pixel.com/user-docs/recipes/general-recipes/masking-page-elements

export const config: CustomProjectConfig = {
  storybookShots: {
    mask: [
      {
        selector: '[data-mask]'
      }
    ]
  }
};

例えばボタンのテキストをマスクした場合は、このような感じになります。

components-button--vrt.png

マスクを使用するほどでもない場合は、thresholdsで調整することも可能です。
https://docs.lost-pixel.com/user-docs/recipes/general-recipes/thresholds

特定のStoryのみを対象にする

全てのStoryではなく、VRT用のStoryのみをテストの対象にしたいことがあります。
LostPixelではfilterShotというオプションを使用します。

export const config: CustomProjectConfig = {
  filterShot: ({ id, story, kind }) => {
    return true;
  },
};

filterShotの引数に渡されるStorybookのid、story、kindを使用してVRTの対象にするか判定できます。
以下がStoryとfilterShotに渡される引数のサンプルです。

import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";

const meta = {
  title: "Components/Button",
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {};
export const VRT: Story = {};

// Primary
{
 id: "components-button--primary",
 story: "Primary",
 kind: "Components/Button"
}

// VRT
{
 id: "components-button--vrt",
 story: "VRT",
 kind: "Components/Button"
}

例えばサフィックスがVRTのStoryのみ対象にしたい場合は、次のような実装になります。

export const config: CustomProjectConfig = {
  filterShot: ({ id, story, kind }) => {
    // S名がVRTで終わる
    return story.endWith("VRT");
  },
};

画像比較のエンジンを切り替える

Lost Pixelの画像の比較にはodiffpixelmatchが使用されており、オプションで切り替えることができます。デフォルト値はpixelmatchです。

ベンチマークではpixelmatchよりodiffの方が高速なため、比較画像が多く、パフォーマンスの問題が発生する際にはodiffの使用を検討してください。

export const config: CustomProjectConfig = {
  compareEngine: "odiff", // 'pixelmatch' or 'odiff'
};

まとめ

今回はベースラインイメージをリポジトリ管理して、VRTを実行できるLost Pixelを紹介しました。
画像をリポジトリ管理することの是非はあるかと思いますが、依存が少なく検証も簡単なため、ゆるくVRTを運用していきたい場合は導入してみるのもありかと思います。

参考

一緒に二次元業界を盛り上げていきませんか?

株式会社viviONでは、フロントエンドエンジニアを募集しています。

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?