【Playwright】toHaveCSS を使って CSS を自動テスト

# はじめに
この記事では、Playwright の toHaveCSS というメソッドを使って CSS の自動テストを実装する方法やユースケースを記載します。

https://playwright.dev/docs/next/api/class-locatorassertions#locator-assertions-to-have-css

# 開発環境
開発環境は以下の通りです。

- Windows 11
- VSCode
- React 18.3.1
- Mantine 7.16.2
- TypeScript 5.5.2
- Playwright 1.50.1

# 事前準備
事前準備として、Playwright の利用設定を行います。
手順は公式ドキュメントや以下の記事に記載があります。

https://playwright.dev/docs/next/intro

https://qiita.com/Yasushi-Mo/items/a886118e8c166933f066

# 1. `color`
以下のコードでは、ボタンをクリックすると、背景色が変化します。

```BackgroundColorChangingButton.tsx
import { FC, useState } from "react";

export const BackgroundColorChangingButton: FC = () => {
  const [bgColor, setBgColor] = useState<"red" | "blue">("red");

  return (
    <button
      style={{ backgroundColor: bgColor }}
      onClick={() => setBgColor((prev) => (prev === "red" ? "blue" : "red"))}
    >
      Change Background Color
    </button>
  );
};

```

![Vite-React-TS-Google-Chrome-2025-02-04-09-45-55.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/7041f272-4c65-8113-d767-4ac600fc3bd2.gif)

toHaveCSS の第1引数にチェック対象の CSS プロパティである `background-color`、第2引数に値である `rgb(255, 0, 0)` を渡します。

```BackgroundColorChangingButton.spec.ts
import { test, expect } from "@playwright/test";

test("button should have correct background color", async ({ page }) => {
  await page.goto(
    "http://localhost:3000/playwright/to-have-css/background-color-changing-button"
  );

  const button = page.getByRole("button", { name: "Change Background Color" });
  await expect(button).toHaveCSS("background-color", "rgb(255, 0, 0)");

  await button.click();
  await expect(button).toHaveCSS("background-color", "rgb(0, 0, 255)");

  await button.click();
  await expect(button).toHaveCSS("background-color", "rgb(255, 0, 0)");
});

```

# 2. `border-color`
上記の例の類型として、マウスオーバー時のボーダーの色の変化なども toHaveCSS を使ってチェックできます。
以下の例では、マウスオーバー時、ボーダーが赤色になります。

```HoverButton.tsx
import { FC } from "react";

export const HoverButton: FC = () => {
  return <button className="hover-button">Hover me</button>;
};
```

```index.css
.hover-button {
  border: 4px solid transparent;
  background-color: white;
  color: black;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.hover-button:hover {
  border-color: red;
}
```

![Screen-Recording-2025-02-07-094448.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/ee81dae4-e6f9-4fee-80be-2dc66f5c8bea.gif)


```HoverButton.spec.ts
import { test, expect } from "@playwright/test";

test("button should change color on hover", async ({ page }) => {
  await page.goto("http://localhost:3000/playwright/to-have-css/hover-button");

  const button = page.getByRole("button");
  await expect(button).toHaveCSS("border-color", "rgba(0, 0, 0, 0)"); // Transparent

  await button.hover();
  await expect(button).toHaveCSS("border-color", "rgb(255, 0, 0)");

  await page.mouse.move(0, 0); // Moves the mouse away
  await expect(button).toHaveCSS("border-color", "rgba(0, 0, 0, 0)");
});
```

# 3. `opacity`

```FadeInText.tsx
import { FC } from "react";

export const FadeInText: FC = () => {
  return <p className="fade-text">Hover over me</p>;
};
```

```FadeInText.spec.ts
import { test, expect } from "@playwright/test";

test("text should fade in when hovered", async ({ page }) => {
  await page.goto("http://localhost:3000/playwright/to-have-css/fade-in-text");

  const text = page.getByText("Hover over me");

  // Check initial opacity (faded)
  await expect(text).toHaveCSS("opacity", "0.3");

  // Hover over the text
  await text.hover();

  // Check full opacity
  await expect(text).toHaveCSS("opacity", "1");
});

```

# 4. `font-size`

```HeadingFontSize.tsx
import { FC } from "react";

export const HeadingFontSize: FC = () => {
  return <h1 style={{ fontSize: "72px" }}>Heading</h1>;
};

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/48ea5532-e314-b951-0d56-ca3962c67f0b.png)

```HeadingSize.spec.ts
import { test, expect } from "@playwright/test";

test("heading should have correct font size", async ({ page }) => {
  await page.goto(
    "http://localhost:3000/playwright/to-have-css/heading-font-size"
  );
  await expect(page.getByRole("heading")).toHaveCSS("font-size", "72px");
});

```

# 参考
- [toHaveCSS](https://playwright.dev/docs/next/api/class-locatorassertions#locator-assertions-to-have-css)