グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



Vitestでブラウザテストができる!? Browser Modeを試してみた

久々に Vitest のドキュメントを漁ったら experimental 段階ですが新しい機能が追加されていました。

昨今のフロントエンド界隈のエコシステムが更新されるスピードは早いですね。

今回は Vitest の実験的(experimental)な新しいテスト方法である Browser Mode を触っていきます。

環境構築

Vite ベースのプロジェクト作成

pnpm create vite@latest playground-vitest-browswer
✓ Select a framework: › React
✓ Select a variant: › TypeScript + SWC

※後で記載していますが、Vitest Browser Mode の使用時には TypeScript を選択せずに TypeScript + SWC を使用するとテスト時に ReferenceError: React is not defined が発生しテストが通らないので注意してください。

Vitest のインストール

pnpm i -D vitest

Browser Mode のインストール

pnpx vitest init browser

インタラクティブ形式で表示される設問を選びます。

✓ Choose a language for your tests › TypeScript
✓ Choose a browser provider. Vitest will use its API to control the testing environment › playwright
✓ Choose a browser › chromium
✓ Choose your framework › react
✓ Install Playwright browsers (can be done manually via ‘pnpm exec playwright install’)? … yes

Choose a browser provider の設問では 3つの選択肢が表示され、browser provider には playwright webdriverio preview から選択可能ですが、previewは CI に適していないと注意書きがあるため playwright を選択しました。

preview – Preview is useful to quickly run your tests in the browser, but not suitable for CI.
DeepL 翻訳:プレビューは、ブラウザ上でテストを素早く実行するのに便利だが、CI には適していない。

pnpx vitest init browser

■ This utility will help you set up a browser testing environment.

✓ Choose a language for your tests › TypeScript
✓ Choose a browser provider. Vitest will use its API to control the testing environment › playwright
✓ Choose a browser › chromium
✓ Choose your framework › react
✓ Install Playwright browsers (can be done manually via 'pnpm exec playwright install')? … yes

■ Installing packages...
■ @vitest/browser, vitest-browser-react, playwright, @vitejs/plugin-react

devDependencies:
+ @vitejs/plugin-react 4.3.2
+ @vitest/browser 2.1.2
+ playwright 1.47.2
+ vitest-browser-react 0.0.1

Done in 2.8s

✓ Created a workspace file for browser tests: vitest.workspace.ts

✓ Added "test:browser" script to your package.json.

■ Installing Playwright dependencies with `pnpx playwright install --with-deps`...

■ Add "@vitest/browser/providers/playwright" to your tsconfig.json "compilerOptions.types" field to have better intellisense support.

✓ Created example test file in vitest-example/HelloWorld.test.tsx
  You can safely delete this file once you have written your own tests.

■ All done! Run your tests with pnpm test:browser

Vitest の設定

Browser Mode の設定はインストール時に自動的に行なってくれますが、TypeScript の型周りの設定は追加する必要があるようです。

インストール時に追加されるファイル

.
├── vitest-example/
│   ├── HelloWorld.test.tsx
│   └── HelloWorld.tsx
└── vitest.workspace.ts

参照:https://vitest.dev/guide/browser/#configuration

ドキュメントを参考に TypeScript の型定義の追加を行います。

Browser Mode のインストール後に表示される案内に従って tsconfig.app.json に TypeScript の型定義を追加します。

--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -18,7 +18,9 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+
+ "types": ["@vitest/browser/providers/playwright"]
},
"include": ["src"]
}

compilerOptions.include プロパティには src しか設定されていないので、このままでは vitest-example ディレクトリ内のファイルは TypeScript に認識されません。
src ディレクトリ内に移動しておきましょう。

mv vitest-example src/

ついでに Vitest の設定でグローバル API を有効化します。

参照:https://vitest.dev/config/#globals

Browser Mode のインストールで vitest.workspace.ts ファイルが作成されるのでそちらに追記します。

--- a/vitest.workspace.ts
+++ b/vitest.workspace.ts
@@ -13,6 +13,7 @@ export default defineWorkspace([
         // https://playwright.dev
         providerOptions: {},
       },
+      globals: true,
     },
   },
 ]);

グローバル API の有効化に合わせて tsconfig.app.json に TypeScript の型情報を追加します。

--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -20,7 +20,7 @@
     "noUnusedParameters": true,
     "noFallthroughCasesInSwitch": true,

-    "types": ["@vitest/browser/providers/playwright"]
+    "types": ["@vitest/browser/providers/playwright", "vitest/globals"]
   },
   "include": ["src"]
 }

閑話休題: vitest.workspace.ts とはなんぞや

これまで vite.config.tsvitest.config.ts で Vitest の設定を行なっていたのですが、今回見慣れないファイルが出てきたので調べました。

参照:Workspace | Guide | Vitest

いつの間にか Vitest に monorepo のビルトインサポートが入ったみたいですね。

特筆すべきはディレクトリやファイル名によってユニットテストやインテグレーションテストを分けられる点です。

import { defineWorkspace } from "vitest/config";

export default defineWorkspace([
  {
    extends: "./vitest.config.ts",
    test: {
      name: "unit",
      include: ["**/*.unit.test.ts"],
    },
  },
  {
    extends: "./vitest.config.ts",
    test: {
      name: "integration",
      include: ["**/*.integration.test.ts"],
    },
  },
]);

参照:https://vitest.dev/guide/workspace#configuration

