0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

個人プロジェクトに E2E(Playwright)を自習導入して GitHub Actions に載せた話

Last updated at Posted at 2025-11-05

初投稿です。
個人開発の Web アプリに、E2E テストがゼロの状態から自習で Playwright を導入し、GitHub Actions に最小コストで載せた記録です。
目的は「まず CI で安定して動く最小構成」を作り、のちに拡張できる骨格を用意すること。自分用に知識のアウトプットを意識しています。

想定読者:E2E 未導入 or 失敗しがちな個人/小規模プロジェクトの方
得られること:差分実行・Chromium 限定・CI 専用 .env・早期スキップ・アーティファクト保存の最小セット


環境(ざっくり)

  • CI: GitHub Actions ubuntu-latest
  • E2E: Playwright(JavaScript/TypeScriptどちらでも可)
  • フロント: React(ビルド後を静的サーバで配信 or dev サーバ)
  • バックエンド: 任意(例: Node/Laravel など)
  • Docker: docker compose でフロント/バック/DB/E2E を起動
  • ブラウザ: Chromium のみ(クロスブラウザは後日)

ポイントは 「CI 専用の .env」 を用意すること(下記参照)


ゴール(最小構成の定義)

  1. 差分実行:E2E ディレクトリに変更が無ければ 即スキップ
  2. Chromium 限定:テスト数 × ブラウザ数の乗算を避ける
  3. 早期チェック:重い Docker やビルドの 前に スキップ判定
  4. CI 専用 .env:権限やログ差で落ちない
  5. 失敗時の調査容易化:HTML レポート・スクショ・動画を保存

ディレクトリ例


project-root/
├─ src/frontend/
│  ├─ e2e/
│  │  ├─ auth/
│  │  │  └─ auth.spec.js
│  │  └─ consent/
│  │     └─ sample.spec.js
│  ├─ playwright-ci.config.js
│  └─ ...
├─ src/backend/ ...(任意)
├─ docker-compose.yml (dev/e2e override は任意)
└─ .github/workflows/e2e.yml


Playwright 設定(最小)

// src/frontend/playwright-ci.config.js
const { defineConfig, devices } = require('@playwright/test');

module.exports = defineConfig({
  testDir: './e2e',
  fullyParallel: false,
  timeout: 600000, // 10分
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html'],
    ['junit', { outputFile: 'test-results/junit.xml' }],
    ['list'], // 進捗が分かる
  ],
  use: {
    baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3000',
    storageState: 'playwright/.auth/user.json',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    viewport: { width: 1280, height: 720 },
    actionTimeout: 60000,
    navigationTimeout: 120000,
    waitForLoadState: 'domcontentloaded',
  },
  projects: [
    {
      name: 'setup',
      testMatch: /.*\.setup\.js/,
      use: { storageState: { cookies: [], origins: [] } },
    },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
      dependencies: ['setup'],
    },
  ],
});

認証セットアップ例(storageState の最小)

// src/frontend/e2e/setup/auth.setup.js
const { test, expect } = require('@playwright/test');

test('global auth setup', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.E2E_EMAIL);
  await page.getByLabel('Password').fill(process.env.E2E_PASSWORD);
  await page.getByRole('button', { name: 'Login' }).click();
  await expect(page).toHaveURL(/\/dashboard|\/home/);
  // 必要ならここで storageState を明示保存する実装に変更してOK
});

GitHub Actions(差分実行 × 早期スキップ)

# .github/workflows/e2e.yml
name: E2E Tests
on:
  pull_request:
    branches: [develop, main]
  workflow_dispatch:

jobs:
  e2e:
    runs-on: ubuntu-latest

    steps:
      - name: Check out
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # ← 差分検出に必須

      - name: Check if E2E tests exist
        id: check-e2e
        run: |
          if [ -d "src/frontend/e2e" ] && \
             [ -n "$(find src/frontend/e2e -name '*.spec.*')" ]; then
            echo "exist=true" >> $GITHUB_OUTPUT
            echo "✓ E2E test directory found"
          else
            echo "exist=false" >> $GITHUB_OUTPUT
            echo "⚠️ No E2E test files - skip"
          fi

      - name: Detect changed E2E files
        id: changed
        if: steps.check-e2e.outputs.exist == 'true'
        run: |
          BASE="${{ github.base_ref || 'develop' }}"
          git fetch origin "$BASE":refs/remotes/origin/"$BASE" --no-tags
          MB=$(git merge-base origin/"$BASE" HEAD) || { echo "::error::merge-base failed"; exit 0; }
          CHANGED=$(git diff --name-only "$MB" HEAD -- 'src/frontend/e2e/**/*.spec.*' | sed 's|src/frontend/||g')
          AUTH="e2e/auth/auth.spec.js"
          if [ -z "$CHANGED" ]; then
            echo "run=false" >> $GITHUB_OUTPUT
            echo "files=" >> $GITHUB_OUTPUT
            echo "⚠️ No E2E spec changed - skip heavy steps"
          else
            FILES=$(echo -e "$AUTH\n$CHANGED" | sort -u | tr '\n' ' ')
            echo "run=true" >> $GITHUB_OUTPUT
            echo "files=$FILES" >> $GITHUB_OUTPUT
            echo "Run: $FILES"
          fi

      # ここから重い処理は条件付き
      - name: Start containers (example)
        if: steps.changed.outputs.run == 'true'
        run: |
          docker compose up -d --build
          # フロント起動待ち(200 応答)
          timeout 180 bash -c 'until curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 | grep -q 200; do echo "waiting frontend..."; sleep 5; done'

      - name: Create CI .env for backend (optional)
        if: steps.changed.outputs.run == 'true'
        run: |
          cat > src/backend/.env << 'EOF'
          APP_ENV=test
          APP_DEBUG=true
          LOG_CHANNEL=stderr
          CACHE_DRIVER=array
          SESSION_DRIVER=array
          QUEUE_CONNECTION=sync
          API_VERSION=api/v1
          EOF

      - name: Run Playwright (only changed files + auth)
        if: steps.changed.outputs.run == 'true'
        run: |
          cd src/frontend
          npm ci
          npx playwright install --with-deps
          TESTS="${{ steps.changed.outputs.files }}"
          echo "Running: $TESTS"
          npx playwright test --config=playwright-ci.config.js $TESTS

      - name: Upload E2E artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: e2e-${{ github.run_id }}
          path: |
            src/frontend/playwright-report/
            src/frontend/test-results/
          retention-days: 7

