Vite アプリに E2E テストを実装し、CircleCI で自動実行するまで

Vite アプリに E2E テストを実装し、CircleCI で自動実行するまで

Vite で構築した TypeScript アプリケーションに E2E テストを導入し、CircleCI で自動実行する環境を構築しました。本記事では、Playwright を使用した E2E テストの実装から、CircleCI での CI/CD パイプライン構築までの手順を紹介します。

利用したフレームワークなど

今回のプロジェクトでは、以下のツールを利用しています。

  • Vite
  • TypeScript
  • Playwright

テスト対象のアプリは、npm create viteでセットアップした直後のカウンターアプリを利用します。

1. Playwright テストのセットアップ

まずはPlaywrightによるE2Eテストを設定しましょう。最初にパッケージのインストールを行います。

npm install -D @playwright/test

続いてpackage.json にスクリプトを追加しました。

{
  "scripts": {
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui"
  }
}

playwright.config.ts を作成し、ローカル環境と CI 環境それぞれにテスト設定を入れましょう。テスト実行結果の出力方式を分岐しています。これはCircleCIではJUnit形式でレポートを作成する必要があるためです。

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  
  // CI環境では複数のレポーターを使用
  reporter: process.env.CI
    ? [
        ['html'], // HTMLレポート(詳細なレポート用)
        ['junit', { outputFile: 'test-results/junit.xml' }], // JUnit形式(CircleCI Test Insights用)
      ]
    : 'html',
  
  use: {
    baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },

  projects: [
        {
          name: 'chromium',
          use: { ...devices['Desktop Chrome'] },
        },
      ],

  // 開発サーバーの自動起動
  webServer: {
    command: process.env.CI ? 'npm run preview' : 'npm run dev',
    url: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173',
    reuseExistingServer: !process.env.CI,
    timeout: 120 * 1000,
  },
});

また、webServer を設定しています。開発サーバーを自動起動することで、テスト実行前の手動起動が不要になるわけです。

2. E2E テストの実装

Playwrightをセットアップしたので、テストを実装しましょう。e2e/counter.spec.ts を作成し、カウンター機能のテストを実装します。

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

test.describe('Counter', () => {
  test('カウンターが正しく動作する', async ({ page }) => {
    await page.goto('/');

    // ページタイトルを確認
    await expect(page).toHaveTitle(/first-cci-app/);

    // カウンターボタンを取得
    const counterButton = page.getByRole('button', { name: /count is/i });

    // 初期値が0であることを確認
    await expect(counterButton).toHaveText('count is 0');

    // カウンターをクリック
    await counterButton.click();

    // カウントが1に増えたことを確認
    await expect(counterButton).toHaveText('count is 1');

    // さらに3回クリック
    await counterButton.click();
    await counterButton.click();
    await counterButton.click();

    // カウントが4に増えたことを確認
    await expect(counterButton).toHaveText('count is 4');
  });

  test('ページの主要要素が表示されている', async ({ page }) => {
    await page.goto('/');

    // ヘッダーが表示されていることを確認
    await expect(page.getByRole('heading', { name: /Vite \+ TypeScript/ })).toBeVisible();

    // Viteロゴが表示されていることを確認
    const viteLogo = page.locator('img[alt="Vite logo"]');
    await expect(viteLogo).toBeVisible();

    // TypeScriptロゴが表示されていることを確認
    const tsLogo = page.locator('img[alt="TypeScript logo"]');
    await expect(tsLogo).toBeVisible();

    // カウンターボタンが表示されていることを確認
    const counterButton = page.getByRole('button', { name: /count is/i });
    await expect(counterButton).toBeVisible();
  });
});

テストコードでは、getByRolegetByText などのセマンティックセレクターを積極的に使用しました。これにより保守性の高いテストを書くことができます。各ステップで何を確認しているかを明確にするため、アサーションには具体的な説明コメントを付けています。また、各テストが独立して実行できるよう設計することで、テストの実行順序に依存しない堅牢な構成になっているといえるでしょう。

3. ローカルでのテスト実行

テストをセットアップしたので、ローカルで実行してみましょう。初回のみ、Playwright のブラウザをインストールします。