実際の現場では jsdom(happy-dom)がサポートしている DOM のエミュレートだけではカバーできずにモックを使用したり、実際のブラウザとは環境が違うことによるテストのしにくかったりという課題があります。

実行速度が早いユニットテストや jsdom(happy-dom)でカバーできるインテグレーションテストと、実際のブラウザを使用するブラウザテストを両立できるようになるのは非常によさそうです。

初めての Browser Mode テスト

Browser Mode のインストール時に vitest-example ディレクトリと package.json に npm scripts が追加されています。

  {
    ...
    "scripts": {
      "dev": "vite",
      "build": "tsc -b && vite build",
      "lint": "eslint .",
      "preview": "vite preview",
+     "test:browser": "vitest --workspace=vitest.workspace.ts"
    },
    ...
  }
// HelloWorld.tsx
export default function HelloWorld({ name }: { name: string }) {
  return (
    <div>
      <h1>Hello {name}!</h1>
    </div>
  );
}

// HelloWorld.test.tsx
import { expect, test } from "vitest";
import { render } from "vitest-browser-react";
import HelloWorld from "./HelloWorld.jsx";

test("renders name", async () => {
  const { getByText } = render(<HelloWorld name="Vitest" />);
  await expect.element(getByText("Hello Vitest!")).toBeInTheDocument();
});

手始めにテストを実行してみます。

pnpm test:browser

oh…

原因は Vite ベースのプロジェクト作成時に @vitejs/plugin-react-swc を選択していたことでした。

--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,5 +1,5 @@
 import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react-swc";
+import react from "@vitejs/plugin-react";

 // https://vitejs.dev/config/
 export default defineConfig({

こちらに変更して再度テストを実行するとテストが通るようになりました。

せっかくなので jsdom(happy-dom) ではできないようなテストも書いてみます。

jsdom では ResizeObserver がサポートされていないので、 ResizeObserver を使ってドキュメントの width と height を出力するコンポーネントを作成します。

// App.tsx
import { useEffect, useRef, useState } from "react";

import "./App.css";

type Rect = {
  readonly width: number;
  readonly height: number;
};

function App() {
  const ref = useRef<HTMLDivElement>(null);
  const [rect, setRect] = useState<Rect | undefined>();

  useEffect(() => {
    const observer = new ResizeObserver((entries) => {
      entries.forEach((entry) => {
        setRect({
          width: entry.contentRect.width,
          height: entry.contentRect.height,
        });
      });
    });

    observer.observe(document.body);

    return () => {
      observer.disconnect();
    };
  }, []);

  return (
    <div ref={ref}>
      <p>width: {rect?.width}</p>
      <p>height: {rect?.height}</p>
    </div>
  );
}

export default App;

これに対するテストがこちらになります。

// App.test.tsx

// 簡易的にファイル内で import していますが、テスト毎に import が必要になるので vitest.workspace.ts に移動した方がよさそうです
// ref. https://github.com/vitest-dev/vitest-browser-react?tab=readme-ov-file#vitest-browser-react
import "vitest-browser-react";
import { page } from "@vitest/browser/context";
import App from "./App";

test("should be work with ResizeObserver", async () => {
  const screen = page.render(<App />);

  // デフォルトの viewport は 414x896
  // ref. https://vitest.dev/config/#browser-viewport
  await expect.element(screen.getByText("width: 414")).toBeInTheDocument();
  await expect.element(screen.getByText("height: 896")).toBeInTheDocument();

  // viewport を 1920x1080 に変更します
  await page.viewport(1920, 1080);

  await expect.element(screen.getByText("width: 1920")).toBeInTheDocument();
  await expect.element(screen.getByText("height: 1080")).toBeInTheDocument();
});

Vitest Browser Mode のデフォルト viewport は 414×896 なのでまずその確認してから viewport を変更してサイズを確認してます。

これでテストを走らせると

      [0] Browser runner started by playwright at http://localhost:5173/

 ✓ |0| src/App.test.tsx (1)
 ✓ |0| src/vitest-example/HelloWorld.test.tsx (1)

 Test Files  2 passed (2)
      Tests  2 passed (2)
   Start at  14:50:48
   Duration  981ms (transform 0ms, setup 0ms, collect 96ms, tests 169ms, environment 0ms, prepare 31ms)

ResizeObserver を使ってもちゃんと動いていますね。

注意事項

この検証をしている時にブラウザを起動した状態で試すと viewport で指定した width から-15px されてテストが通らなかったため、ヘッドレスモードでテストを行なっています。
恐らくは Vitest Browser Mode の UI で使用される iframe のスクロールバー分の横幅が引かれた状態になってしまうのかな?という予想ですが詳細不明です。
document.body の横幅をテストしたいケースはそこまでないかと思ったので、取り急ぎ ヘッドレスモードで難を凌いでいますのでご注意ください。

--- a/vitest.workspace.ts
+++ b/vitest.workspace.ts
@@ -12,6 +12,7 @@ export default defineWorkspace([
         provider: "playwright",
         // https://playwright.dev
         providerOptions: {},
+        headless: true,
       },
       globals: true,
     },

参照:https://vitest.dev/guide/browser/#headless

まとめ

Vitest を使用してブラウザテストができるのは中々インパクトがあるのではないでしょうか。

公式ドキュメントにもあるように experimental 段階ですので、API に将来変更が入る可能性が高いので導入にはドキュメント等を確認の上ご検討ください。

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします