🧪

Vitest Browser Mode を試す

2024/09/07に公開

はじめに

Vitest 2.0から に新しい E2E テスト機能として Browser Mode が追加されたので試してみました。
公式ドキュメントを見ても少し詰まった箇所があったので備忘録も兼ねてその導入手順を記事にしました。

https://vitest.dev/guide/browser/

Browser Mode は実験的機能ですので、今後の変更があるかもしれません。

本記事は vitest@2.0.5, pnpm, TypeScript, React, Playwight (Chromium)を使ったテストを前提に書いています。最後にVueのテストも記載しています。

インストール

Vitest やその他セットアップが完了している前提で進めます。

@vitest/browser をインストールします。

pnpm add -D @vitest/browser

Vitest は E2E プロバイダーに依存しないためローカルで動かす場合は依存はこれだけですが、CI の対応も兼ねてここでは playwright を使います。

pnpm add -D playwright

Vitest では PlaywrightWebdriverIO がサポートされています。

https://playwright.dev/

https://webdriver.io/

コンフィグを設定

vitest.config.ts に設定を追加します。エイリアスなどの設定も環境に合わせて追加しています。

nameにはブラウザ名を指定します。プロバイダー毎にサポートしているブラウザが異なります。詳細は公式ドキュメントを参照してください。

ここではchromiumを利用します。

import { defineConfig } from "vitest/config";

export default defineConfig({
  resolve: {
    alias: {
      "@": "/src",
    },
  },
  test: {
    browser: {
      enabled: true,
      name: "chromium",
      provider: "playwright",
    },
  },
});

テストを書く

Vitest はレンダリング機能を提供していないため、@testing-library/reactなどを使ってレンダリングするため、インストールします。

pnpm add -D @testing-library/react

*.spec.tsxでテストを書きます。 CSS もテストファイルから読み込むことで、ブラウザテストにも反映されます。クリックなどのインタラクションのために API が用意されています。@vitest/browser/contextからuserEventをインポートして利用します。詳細は公式ドキュメントを参照してください。

import "../global.css";
import "@toshusai/cmpui/dist/index.css";
import "@toshusai/cmpui-css/dist/index.css";

import { userEvent } from "@vitest/browser/context";
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";
import { AssetView } from "../components/AssetView";
import { state } from "@/state";

test("select an asset", async () => {
  render(<AssetView />);
  const el = screen.getByText("Honk");
  await userEvent.click(el);
  expect(Object.keys(state.selectedAssetIds).length).toBe(1);
  expect(el).toHaveAttribute("aria-selected", "true");
});

toHaveAttributejest-domのアサーション APIです。型を追加するために、typesRoots./node_modulesを追加し、types@vitest/browser/providers/playwrightを追加します。
こちらもプロバイダー毎に若干異なるようで、詳しくは公式ドキュメントを参照してください。

{
  "compilerOptions": {
    "typeRoots": ["./node_modules"],
    "types": ["@vitest/browser/providers/playwright"]
  }
}

テストを実行

vitestを実行すると、ブラウザや Chromium が起動してテストが実行されます。

{
  "scripts": {
    "test": "vitest"
  }
}

vitestのテストを実行した後のスクリーンショット

ブラウザテスト以外のテストに関してもダッシュボード形式で表示されるため、ブラウザテスト以外の目的でも利用できそうです。

ドラッグのテスト

ドラッグ&ドロップではなく、pointerdownを用いるドラッグについてはイベントが提供されていなかったため、自分で以下のように実装しました。あまり綺麗ではありませんが、これで一応は動作しているように見えます。

test("drag", async () => {
  const pointerId = 1;
  el?.dispatchEvent(
    new PointerEvent("pointerdown", { bubbles: true, pointerId: pointerId })
  );
  for (let i = 1; i <= 30; i++) {
    el?.dispatchEvent(
      new PointerEvent("pointermove", { bubbles: true, clientX: i, pointerId })
    );
  }
  el?.dispatchEvent(
    new PointerEvent("pointerup", { bubbles: true, pointerId })
  );
});

Vue での利用

Vue でも利用してみました。vitest.config.tsに Vue プラグインを追加してください。
概ね React と同じですが、SFC での利用ではなく、defineComponentを利用してテストコンポーネントを作成しました。

レンダリングが絡む部分はnextTickなどを利用するとテストが書きやすそうです。

import { render, screen } from "@testing-library/vue";
import { test, expect } from "vitest";
import CSelect from "./CSelect.vue";
import { defineComponent, h, nextTick, ref } from "vue";

import "@toshusai/cmpui-css/dist/index.css";

const waitNextTick = () => new Promise<void>((resolve) => nextTick(resolve));

test("select an item", async () => {
  render(
    defineComponent({
      setup() {
        const value = ref("bravo");
        return () =>
          h(CSelect, {
            options: [
              { label: "Alfa", value: "alfa" },
              { label: "Bravo", value: "bravo" },
            ],
            value: value.value,
            onChange: (v: string) => {
              value.value = v;
            },
          });
      },
    }),
  );
  const selectButtonElement = screen.getByText("Bravo");
  const pointerId = 1;
  selectButtonElement?.dispatchEvent(
    new PointerEvent("pointerdown", { bubbles: true, pointerId: pointerId }),
  );
  for (let i = 1; i <= 16; i++) {
    selectButtonElement?.dispatchEvent(
      new PointerEvent("pointermove", {
        bubbles: true,
        movementY: 1,
        clientY: i,
        pointerId,
      }),
    );
  }

  await waitNextTick();
  const targetItemElement = screen.getByText("Alfa");
  targetItemElement?.dispatchEvent(
    new PointerEvent("pointerup", { bubbles: true, pointerId }),
  );
  await waitNextTick();
  expect(selectButtonElement.innerText).toBe("Alfa");
});

おわりに

Vitest Browser Mode は Vitest の延長で利用することができ、設定が少ないので簡単に利用できました。
複雑なGUIアプリケーションではマウスやキーボード操作をテストし、アプリやライブラリの品質を保ちたいため、このようなテストが簡単に行えるようになるのは非常に嬉しく思います。
まだ実験的な機能ですが導入も簡単で気に入りました。今後の発展が楽しみです。

今回は、趣味で作っているUIコンポーネントライブラリ動画編集ソフトのテストとして導入してみました。絶賛開発中ではありますが、もし興味がありましたら以下のリポジトリもご覧いただけたら幸いです。

https://github.com/toshusai/cmpui

https://github.com/toshusai/Vega

追記

  • [2024/09/08]: Vitest CLIのオプションは不要だっため、テストの実行に関する記載を修正しました。

Discussion