npx playwright install chromium

テストは以下のコマンドで実行できます。

# 通常のテスト実行
npm run test:e2e

# UIモードでテスト実行(デバッグに便利)
npm run test:e2e:ui

# CI環境をシミュレート
CI=true npm run test:e2e

4. CircleCI の設定

テストコードを実装したので、CircleCIで自動テストを実行できるようにします。.circleci/config.yml を作成し、E2E テストを実行するワークフローを定義します。

version: 2.1
orbs:
  node: circleci/node@5.0.2

jobs:

  e2e_test:
    docker:
      - image: mcr.microsoft.com/playwright:v1.57.0-noble
    environment:
      CI: true
    steps:
      - checkout
      - node/install-packages:
          pkg-manager: npm
      - attach_workspace:
          at: ~/project
      - run:
          name: Run E2E tests
          command: npm run test:e2e
      - store_test_results:
          path: test-results
          when: always
      - store_artifacts:
          path: playwright-report
          when: always
      - store_artifacts:
          path: test-results
          when: always

workflows:
  test_workflow:
    jobs:
      - e2e_test

この設定では、circleci/node orb を活用することで設定を簡潔に保っています。このOrbでできることについては、別の記事を用意していますので、こちらもご覧ください。なお、E2Eテストについては、Playwright用のDocker Imageである mcr.microsoft.com/playwright:v1.57.0-noble を使用しています。これはブラウザ関連のシステム依存関係が事前にインストールされているため、セットアップ時間を短縮できます。

ワークスペースを活用して、build_app ジョブの成果物を e2e_test ジョブで再利用する構成にしました。レポートの保存では、store_test_results を使用して CircleCI の Test Insights で可視化できるようにしています。store_artifacts により HTML レポートとスクリーンショットを保存し、when: always を指定することで成功時も失敗時もレポートが保存されるわけです。

最後に.gitignoreで自動生成されるファイルを Git から除外しておきましょう。

# Playwright
test-results/
playwright-report/
.playwright/

5. CircleCI で実行結果をチェックする

設定ファイルも作成できましたので、CircleCIでテストを実行してみましょう。すべてのコードをGitにコミットし、GitHubリポジトリへpushしておきましょう。あとはCircleCIのパイプラインが完了することを待ちましょう。ジョブが成功している場合は、このような画面が表示されるはずです。

store_test_results でテスト実行結果を保存しているので、テスト詳細データについては、Artifactsタブでチェックできます。

index.htmlをクリックすると、Playwrightが作成したテスト詳細レポートページが表示されます。

テストごとの詳細も、テスト名をクリックすると以下のようにstepごとにチェックできます。

もしE2EテストがFailしている場合は、どのアサーションが失敗したかなどのレポートが表示されます。スクリーンショットなども閲覧できる様子ですので、テストが失敗した時の調査などをCircleCI上で一通り行えると言えそうです。

まとめ

本記事では、Vite アプリに Playwright を使用した E2E テストを実装し、CircleCI で自動実行する環境を構築しました。

CircleCIには、テスト結果やスクリーンショットなどを保存し、ブラウザから閲覧できるようにする Artifact 機能が用意されています。この機能を活用するためには、Playwrightでテスト結果をJUnit形式で出力させたり、store_artifactsでテスト結果を保存するなどの設定を入れるようにしましょう。

CI パイプラインは、「問題が起きた時に迅速に発生した問題や原因について調査できるようにする」環境づくりが重要です。このステップを省略すると、CI実行結果やローカルでの対象ブランチのチェックアウトとテスト再実行、エラーログの特定などの手作業が発生し、差別化につながらない重労働につながります。

CIサービスを活用して、「問題発生時にやるべきこと」を軽減させましょう。

参考リンク

Hidetaka Okamoto profile photo

Hidetaka Okamoto

ビジネスデベロップメント

DigitalCubeのBizDev。EC ASPの開発やStripeのDeveloper Advocateとしての経験を元に、SaaSやECサイトの収益を増やすための方法・生成AIを使った効率化や新しい事業モデルの模索などに挑戦する。