ユニットテストでカバーできない統合レベルのバグは、本番環境で初めて発覚することがあります。「ローカルでは動いていたのに」という状況を防ぐには、実際のブラウザ操作を伴うE2Eテストが有効です。
本記事では、Viteで構築したTypeScriptアプリケーションにPlaywrightでE2Eテストを導入し、CircleCIで自動実行するまでの手順を紹介します。
E2EテストをCIで自動化する価値
E2Eテストは手動で実行するケースもありますが、CIパイプラインに組み込むことで以下のメリットが得られます。
まず、プルリクエストごとにテストが自動実行されるため、統合レベルのバグを早期に検出できます。「マージしてから気づいた」という事態を防げます。
2つ目としては、テスト失敗時の調査効率が向上します。CircleCIのArtifacts機能を活用すれば、失敗時のスクリーンショットやHTMLレポートをブラウザから即座に確認できます。ローカルでブランチをチェックアウトし、テストを再実行する手間が省けます。
他にも、Test Insights機能により、テストの傾向分析が可能になります。

最も失敗頻度の高いテストや、実行時間の長いテストを可視化できるため、改善の優先順位づけに役立ちます。
環境構成
本記事では以下のツールを使用します。
- Vite(TypeScript)
- Playwright(E2Eテストフレームワーク)
- CircleCI(CI/CDプラットフォーム)
テスト対象は npm create vite@latest で生成したカウンターアプリです。
シンプルな例ですが、実際のプロジェクトにも同様の手順で導入できます。
Playwrightのセットアップ
パッケージのインストール
まずPlaywrightをインストールします。
npm install -D @playwright/test
package.json にテスト実行用のスクリプトを追加します。
{
"scripts": {
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui"
}
}
playwright.config.ts の設定
プロジェクトルートに playwright.config.ts を作成します。ローカル環境とCI環境で設定を分岐させるのがポイントです。
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'],
['junit', { outputFile: 'test-results/junit.xml' }],
]
: '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,
},
});
reporter の設定では、CI環境でJUnit形式のXMLを出力しています。CircleCIの store_test_results はJUnit形式を受け付けるため、この設定がTest Insightsとの連携に必要です。HTMLレポートも同時に出力し、Artifactsとして保存することで詳細な調査が可能になります。
webServer の設定では、テスト実行前にアプリケーションサーバーを自動起動します。CI環境では本番ビルド済みの preview を、ローカルでは開発サーバーを使用します。
screenshot: 'only-on-failure' を設定することで、テスト失敗時のみスクリーンショットが保存されます。問題発生時の調査に役立ちます。
E2Eテストの実装
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();
await expect(counterButton).toHaveText('count is 1');
// さらに3回クリック
await counterButton.click();
await counterButton.click();
await counterButton.click();
await expect(counterButton).toHaveText('count is 4');
});
test('ページの主要要素が表示されている', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { name: /Vite \+ TypeScript/ })).toBeVisible();
const viteLogo = page.locator('img[alt="Vite logo"]');
await expect(viteLogo).toBeVisible();
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();
});
});
テストコードでは getByRole や getByText などのセマンティックセレクターを使用しています。CSSセレクターやXPathに比べて、UIの変更に強く保守性の高いテストが書けます。
CircleCIパイプラインの設定
.circleci/config.yml を作成します。
version: 2.1
orbs:
node: circleci/node@5
jobs:
e2e_test:
docker:
- image: mcr.microsoft.com/playwright:v1.57.0-noble
environment:
CI: true
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
name: Build application
command: npm run build
- run:
name: Run E2E tests
command: npm run test:e2e
- store_test_results:
path: test-results
- store_artifacts:
path: playwright-report
when: always
- store_artifacts:
path: test-results
when: always
workflows:
test_workflow:
jobs:
- e2e_test
CIファイル作成のポイント1: Playwright公式Dockerイメージの使用
mcr.microsoft.com/playwright:v1.57.0-noble はPlaywrightが提供する公式Dockerイメージです。Chromium、Firefox、WebKitのブラウザと、必要なシステム依存関係がすべてインストール済みのため、セットアップ時間を大幅に短縮できます。
CircleCIの公式ドキュメントでは、Playwright v1.34.2以降の使用が推奨されています。それ以前のバージョンでは、Test Insightsと互換性のないJUnit XML形式が出力される可能性があります。
CIファイル作成のポイント2: circleci/node Orbの活用
circleci/node Orb の install-packages コマンドを使用することで、依存関係のインストールとキャッシュ処理が簡潔に記述できます。キャッシュの設定を手動で書く必要がありません。
CIファイル作成のポイント3: store_test_results と store_artifacts
store_test_results はJUnit形式のテスト結果をCircleCIに送信します。この設定により、Test Insightsでテスト傾向の分析やflaky test(不安定なテスト)の検出が可能になります。
store_artifacts はHTMLレポートやスクリーンショットをArtifactsとして保存します。when: always を指定することで、テストの成功・失敗に関わらずレポートが保存されます。
実行結果の確認
設定ファイルをコミットしてGitHubにpushすると、CircleCIでパイプラインが実行されます。
Testsタブでの確認
ジョブ詳細画面の「Tests」タブで、テストの成功・失敗状況を確認できます。

store_test_results で送信したJUnit XMLがここに反映されます。
Artifactsタブでの確認
「Artifacts」タブでは、保存したHTMLレポートやスクリーンショットを確認できます。playwright-report/index.html をクリックすると、Playwrightが生成した詳細なテストレポートがブラウザで表示されます。
各テストのステップごとの実行時間や、失敗時のスクリーンショット、エラーメッセージなどを確認できるため、問題の調査がCircleCI上で完結します。
発展:テスト分割と並列実行
E2Eテストの数が増えてくると、実行時間が課題になります。CircleCIのテスト分割機能を使えば、複数のコンテナでテストを並列実行し、実行時間を短縮できます。
jobs:
e2e_test:
parallelism: 4
docker:
- image: mcr.microsoft.com/playwright:v1.57.0-noble
steps:
# ...
- run:
name: Run E2E tests
command: |
TESTFILES=$(circleci tests glob "e2e/**/*.spec.ts" | circleci tests split --split-by=timings)
npx playwright test $TESTFILES
--split-by=timings を指定すると、過去の実行時間データに基づいて各コンテナの負荷が均等になるようテストが分割されます。
まとめ
本記事では、ViteアプリにPlaywrightでE2Eテストを実装し、CircleCIで自動実行する環境を構築しました。
CIパイプラインでは「問題が起きたときに迅速に原因を調査できる」環境づくりが重要です。store_test_results と store_artifacts を適切に設定することで、テスト失敗時の調査をCircleCI上で完結させることができます。
E2Eテストの数が増えてきたら、テスト分割による並列実行も検討してください。

