🎲

Playwrightのテスト用エンドポイントを雑に生やすのにViteのMulti Page Appは便利

2024/01/16に公開

どういうこと?

Vite は特に設定を書かなくても、プロジェクトディレクトリ内に HTML ファイルを置けばエンドポイントとして扱えます。

例えば下記のように HTML を配置してviteで開発サーバーを起動すれば下記のエンドポイントが生えます。

  • index.html -> <root>/
  • sub.html -> <root>/sub.html
  • nested/index.html -> <root>/nested/
$ tree -L 2 -I node_modules
.
├── index.html
├── nested
│   └── index.html
├── package-lock.json
├── package.json
├── playwright.config.ts
├── src
│   ├── main.ts
│   └── vite-env.d.ts
├── sub.html
├── tsconfig.json
└── vite.config.ts

どんな時に役に立つの?

  • ペライチの HTML と script タグで書いたようなページを複数パターンで試せる
    • <script>タグで JavaScript を読み込んでちょろっと JS のコードを書くようなページなど
      • iframe やマップやツイート埋め込み(Embed)など
  • E2E テストをやりたいが、そのままやるのは重すぎるので雑に切り出してでも E2E テストを書きたい場合
    • HTML テンプレートをサーバーサイドレンダリングして JS 読み込んで...みたいなプロダクトを想定
    • モックサーバー用意したいけど、全ページだとモックが巨大になりすぎるので少ないページで書きたい
  • 本番環境用に JS のビルドが必要な場合でも Vite なので素早くビルドしてプレビューできる

Playwright と Vite を連携させる

Playwright でテストを実行時に Vite で開発サーバーを起動させて連携させます。

playwright の設定。お好みですが、TypeScript 推奨です。

$ npm init playwright@latest
  • webServer.command で開発サーバーを実行するコマンド、 webServer.urlに開発サーバーの URL を設定
    • デフォルトのポートだと既に使われている可能性があるので、意図的に指定しておく
  • use.baseURLにて開発サーバーの Path を記載
    • これによって、await page.goto("/")と相対パスを指定しただけでawait page.goto("http://localhost:3000")にアクセスしたのと同義になる。
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  reporter: "html",
  use: {
+   baseURL: "http://localhost:3000",
    trace: "on-first-retry",
  },
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
  webServer: {
+   command: "npx vite dev --port 3000",
+   url: "http://localhost:3000",
    reuseExistingServer: !process.env.CI,
  },
});

テストを書きます。説明のためにtests/index.spec.ts 1 ファイルにまとめていますが、実際は 1 ページ 1 ファイルにした方がいいです。

import { test, expect } from "@playwright/test";

test("index:has title", async ({ page }) => {
  await page.goto("/");
  await expect(page).toHaveTitle("Index");
});

test("sub:has title", async ({ page }) => {
  await page.goto("/sub.html");
  await expect(page).toHaveTitle("Sub");
});

test("nested:has title", async ({ page }) => {
  await page.goto("/nested/");
  await expect(page).toHaveTitle("Nested");
});

// 同一テストファイル内で同じページに毎回アクセスする場合は`test.beforeEach`で設定する
test.beforeEach(async ({ page }) => {
  await page.goto("/");
});

テストを実行します。成功すれば下記のように出力されます。

$ npx playwright test

Running 3 tests using 3 workers
  3 passed (3.0s)

To open last HTML report run:

  npx playwright show-report

できるだけ本番環境でやりたい場合

本番環境でやりたい場合は起動前にビルドしてプレビューコマンドを実行します。

ビルドする場合はvite.config.tsにて複数ページ用の設定をする必要があります。

import { resolve } from "path";
import { defineConfig } from "vite";

export default defineConfig({
  build: {
    rollupOptions: {
      input: {
        main: resolve(__dirname, "index.html"),
        sub: resolve(__dirname, "sub.html"),
        nested: resolve(__dirname, "nested/index.html"),
      },
    },
  },
});
  webServer: {
-   command: "npx vite dev --port 3000",
+   command: "npm run build && npx vite preview --port 3000",
    url: "http://localhost:3000",
    reuseExistingServer: !process.env.CI,
  },

まとめ

開発用のサイトを立てるとアクセス制限とかでテストがしづらくなるので、こういう開発用サーバーをテスト前にローカルで起動して実行する方がテストしやすいと思います。E2E テストはただでさえ flaky になりやすいので、失敗のリスク減らせた方がいいです。

Vite には普通に JS をビルドする以外にもこういう使い方あるよっていうアイディアでした。

Discussion