コツCheck if E2E tests existDetect changed E2E files最初に置くと、差分が無い PR は重い処理を全部スキップできます。


CI 専用 .env(例:バックエンド)

CI はファイル権限差で落ちやすいので、ファイル I/O を避ける方針。

# src/backend/.env(CIのみ)
APP_ENV=test
APP_DEBUG=true
LOG_CHANNEL=stderr   # ← ファイルに書かない
CACHE_DRIVER=array
SESSION_DRIVER=array
QUEUE_CONNECTION=sync
API_VERSION=api/v1

ローカル実行(最小手順)

# 1) アプリ起動(Docker/手動どちらでもOK)
docker compose up -d

# 2) フロント/バックが 200 応答になるまで待つ
curl -I http://localhost:3000

# 3) E2E 実行
cd src/frontend
npm ci
npx playwright install --with-deps
npx playwright test --config=playwright-ci.config.js

代表的なつまずき(チェックリスト)

  • 404/タイムアウト:フロントの呼び先と API prefix(例 api/v1)の 一致
  • 権限:CI ではファイルログを避ける(LOG_CHANNEL=stderr
  • 差分検出fetch-depth: 0git merge-basegit diff
  • テスト数の暴増:テーブル駆動 × 複数ブラウザの乗算。CI は Chromium のみ
  • vendor/依存不足:コンテナ起動直後の serve で落ちないよう、起動前に依存解決
  • 待機不足:DB/フロントの起動を 必ず待つ(curl / mysqladmin ping)

実装ハイライト(本稿に含めた主な要素)

  • 差分実行(変更なしで E2E をスキップ)
  • Chromium 限定(最小で高速化)
  • 早期チェック(重い処理の前にスキップ判定)
  • CI 専用 .env(stderr ログ / array ドライバで安定)
  • アーティファクト保存(HTML / スクショ / 動画で原因特定を高速化)

追記候補(必要に応じて):storageState の保存/再利用、wait-for-*.sh の共通化、compose ファイルの重ね順、Lint/Prettier の CI 設定


まとめ・振り返り

  • ゼロ→イチは骨格優先Chromium × 差分実行 × 早期スキップ で最短 “動く” を確保
  • CI とローカルは別設計:CI 専用 .envLOG_CHANNEL=stderr など)で落ちにくくする
  • 履歴が要fetch-depth: 0 で正しい差分検出
  • 見える化で速くなるlist レポーター + アーティファクト保存

一番難しかったところ(所感)

  1. ルーティング不一致と認証の不安定

    • 原因:フロント api/v1 とサーバ v1 の齟齬など
    • 対処:API プレフィックス統一、setup project で認証 state を作って再利用
  2. CI 固有の権限問題

    • 原因:storage/logs/... への書き込みで Permission denied
    • 対処:ファイルに書かないLOG_CHANNEL=stderr
  3. 差分検出の “no merge base”

    • 原因:shallow clone
    • 対処:actions/checkout@v4fetch-depth: 0 を指定

今後の改善(短期)

  • キャッシュ:npm / Docker 層のキャッシュ
  • 優先度制御:重要 spec は常時、それ以外はナイトリー
  • モニタリング:実行時間・失敗率の可視化

参考コマンド(コピペ用)

フロント起動待ち(200 応答待機)

timeout 900 bash -c '
  until [ "$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000)" = "200" ]; do
    echo "waiting frontend...";
    sleep 5;
  done
'

差分に含まれる E2E spec の一覧

BASE=origin/develop
git fetch origin develop:refs/remotes/origin/develop --no-tags
MB=$(git merge-base $BASE HEAD)
git diff --name-only "$MB" HEAD -- 'src/frontend/e2e/**/*.spec.*'

結論(短句)

  • 最短で動かすならChromium × 差分実行 × 早期スキップ
  • 安定させるならstderr ログ × 起動待ち × CI 専用 .env
  • 伸ばすならキャッシュ × 優先度制御 × モニタリング
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?