手帳

  • 2025-10-01
  • IT系

Webアプリケーション開発において、手動でのブラウザテストは時間がかかり、見落としも発生しやすいものです。この記事では、VPS環境でPuppeteerとヘッドレスChromiumを使って、複数プロジェクトで再利用可能な汎用E2Eテストフレームワークを構築する方法を紹介します。

この記事で学べること

  • VPS環境でのヘッドレスChromiumのセットアップ
  • Puppeteerを使ったブラウザ自動操作
  • 複数プロジェクトで再利用可能なテストフレームワークの設計
  • Laravel/Filament などのWebアプリケーションの自動テスト

環境

  • OS: Ubuntu 22.04 LTS (VPS)
  • Node.js: v23.9.0
  • Puppeteer: v23.0.0
  • Chromium: 140.0.7339.185

アーキテクチャ

/home/user/server/
├── e2e-testing/              # 汎用テストフレームワーク
│   ├── bin/
│   │   └── test-runner.js   # CLIツール
│   ├── lib/
│   │   ├── browser.js       # ブラウザ接続管理
│   │   ├── auth.js          # 認証ヘルパー
│   │   ├── checker.js       # エラーチェック
│   │   └── reporter.js      # レポート生成
│   └── package.json
│
└── domains/example.com/project/
    └── e2e-tests/            # プロジェクト固有設定
        ├── config.json       # テスト設定
        ├── screenshots/      # スクリーンショット
        └── reports/          # テストレポート

Step 1: Chromiumのインストールとセットアップ

Chromiumインストール

# Chromiumブラウザをインストール
sudo apt-get update
sudo apt-get install -y chromium-browser

# バージョン確認
chromium-browser --version
# Chromium 140.0.7339.185 Built on Ubuntu, running on Ubuntu 22.04

日本語フォントのインストール(重要!)

VPS環境のChromiumでは日本語フォントがデフォルトでインストールされていないため、日本語が文字化け(□□□□のような表示)してしまいます。スクリーンショットで日本語を正しく表示するために、日本語フォントをインストールします:

# 日本語フォントをインストール
sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra fonts-ipafont fonts-ipaexfont

インストールされるフォント:

  • fonts-noto-cjk: GoogleのNoto Sans CJK(日中韓統合フォント)
  • fonts-noto-cjk-extra: Noto CJKの追加ウェイト
  • fonts-ipafont: IPA フォント(日本語標準フォント)
  • fonts-ipaexfont: IPA拡張フォント

フォントをインストールした後は、Chromiumを再起動する必要があります:

# Chromiumを停止
pkill -f chromium

# 再起動
chromium-browser \
  --headless \
  --remote-debugging-port=9222 \
  --remote-debugging-address=127.0.0.1 \
  --no-sandbox \
  --disable-gpu &

ヘッドレスモードで起動

VPS環境ではGUIがないため、ヘッドレスモード(画面なし)で起動します:

# ヘッドレスモードで起動(バックグラウンド実行)
chromium-browser \
  --headless \
  --remote-debugging-port=9222 \
  --remote-debugging-address=127.0.0.1 \
  --no-sandbox \
  --disable-gpu &

オプション説明:

  • --headless: GUI不要のヘッドレスモード
  • --remote-debugging-port=9222: DevToolsプロトコル用ポート
  • --remote-debugging-address=127.0.0.1: localhostのみアクセス許可(セキュリティ重要)
  • --no-sandbox: サンドボックス無効化(VPS環境で必要な場合)
  • --disable-gpu: GPU無効化(軽量化)

セキュリティ確認(重要!)

ポート9222がlocalhostのみでリッスンしていることを確認します:

# ポート9222のバインドアドレスを確認
netstat -tlnp | grep 9222
# または
ss -tlnp | grep 9222

正しい設定(安全):

tcp    0    0 127.0.0.1:9222    0.0.0.0:*    LISTEN

127.0.0.1 になっていればOK(localhostからのみアクセス可能)

危険な設定(絶対に避ける):

tcp    0    0 0.0.0.0:9222      0.0.0.0:*    LISTEN

0.0.0.0 は全てのIPアドレスからアクセス可能を意味します

なぜlocalhost制限が必要か?

Chrome DevToolsプロトコル(ポート9222)は、ブラウザへの完全な制御権限を提供します。外部に公開すると:

  • ❌ 誰でもブラウザを操作可能
  • ❌ Cookie/セッションの盗聴
  • ❌ 任意のページへのアクセス
  • ❌ フォーム入力やボタンクリックの実行
  • ❌ スクリーンショットの取得

そのため、必ずlocalhost(127.0.0.1)からのみアクセスできるように制限する必要があります。

Step 2: Node.jsとnpmのセットアップ

nvmでNode.jsインストール

# nvm (Node Version Manager) インストール
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# シェルを再起動 or 以下を実行
source ~/.bashrc

# Node.js最新版をインストール
nvm install node

# バージョン確認
node --version  # v23.9.0
npm --version   # 10.9.2

Step 3: 汎用E2Eテストフレームワークの構築

プロジェクト初期化

# フレームワーク用ディレクトリ作成
mkdir -p ~/server/e2e-testing
cd ~/server/e2e-testing

# package.json作成
npm init -y

依存パッケージインストール

npm install puppeteer-core chalk commander

パッケージ説明:

  • puppeteer-core: Puppeteerのコア機能(Chromium同梱なし)
  • chalk: ターミナル出力の色付け
  • commander: CLIツール作成

コアライブラリ作成 – browser.js

ブラウザ接続管理を行うライブラリを作成します(lib/browser.js):

const puppeteer = require('puppeteer-core');

class BrowserManager {
    constructor(config = {}) {
        this.config = {
            wsEndpoint: config.wsEndpoint || 'ws://127.0.0.1:9222',
            defaultTimeout: config.defaultTimeout || 30000,
            ...config
        };
        this.browser = null;
    }

