以下の記事で書いた CSSをテストする方法について、試してみました。
ソースコードは、以下に置いています。
検証ページは、以下のURLです。
CSSを書いていて、以下のミスをしたことはありませんか?
flex-shrink の指定を忘れて、要素が押しつぶされてしまったz-index の指定を間違えて、要素が意図せず前面(または背面)に表示されてしまったobject-fit の指定を間違えて、画像の一部が欠損してしまったミスした場合、誤りを修正してリリースすると思います。
そのミスが再発しないように、テストコードを書きたくありませんか?
CSSの場合、見た目に関するテストというと、Visual Regression Testing(以下、VRT)が挙げられます。
対象のUIの画像を保存しておき、変更後のUIの画像と比較して差分を出し、意図するものか確認する。
これでも問題の検知はできるのですが、もう少し詳細な検証が欲しくなる場合があります。
特定のデータを入れた場合や特定のビューポートを設定した場合 など、さまざまな条件においてのテストが書きたいかと思います。
Vitest Browser Mode を使います。
それを使うことで、 テスト実行環境がPlaywrightなどブラウザ上で動かすこと ができます。
JSDOMのような仮想的なDOMではなく、Playwrightのような本物に近いブラウザ環境でテストを動かすことが重要です。
Playwright上でコンポーネントを表示すれば、テスト上で要素のスタイル情報を正確に取得できます。
さらに、Web APIも使ってテストコードを書くことができます。
getBoundingClientRect で要素の寸法を取得できる
elementFromPoint で前面に出ている要素を検出できる
加えて、Playwrightを動かす際にビューポートを指定することができます。
モバイル端末のようなビューポートが狭い状態の中で発生するデザイン崩れ も、テストコードを書くことができます。
では、具体的なテストコードを2つ紹介します。
1つ目は、"fixedなグローバルヘッダーの上に画像が前面に出てしまった不具合" を守るためのテストコードです。
以下のテストは、https://learn-layout.vercel.app にあるグローバルヘッダーがデモ画像より上に来ていることをテストしています。
import { expect, test } from "vitest";
import { page } from "vitest/browser";
import { render } from "vitest-browser-react";
import { CssAiPage } from "./CssAiPage";
test("global header stays above demo image when they overlap", async () => {
// Arrange
await page.viewport(1200, 800);
await render(<CssAiPage />);
const header = document.querySelector<HTMLElement>(".fixed-header");
const demoImage = document.querySelector<HTMLElement>(".demo-image");
if (!header || !demoImage) {
throw new Error("header or demo image not found");
}
// Act
document.documentElement.style.scrollBehavior = "auto";
const imgAbsTop = demoImage.getBoundingClientRect().top;
window.scrollTo({ top: imgAbsTop });
await new Promise(requestAnimationFrame);
// Assert
const imgRect = demoImage.getBoundingClientRect();
const pointX = Math.round(imgRect.left);
const pointY = Math.round(imgRect.top);
const hit = document.elementFromPoint(pointX, pointY);
expect(hit?.closest(".fixed-header")).toBe(header);
});上記のコードは、"デモ画像までスクロールして、デモ画像の表示位置のX,Y座標の elementFromPoint が グローバルヘッダーである" ことをテストしています。
デモ画像に z-index: 99999 を指定するとデモ画像がグローバルヘッダーより上になるため、エラーとなります。
2つ目は、"モバイルとデスクトップで表示する位置関係が変わる" ことを守るテストコードです。
以下のテストは、https://learn-layout.vercel.app にあるデモセクションの左右にあるコンテンツ(デモ動画とデモ手順)がモバイルとデスクトップで以下の状態であることをテストします。
import { page } from "vitest/browser";
import { expect, test } from "vitest";
import { render } from "vitest-browser-react";
import { CssAiDemo } from "./CssAiDemo";
const setViewport = async (width: number, height = 800) => {
await page.viewport(width, height);
};
const getRects = () => {
const video = document.querySelector<HTMLElement>(".sidebar-video");
const steps = document.querySelector<HTMLElement>(".sidebar-steps");
if (!video || !steps) {
throw new Error("sidebar blocks not found");
}
return {
video: video.getBoundingClientRect(),
steps: steps.getBoundingClientRect(),
};
};
test("positions demo blocks for desktop viewport", async () => {
// Arrange
await setViewport(1200);
// Act
await render(<CssAiDemo />);
// Assert
const { video, steps } = getRects();
expect(video.left).toBeLessThan(steps.left);
expect(video.top).toBe(steps.top);
});
test("positions demo blocks for mobile viewport", async () => {
// Arrange
await setViewport(375);
// Act
await render(<CssAiDemo />);
// Assert
const { video, steps } = getRects();
expect(video.top).toBeLessThan(steps.top);
});どちらのテストも、ビューポートを最初に設定し、コンポーネントを描画します。
そして、大事なのが 各要素を getBoundingClientRect を使って 要素のビューポートからの相対位置を取得します。
位置関係の情報を使って、どちらが左側にあるか、どちらが下側にあるか ということを検証します。
今回は簡単な2つのテストケースを紹介しました。
プロダクションコードでは、reflowの懸念があるので getBoundingClientRect などは 比較的使うことを躊躇います。
しかし、テストコードでは そのような躊躇いは不要です。
getBoundingClientRect だけでなく、innerHeight や scrollY といったものもテストコードで使えます。
テストのバリエーションは広がるのではないでしょうか?
昨今、さまざまなビューポートでの動作確認に加え、与えられるデータのバリエーションやボリュームも多様になってきました。
CSSのリンターやVRTに加えて、テストコードも加えると品質向上の一助になるかと思います。
ご参考にしてください。
-
※ ログイン不要で投稿できます。
※ 同じブラウザから投稿を削除できます。
0
読み込み中...
タグ「フロントエンド」の記事
以下で書いた通り、プロダクトコードを写経したテストコードを削除しました。 "こぶりー" ( https://kobliy.vercel.app/ ) という個人ブログを読むアプリのコードです。 https://silverbirder.gi
最近のお悩みは、Webのソフトウェア開発におけるテストコードが爆増したことにより、 テスト成功による過度な安心感 によって手動確認するのが減っているのかもと思ったりしています。 例えば、Webのフォーム画面に小さな改修があったとして、その修
以下で書いた個人ブログを読むアプリ(個人ブログライブラリ、略して "こぶりー" )をモバイルアプリで開発していました。 https://silverbirder.github.io/blog/contents/20260419/ 審査関連で
2026年05月11日
タグ「テスト」の記事
以下で書いた通り、プロダクトコードを写経したテストコードを削除しました。 "こぶりー" ( https://kobliy.vercel.app/ ) という個人ブログを読むアプリのコードです。 https://silverbirder.gi
業務でWebフロントエンドのテストコードを書く際に、どこに何を書くかというのをざっくりと考えをまとめてみます。 前提 Webアプリケーションのプログラムファイルがツリー構造である前提とします。 tree よくあるフィーチャー単位のフォルダ構
2026年06月05日
個人開発のバイブコーディングでテストコードを書かせているが意味がなかった。 期待する機能をプロンプトで指示しプロダクションコードが出来上がるが、同時にテストコードも書かせていた。 そのテストコードは、プロダクションコードをそのままテストコー