はじめに
UI のビジュアルテストは、意図しない見た目の変化を検知するのに非常に有用です。しかし、アニメーションやトランジションが有効なままだと、スクリーンショット間で微妙に異なる画像が生成され、不要な差分に引っかかってテストが落ちてしまうことがあります。
この記事では、Mantine の Notification コンポーネントを例に、Playwright のスクリーンショット機能を使いつつ、不要な差分を防ぐコツを紹介します。
開発環境
開発環境は以下の通りです。
- Windows 11
- VSCode
- Vite 6.3.4
- React 18.3.1
- TypeScript 5.8.3
- Playwright 1.52.0
- Mantine:7.17.7
開発準備
まず、Playwright や Mantine の利用設定を行います。
手順は公式ドキュメントや以下の記事に記載があります。
また、Notifiction を利用するためには、上記の設定に加え、@mantine/notifications
のインストールと Notification のスタイルのインポートが必要です。
npm install @mantine/notifications
import '@mantine/notifications/styles.css';
テスト対象コードの実装
テスト対象となる最低限のアプリを実装します。
// organize-imports-ignore
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import { Button, MantineProvider } from "@mantine/core";
import { Notifications, notifications } from "@mantine/notifications";
function App() {
return (
<MantineProvider>
<Notifications />
<Button
onClick={() =>
notifications.show({
title: "Default notification",
message: "Do not forget to star Mantine on GitHub! 🌟",
})
}
>
Show notification
</Button>
</MantineProvider>
);
}
export default App;
動作確認をします。ボタンをクリックすると、Notification が表示されます。
テストコード
上記のプロダクションコードをテストするコードを実装します。
import { expect, test } from "@playwright/test";
test("notification test", async ({ page }) => {
await page.goto("/");
await page.getByRole("button", { name: "Show notification" }).click();
await expect(page.getByText("Default notification")).toBeVisible();
await page.screenshot({
path: "src/__tests__/screenshots/notification.png",
});
});
不要な差分が発生するケース
Mantine の Notification にはデフォルトでフェードイン/フェードアウトのアニメーションがかかっているため、表示タイミングや実行環境によってスクリーンショットがわずかに異なる場合があります。
page.addStyleTag
Playwright では、page.addStyleTag
を使ってテスト中に任意の CSS をページに追加できます。ここで全要素のトランジションとアニメーションを無効化してしまえば、毎回同じ見た目でスクリーンショットが取得できます。
await page.addStyleTag({
content: `
*, *::before, *::after {
transition: none !important;
animation: none !important;
}
`,
});
これをテストの最初に追加しておくことで、以降の操作中のアニメーションによる揺らぎを取り除くことができます。
import { expect, test } from "@playwright/test";
test("notification test", async ({ page }) => {
await page.goto("/");
await page.addStyleTag({
content: `
*, *::before, *::after {
transition: none !important;
animation: none !important;
}
`,
});
await page.getByRole("button", { name: "Show notification" }).click();
await expect(page.getByText("Default notification")).toBeVisible();
await page.screenshot({
path: "src/__tests__/screenshots/notification.png",
});
});
notification.png
は常に同じビジュアルで取得されるため、ビジュアルレグレッションのベースラインとして安心して利用できます。