    // ブラウザに接続
    async connect() {
        this.browser = await puppeteer.connect({
            browserWSEndpoint: this.config.wsEndpoint,
            defaultViewport: { width: 1280, height: 800 }
        });
        return this.browser;
    }

    // 新しいページを作成
    async newPage() {
        const page = await this.browser.newPage();
        page.setDefaultTimeout(this.config.defaultTimeout);

        // エラーログ収集
        page.on('console', msg => {
            if (msg.type() === 'error') {
                console.log('[Console Error]:', msg.text());
            }
        });

        page.on('pageerror', error => {
            console.error('[Page Error]:', error.message);
        });

        return page;
    }

    // ブラウザから切断
    async disconnect() {
        if (this.browser) {
            await this.browser.disconnect();
        }
    }
}

module.exports = BrowserManager;

認証ヘルパー – auth.js

FilamentPHPなどの管理画面へのログイン処理を簡単にするヘルパーを作成します(lib/auth.js):

class AuthHelper {
    constructor(page, config) {
        this.page = page;
        this.config = config;
    }

    // FilamentPHPログイン
    async loginFilament() {
        const { loginUrl, credentials } = this.config;
        const baseUrl = this.config.baseUrl || '';

        await this.page.goto(`${baseUrl}${loginUrl}`, {
            waitUntil: 'networkidle2'
        });

        // メールアドレス入力
        await this.page.waitForSelector('input[type="email"]');
        await this.page.type('input[type="email"]', credentials.email);

        // パスワード入力
        await this.page.type('input[type="password"]', credentials.password);

        // ログインボタンクリック
        await Promise.all([
            this.page.waitForNavigation({ waitUntil: 'networkidle2' }),
            this.page.click('button[type="submit"]')
        ]);

        console.log('✓ ログイン成功');
        return true;
    }
}

module.exports = AuthHelper;

Step 4: プロジェクトでの使用方法

設定ファイルの作成

プロジェクトディレクトリに設定ファイルe2e-tests/config.jsonを作成します:

{
  "baseUrl": "https://example.com",
  "auth": {
    "type": "filament",
    "loginUrl": "/admin/login",
    "credentials": {
      "email": "admin@example.com",
      "password": "password"
    }
  },
  "pages": [
    {
      "name": "Dashboard",
      "url": "/admin",
      "checks": ["noJsErrors", "noServerErrors", "hasContent"]
    },
    {
      "name": "Users",
      "url": "/admin/users",
      "checks": ["noJsErrors", "noServerErrors", "hasContent"]
    }
  ],
  "browser": {
    "wsEndpoint": "ws://127.0.0.1:9222/devtools/browser/...",
    "defaultTimeout": 30000
  },
  "screenshots": {
    "enabled": true,
    "directory": "./e2e-tests/screenshots"
  }
}

テスト実行

# テスト実行
node ~/server/e2e-testing/bin/test-runner.js run

# または、npmスクリプトに追加
npm run e2e-test

トラブルシューティング

WebSocket接続エラー

エラーメッセージ:

✗ ブラウザ接続エラー: Unexpected server response: 404

原因: Chromiumを再起動すると、WebSocketエンドポイントのURLが変わります。

解決方法:

最新のWebSocketエンドポイントURLを取得:

curl -s http://127.0.0.1:9222/json/version | grep webSocketDebuggerUrl

出力されたURLをconfig.jsonbrowser.wsEndpointに設定します。

スクリーンショットで日本語が文字化け

症状: スクリーンショットで日本語が □□□□ のように表示される

原因: VPS環境に日本語フォントがインストールされていない

解決方法:

# 日本語フォントをインストール
sudo apt-get install -y fonts-noto-cjk fonts-noto-cjk-extra fonts-ipafont fonts-ipaexfont

# Chromiumを再起動
pkill -f chromium
chromium-browser --headless --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 --no-sandbox --disable-gpu &

node\r エラー

エラーメッセージ:

/usr/bin/env: 'node\r': そのようなファイルやディレクトリはありません

原因: Windows環境で作成されたファイルのCRLF改行コード

解決方法:

sed -i 's/\r$//' /path/to/test-runner.js

応用例

CI/CDパイプラインに組み込む

# GitHub Actions例
name: E2E Tests

on: [push]

jobs:
  e2e-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '18'
      - name: Install Chromium
        run: sudo apt-get install -y chromium-browser
      - name: Start Chromium
        run: chromium-browser --headless --remote-debugging-port=9222 &
      - name: Run E2E Tests
        run: node ~/server/e2e-testing/bin/test-runner.js run

定期実行 (cron)

# 毎日午前2時にテスト実行
0 2 * * * cd ~/server/domains/example.com/myproject && node ~/server/e2e-testing/bin/test-runner.js run

まとめ

この記事では、VPS環境でPuppeteerとヘッドレスChromiumを使った汎用E2Eテストフレームワークの構築方法を紹介しました。

メリット:

  • ✅ 複数プロジェクトで再利用可能
  • ✅ 設定ファイルベースで簡単カスタマイズ
  • ✅ HTMLレポート・スクリーンショット自動生成
  • ✅ CI/CD統合が容易
  • ✅ 日本語コンテンツにも対応

実装時のポイント:

  • 日本語フォントのインストールは必須
  • Chromium再起動時はWebSocketエンドポイントの更新が必要
  • セッション管理に注意(Cookie/キャッシュのクリア)
  • 改行コードはLF(UNIX形式)に統一

次のステップ:

  • より詳細なエラー検出ロジックの追加
  • Slack/Email通知機能の追加
  • パフォーマンス測定機能の追加
  • 並列実行対応

参考リンク