テスト設計書
概要
MorningStatusApp のテストは Vitest で実行する。
テストコマンド: bun run test
画面の見た目の回帰確認は、Vitest とは別系統で Playwright の toHaveScreenshot() を使うビジュアル回帰テストで行う(ビジュアル回帰テスト)。
テストコマンド: bun run test:visual
テスト分類
| 分類 | 対象 | 配置場所 |
|---|---|---|
| ユニットテスト | ライブラリ関数・ユーティリティ | lib/*.test.ts |
| コンポーネントテスト | React コンポーネント(UI 表示・操作) | components/*.test.tsx |
| ページテスト | Next.js ページ(Server Component) | app/**/*.test.tsx |
| API テスト | Next.js API Route(GET/PUT) | app/api/**/*.test.ts |
| スクリプトテスト | バッチスクリプト(同期処理) | scripts/*.test.ts |
| ビジュアル回帰テスト | 画面の見た目(変更前後のスクリーンショット比較) | app/**/*.visual.ts |
テストファイル一覧
ライブラリ(lib/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
lib/blob.test.ts |
getMembersFromBlob() |
Blob フェッチ成功・失敗・バリデーションエラー |
lib/releases.test.ts |
getReleases() |
Prisma フィールドマッピング・null→undefined 変換・TrackLink→ExternalLink |
lib/date.test.ts |
formatJST() |
JST 変換・空文字・無効値 |
lib/sns.test.ts |
resolveSnsCheck() / hasOfficialSnsSourceMatch() |
各 snsCheck 状態の解決・SNS ドメイン一致判定 |
lib/member-release-linker.test.ts |
linkMemberToReleases() |
在籍期間とリリース日の重複判定・overrides 適用 |
lib/musicbrainz.test.ts |
fetchReleaseGroupsByArtist() |
API 正常取得・レート制限(429)再試行・JSON 解析エラー |
lib/release-normalizer.test.ts |
normalizeReleases() / deduplicateReleases() |
正規化・重複排除・releaseDate のスペース処理 |
コンポーネント(components/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
components/MemberCard.test.tsx |
MemberCard |
氏名・世代・ステータス・SNS バッジ・最終更新日の表示 |
components/MemberDetailView.test.tsx |
MemberDetailView |
プロフィール表示・編集フォーム・保存・キャンセル・statusHistory 折りたたみ |
components/MemberSelect.test.tsx |
MemberSelect |
検索フィルタ(名前・読みの前方一致)・フィルタ解除 |
ページ(app/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
app/page.test.tsx |
Home(トップ) |
メンバー件数・最終更新日・バッチサマリ・isRecentlyUpdated 境界値 |
app/members/[id]/page.test.tsx |
MemberDetail |
メンバー表示・notFound・前後ナビゲーション・関連リリース表示 |
app/releases/page.test.tsx |
ReleasesPage |
フォーマット別グルーピング・データなし表示 |
app/releases/[id]/page.test.tsx |
ReleaseDetailPage |
リリース詳細表示・notFound・関連メンバー表示 |
API Route(app/api/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
app/api/members/route.test.ts |
GET / PUT /api/members |
Blob フェッチ・保存成功・members.json 取得失敗時のエラー・statusHistory 蓄積・上限100件・デスクトップモード時の本番キャッシュ再検証呼び出し(#1600) |
スクリプト(scripts/)
| ファイル | テスト対象 | 主なテスト項目 |
|---|---|---|
scripts/sync-status.test.ts |
syncMembers() / fetchMemberStatus() / performSearch() |
SNS 検索・Web 検索・日本語判定・snsCheck 更新・active フラグ不変・スキップ・曜日制御・ドライラン |
scripts/sync-discography.test.ts |
syncDiscography() |
MusicBrainz 取得・正規化・Blob 保存・ドライラン |
scripts/migrate-to-blob.test.ts |
migrateToBlob() |
ローカルファイル読み込み・Blob 書き込み |
scripts/patch-official-sns.test.ts |
patchOfficialSns() |
officialSns マージ・型変換・mergedCount 0件 |
テスト実装指針
モックパターン
| 対象 | モック方法 |
|---|---|
fetch(グローバル) |
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({...})) |
| 環境変数 | vi.stubEnv('VAR_NAME', 'value') |
| Vercel Blob | vi.mock('@vercel/blob', () => ({ put: vi.fn() })) |
| exa-js(クラス) | vi.mock('exa-js', ...) + mockImplementation(function() {...}) |
Dateのみ偽装 |
vi.useFakeTimers({ toFake: ['Date'] }) + vi.setSystemTime(...) |
fetchモックには必ずok: trueを含めること(省略すると正常系が全失敗する)。
exa-js のようなクラスコンストラクタのモックは
functionキーワードを使うこと (アロー関数では「is not a constructor」エラーになる)。
クリーンアップ
afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
vi.useRealTimers(); // useFakeTimers を使った場合
// vi.restoreAllMocks() はタイマーを戻さないため useRealTimers が別途必要
});
Server Component のテスト
Server Component(async function Page())は render() を使わず直接 await して結果を検証する。
const result = await Home();
const html = JSON.stringify(result);
expect(html).toContain('"isRecentlyUpdated":true');
next/linkのLinkはJSON.stringifyで循環参照エラーになるためvi.mock('next/link', ...)で差し替えること。
曜日依存ロジックのテスト
曜日によって結果が変わるロジック(shouldSyncOG() 等)は Date を固定してテストする。
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(new Date('2026-01-05T11:30:00Z')); // 月曜日
再試行ロジックのテスト
setTimeout を含む再試行ループは vi.useFakeTimers() 全面適用 + vi.runAllTimersAsync() で完走させる。
ビジュアル回帰テスト
見た目が変わらないはずの変更(Panda CSS のメジャー更新など、依存パッケージの更新)の前後で画面のスクリーンショットを比較し、型チェックやユニットテストでは検出できない見た目の回帰がないことを確認する。合格条件は、比較した全画面で差分が出ないことである。
用途
| 変更の種類 | 使い方 |
|---|---|
| 見た目が変わらないはずの変更(依存パッケージの更新、リファクタリング) | 変更前後を比較し、差分が出ないことを確認する |
| 意図した見た目の変更(UI の改修) | 変更した画面は比較の対象外とし、従来どおり動作確認のスクリーンショットを PR に添付して確認する。影響が及ばないはずの画面に差分が出ないことの確認には使ってよい |
配置とファイル構成
- 画面ごとのファイルを、対象画面のディレクトリに
page.visual.tsとして置く(例:app/timeline/page.visual.ts)。ユニットテスト(page.test.tsx)と同じ置き方である。 - 各ファイルには、画面への遷移、操作(モード切替・データ選択等)、撮影する状態を定義する。
- 撮影の共通処理(画面の表示待ち・スクロール・画像の読み込み待ち・スクリーンショットの比較)は、
test/visual/helpers.tsのexpectScreenshot()にまとめ、各ファイルから呼ぶ。 - 拡張子
.visual.tsは Vitest の対象(*.test.*/*.spec.*)と重ならないため、bun run testには含まれない。 - 共通設定は、リポジトリ直下の
playwright.config.tsに集約する。
共通設定
| 項目 | 設定 |
|---|---|
| プロジェクト | mobile(幅375px)・desktop(幅1280px)。ブラウザは Chromium のみ |
| ロケール | locale: 'ja-JP' と起動引数 --lang=ja-JP を併用する。タイムゾーンは Asia/Tokyo |
| 開発サーバー | webServer で bun run dev を起動する(起動済みの場合は再利用する) |
| 並列実行 | しない(workers: 1)。同一の開発サーバー・データストアを共有するため |
| タイムアウト | 画面の初回コンパイルが遅いため、テスト・ナビゲーション・expect とも既定値より長くする |
| 撮影 | animations: 'disabled'。撮影前にページ最下部までスクロールして遅延画像を読み込ませ、先頭へ戻す。画面内の画像の読み込みが終わるのを待ってから撮る(networkidle は、埋め込みのある画面で到達しないため使わない) |
| 表示の除外 | 開発サーバーの開発用インジケーター(Next.js)が画像に写り込まないよう、test/visual/visual.css(stylePath)で非表示にする |
画面ごとの *.visual.ts は、ロード中表示(loading.tsx のフォールバック)が消えたことを待ってから撮影する。
基準画像と比較結果
| 種類 | 保存先 | 設定 |
|---|---|---|
| 基準画像 | .visual-regression/baseline/ |
snapshotPathTemplate |
| 比較結果(実際の画像・差分画像) | .visual-regression/results/ |
outputDir |
- 保存先の
.visual-regression/は、.claude/tmp/(Issue の登録・編集、レビュー、動作確認用スクリプトなどの作業用一時ファイル)とは別のディレクトリとし、Git 管理外とする。 - Git 管理しない理由: 表示データ(Vercel Blob・Neon)は随時更新されるため、コミットした基準画像は CSS を変えなくても古くなり、偽の差分を出す。フォントの描画も OS・端末で変わるため、他の端末で撮った基準画像とは一致しない。基準画像は比較のたびに同じ端末で作り直す一時的な成果物であり、保管しない。
- PR に添付する動作確認のスクリーンショットは、従来どおり
screenshots/<PR番号>/に格納する。基準画像とは別物である。
運用手順
- 変更前(main)で、基準画像を作成する:
bun run test:visual --update-snapshots - 作業ブランチに切り替えて変更を適用する(依存パッケージの更新など)。
- 同じ端末で、時間を空けずに比較する:
bun run test:visual。差分のあった画面だけが失敗し、差分画像が出力される。 - 差が出たら、
node test/visual/crop-diff.mjsで差のある箇所を切り出し、差の場所が日付に依存する表示などに限られるかを確認する(差のある箇所の切り出し)。
表示データの更新が手順1と3の間に入ると、CSS と無関係な差分が出る。日付・最新データなど毎回変わる領域は、toHaveScreenshot() の mask でマスクする。
差のある箇所の切り出し
比較で差が出たとき、差のある箇所を、基準画像と今回の画像から同じ範囲で切り出して並べて確認する。Playwright が出す差分画像(*-diff.png)は、一致する部分を薄く描くため白っぽく、実際の配色の違いを確認する証拠には向かない。切り出した画像は、PR に添付する確認用の画像として使える。
node test/visual/crop-diff.mjs [--out <出力先>] [--top <領域の数>] [--margin <余白>]
| 引数 | 既定値 | 内容 |
|---|---|---|
--out |
.visual-regression/crops/ |
切り出した画像の出力先 |
--top |
2 |
1つの組から切り出す領域の数(差の大きい順) |
--margin |
24 |
差の範囲に足す余白(px) |
- 入力:
.visual-regression/results/を走査し、同じディレクトリに*-expected.png(基準画像)と*-actual.png(今回の画像)が揃った組をすべて対象にする。画面の一覧はスクリプトに持たない。 - 差の判定: 画素の RGB のいずれかが、基準画像と今回の画像で 8 より大きく違えば、差ありとする。差のある画素を 16px 単位のブロックにまとめ、隣り合うブロック(斜めを含む)を 1 つの領域にする。領域は差のある画素の多い順に並べ、上位
--top件を切り出す。 - 切り出す範囲: 領域の範囲に
--marginを足す。小さすぎて見づらくならないよう、幅 320px・高さ 160px を最小とし、画像の内側に収める。画像の端に近く、最小のサイズを確保すると画像の外に出る場合は、画像の内側に収めることを優先する。このとき、切り出し範囲は最小のサイズを下回ることがある。 - 出力: 領域ごとに、同じ範囲を
<ディレクトリ名>-<ベース名>-<通し番号>-expected.pngと-actual.pngで出力する。通し番号は 0 から始まり、差の大きい順である。ディレクトリ名のうち、ファイル名に使えない文字(\/:*?"<>|)は_に置き換える。 - 比較できない組: 基準画像と今回の画像の大きさが違う組は切り出さず、その旨を出力して次の組へ進む。
- 対象の組がない場合: 対象の組が 1 つもなければ、その旨を出力して終了コード 0 で終了する。
.visual-regression/results/自体がなければ、先にbun run test:visualを実行するよう案内して、終了コード 1 で終了する。 - 差のない組: 基準画像と今回の画像の大きさが同じで、差のあるピクセルがない組は、画像を出力しない。
- 引数が不正な場合: 値のない引数、数値でない
--top・--margin、不明な引数は、メッセージを出して終了コード 1 で終了する。 - 画像の処理には
sharpを使う。devDependenciesに直接宣言する。実行はnodeで行う。 - 切り出しスクリプト(
crop-diff.mjs)のテストはtest/visual/crop-diff.test.tsに置き、Vitest で実行する。scripts/配下のバッチスクリプトのテスト(スクリプトテスト)とは別物で、ビジュアル回帰テスト(*.visual.ts)とも異なり、拡張子が.test.tsのためbun run testに含まれる。
対象画面
初期は、Panda CSS 2.x への更新(#1672)の回帰確認に必要な、メンバー一覧・タイムライン・メンバー関係性マップ(関係性レーダーチャートを含む)とする。他の画面は、その画面に触れるときに追加する。
メンバー詳細は、モバイルで投稿を表示する領域が極端に縦長になり(#1741)、ページ全体のスクリーンショットを撮れないため、#1741 の対応後に追加する。
テスト実行
bun run test # 全テスト実行
bun run test --watch # ウォッチモード
bun run test path/to/file.test.ts # 特定ファイルのみ
bun run test:visual # ビジュアル回帰テスト(基準画像との比較)
bun run test:visual --update-snapshots # ビジュアル回帰テストの基準画像を作成・更新
node test/visual/crop-diff.mjs # ビジュアル回帰の差のある箇所を切り出す(差が出たあとに実行)
CI では
bun run lint(0 warnings)・bun run type-checkも合わせて通過させること。
改訂履歴(バージョン降順に記載)
| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.5 | 2026-10-24 | ビジュアル回帰テストに「差のある箇所の切り出し」節を追加し、運用手順に差が出たときの確認を追記(MorningStatusApp#1776) |
| 1.4 | 2026-10-12 | Blob の URL を個々の環境変数で指定する方式から相対パスで指定する方式に変更する設計を追記 |
| 1.3 | 2026-10-11 | ビジュアル回帰テストの共通ヘルパー・並列実行・開発用インジケーターの非表示を追記。テスト分類表の列幅の統一と、ビジュアル回帰テスト節のカスタムIDの削除 |
| 1.2 | 2026-10-10 | ビジュアル回帰テストの方針と運用手順を追記し、待機方式を反映。初期の対象画面からのメンバー詳細の除外 |
| 1.1 | 2026-09-07 | デスクトップモードの編集保存が本番Webのキャッシュを無効化していなかった不具合に合わせて修正 |
| 1.0 | 2026-07-23 | Blume 設計書サイトへ取り込み(これより前の履歴は、この表では追えない。内容の変更を伴わない整理〔タイトルの重複表示の解消、設計書の配置の再編〕は載せていない) |