【保存版】Laravel Duskで実現する最強の自動テスト環境構築ガイド 2024年度版

Laravel Duskとは? 実践で使える基礎知識

ブラウザテスト自動化の画期的なツール

Laravel Duskは、Laravelアプリケーションのブラウザテストを簡単かつ強力に実現するテスティングフレームワークです。従来の自動テストでは難しかったJavaScriptを使用するページや動的なUIのテストを、直感的なAPI設計により効率的に実行できます。

主な特徴:

  • ChromeDriverを利用した実ブラウザテスト
  • Laravel標準の優れた開発者体験の継承
  • JavaScriptフレームワークとの親和性
  • ページオブジェクトモデルのサポート
  • CIツールとの容易な統合
// Duskの基本的なテストケース例
class ExampleTest extends DuskTestCase
{
    public function testBasicExample()
    {
        // ブラウザ操作をシミュレート
        $this->browse(function (Browser $browser) {
            $browser->visit('/')                    // トップページにアクセス
                   ->assertSee('Welcome')           // 'Welcome'テキストの存在確認
                   ->clickLink('Login')             // ログインリンクをクリック
                   ->waitForPath('/login')          // ページ遷移を待機
                   ->type('email', 'test@test.com') // メールアドレスを入力
                   ->type('password', 'password')   // パスワードを入力
                   ->press('Login')                 // ログインボタンをクリック
                   ->assertPathIs('/home');         // リダイレクト先を確認
        });
    }
}

従来のテストツールと比較した際の強み

Laravel Duskと他のテストツールを比較することで、その特徴がより明確になります:

機能Laravel DuskPHPUnitSeleniumCypress
設定の容易さ
Laravel統合
JavaScript対応×
実行速度
デバッグ機能
学習コスト

特筆すべき利点:

  1. Laravel環境との完全な統合
  2. 直感的なAPI設計による低い学習コスト
  3. 充実したデバッグ機能とスクリーンショット機能
  4. 安定した非同期処理のハンドリング

Duskが解決する3つの開発課題

  1. 複雑なユーザーインタラクションのテスト自動化
  • SPAやJavaScriptを多用するページのテスト
  • ドラッグ&ドロップなどの高度なUI操作
  • 非同期処理を含むフォーム送信
// 複雑なユーザーインタラクションのテスト例
$browser->dragDown('.sortable-item')     // ドラッグ操作
        ->dropDown('.target-zone')       // ドロップ操作
        ->waitFor('.success-message')    // 非同期処理の完了待ち
        ->assertSee('Item moved');       // 結果の確認
  1. 環境依存の問題解決
  • 開発環境間の差異を吸収
  • CI環境での安定した実行
  • クロスブラウザテストの簡略化
// 環境に依存しない待機処理の実装例
$browser->whenAvailable('.dynamic-content', function ($element) {
    $element->assertSee('Loading Complete')
           ->click('.action-button');
}, 10);
  1. テストメンテナンスの効率化
  • ページオブジェクトパターンによる再利用性の向上
  • 明確なエラーメッセージによるデバッグの効率化
  • 豊富なヘルパーメソッドによるコード量の削減
// ページオブジェクトパターンの実装例
class LoginPage extends Page
{
    public function login($email, $password)
    {
        return $this->type('@email', $email)     // @emailはセレクタ
                    ->type('@password', $password)
                    ->press('@submit');
    }
}

// テストでの利用
$browser->visit(new LoginPage)
        ->login('test@test.com', 'password')
        ->assertPathIs('/home');

このように、Laravel Duskは単なるテストツールではなく、開発者の生産性を大きく向上させる包括的なテスティングソリューションとして機能します。次のセクションでは、この強力なツールの具体的な導入手順について解説していきます。

環境構築から始めるLaravel Dusk導入手順

完璧な開発環境のセットアップ方法

Laravel Duskの環境構築は、適切な手順に従えば短時間で完了できます。以下に、確実な導入手順を解説します。

  1. Composerを使用したDuskのインストール
# Duskパッケージのインストール
composer require --dev laravel/dusk

# Duskのインストール
php artisan dusk:install
  1. 環境設定ファイルの調整
// .env ファイルの設定
APP_URL=http://localhost:8000  // 実際の開発環境URLに合わせる
DUSK_DRIVER_URL="http://localhost:9515"

// config/database.php での設定(テスト用DBの指定)
'connections' => [
    'sqlite_testing' => [
        'driver' => 'sqlite',
        'database' => ':memory:',
        'prefix' => '',
    ],
],
  1. テスト用データベースの設定
// DuskTestCase.php でのDB設定
use Illuminate\Support\Facades\DB;

public function setUp(): void
{
    parent::setUp();
    DB::connection('sqlite_testing')->reconnect();
}
  1. 基本的なディレクトリ構造の確認
tests/
├── Browser/                 # Duskテストファイル
│   ├── Pages/              # ページオブジェクト
│   └── Components/         # 再利用可能なコンポーネント
├── CreatesApplication.php
└── DuskTestCase.php        # Duskの基本設定

ChromeDriver の正しい設定とトラブル回避

ChromeDriverの適切な設定は、安定したテスト実行の鍵となります。

  1. バージョン管理の自動化
// DuskTestCase.php での ChromeDriver バージョン自動管理
public static function prepare()
{
    if (! static::runningInSail()) {
        // インストール済みのChromeバージョンに合わせて自動的にDriverをダウンロード
        static::startChromeDriver(['--port=9515']);
    }
}

// カスタムオプションの追加
protected function driver()
{
    $options = (new ChromeOptions)->addArguments([
        '--disable-gpu',
        '--headless',          // ヘッドレスモード
        '--window-size=1920,1080',
        '--no-sandbox',
        '--disable-dev-shm-usage',
    ]);

    return RemoteWebDriver::create(
        $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
        DesiredCapabilities::chrome()->setCapability(
            ChromeOptions::CAPABILITY, $options
        )
    );
}
  1. 一般的なトラブルと解決策
問題原因解決策
Connection refusedポート番号の不一致DUSK_DRIVER_URLの確認
Chrome version errorバージョンの不一致php artisan dusk:chrome-driver の実行
Permission denied権限の問題スクリーンショットディレクトリの権限確認
Blank page errorJavaScript実行の問題waitForの適切な使用

テスト実行環境の確認と動作検証

実装したテストが確実に動作することを確認するための手順です。

  1. 基本的な動作確認テスト
// tests/Browser/ExampleTest.php
public function testBasicExample()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
                ->assertSee('Laravel')
                ->screenshot('home_page');  // スクリーンショットの保存
    });
}
  1. テスト実行コマンドとオプション
# 全テストの実行
php artisan dusk

# 特定のテストの実行
php artisan dusk tests/Browser/ExampleTest.php

# 特定のメソッドのみ実行
php artisan dusk --filter testBasicExample
  1. テスト実行の自動化設定
# .github/workflows/dusk.yml の例
name: Dusk Tests
on: [push, pull_request]
jobs:
  dusk:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - name: Install Dependencies
        run: composer install
      - name: Upgrade Chrome Driver
        run: php artisan dusk:chrome-driver
      - name: Start Chrome Driver
        run: ./vendor/laravel/dusk/bin/chromedriver-linux &
      - name: Run Laravel Server
        run: php artisan serve &
      - name: Run Dusk Tests
        env:
          APP_URL: "http://127.0.0.1:8000"
        run: php artisan dusk
  1. テスト結果の確認ポイント
  • スクリーンショットの確認(tests/Browser/screenshots/
  • コンソールログの確認(tests/Browser/console/
  • 失敗したテストの詳細レポート
  • テスト実行時間とパフォーマンス

設定が完了したら、以下のチェックリストで最終確認を行います:

✅ ChromeDriverが正しく起動している
✅ テストデータベースが正しく設定されている
✅ スクリーンショットが保存できる
✅ CIパイプラインでテストが実行できる
✅ エラーハンドリングが適切に機能する

これらの設定と確認を丁寧に行うことで、安定した自動テスト環境を構築することができます。次のセクションでは、この環境を活用した実践的なテストコードの作成方法について解説していきます。

実践的なテストレールの作成テクニック

効率的なセレクター設計の極意

効率的なセレクター設計は、メンテナンス性の高いテストコードを作成する上で重要な要素です。以下に、実践的なアプローチを解説します。

  1. データ属性を活用したセレクター設計
// テンプレート側の実装
<button data-testid="submit-button">送信</button>
<input data-testid="email-input" type="email" />

// Duskテストでの使用
$browser->click('@submit-button')        // @記法でdata-testid属性を参照
        ->type('@email-input', 'test@example.com');
  1. セレクターの優先順位
class UserRegistrationTest extends DuskTestCase
{
    // 推奨度の高い順
    $browser->select('[data-testid="role-select"]')     // 1. データ属性
            ->click('#submit-button')                    // 2. ID
            ->assertVisible('.error-message')            // 3. クラス
            ->assertSee('Success');                      // 4. テキスト内容
}
  1. ページオブジェクトでのセレクター管理
// Pages/UserRegistration.php
class UserRegistration extends Page
{
    // セレクターを一元管理
    public $selector = [
        '@emailInput' => '[data-testid="email-input"]',
        '@passwordInput' => '[data-testid="password-input"]',
        '@submitButton' => '[data-testid="submit-button"]',
    ];

    // メソッドでカプセル化
    public function register($email, $password)
    {
        return $this->type('@emailInput', $email)
                    ->type('@passwordInput', $password)
                    ->click('@submitButton');
    }
}

安定性の高いテストコードの書き方

テストの信頼性を高めるためのベストプラクティスを紹介します。

  1. 適切な待機処理の実装
class ProductSearchTest extends DuskTestCase
{
    public function testSearchResults()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/products')
                    ->type('@searchInput', 'テスト商品')
                    // 明示的な待機処理
                    ->waitFor('@searchResults')
                    // 条件付き待機
                    ->waitUntil('!$.active')
                    // カスタム条件での待機
                    ->waitUntilMissing('.loading-spinner')
                    ->assertSee('検索結果');
        });
    }
}
  1. テストの独立性確保
class OrderProcessTest extends DuskTestCase
{
    public function setUp(): void
    {
        parent::setUp();
        // テスト前の状態リセット
        $this->artisan('migrate:fresh');
        $this->seed(TestDataSeeder::class);
    }

    protected function tearDown(): void
    {
        // テスト後のクリーンアップ
        $this->artisan('cache:clear');
        parent::tearDown();
    }
}
  1. アサーションの適切な組み合わせ
public function testUserDashboard()
{
    $this->browse(function (Browser $browser) {
        $browser->loginAs(User::factory()->create())
                ->visit('/dashboard')
                // 複数の条件を確認
                ->assertPresent('@userProfile')
                ->assertSeeIn('@userName', 'John Doe')
                ->assertVisible('@settingsButton')
                // 否定のアサーション
                ->assertMissing('.error-message')
                // カスタムアサーション
                ->assertScript(
                    "return document.querySelector('.loading').style.display",
                    'none'
                );
    });
}

非同期処理を考慮したテスト実装

モダンなWebアプリケーションでは、非同期処理のテストが重要です。

  1. Ajaxリクエストのテスト
class AsyncOperationTest extends DuskTestCase
{
    public function testAsyncDataLoading()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/async-page')
                    ->click('@loadDataButton')
                    // Ajax完了を待機
                    ->waitFor('@dataContainer')
                    // カスタムJavaScript条件での待機
                    ->waitUntil('return !!document.querySelector(".data-loaded")')
                    // 非同期データの検証
                    ->assertScript(
                        'return document.querySelectorAll(".data-item").length',
                        5
                    );
        });
    }
}
  1. WebSocketテストの実装
class RealtimeUpdateTest extends DuskTestCase
{
    public function testWebSocketUpdates()
    {
        $this->browse(function (Browser $first, Browser $second) {
            // 複数ブラウザでのテスト
            $first->visit('/chat')
                  ->waitFor('@chatRoom');

            $second->visit('/chat')
                   ->waitFor('@chatRoom')
                   ->type('@messageInput', 'Hello')
                   ->click('@sendButton');

            $first->waitForText('Hello')
                  ->assertSee('Hello');
        });
    }
}
  1. 非同期バリデーションのテスト
public function testAsyncValidation()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/registration')
                ->type('@email', 'test@example.com')
                // フォーカスアウトイベントの発火
                ->click('@password')
                // バリデーション完了の待機
                ->waitForText('このメールアドレスは既に使用されています')
                // エラーメッセージの検証
                ->assertPresent('@email-error')
                // 送信ボタンの状態確認
                ->assertDisabled('@submitButton');
    });
}

これらのテクニックを組み合わせることで、信頼性の高いテストスイートを構築することができます。次のセクションでは、これらの基礎的なテクニックを応用した、より実践的なテストパターンについて解説していきます。

現場で使えるDuskテスト実装パターン集

フォーム送信とバリデーションテストの実装例

実務で頻出するフォーム処理のテストパターンを解説します。

  1. 複雑なフォームバリデーションのテスト
class ComplexFormTest extends DuskTestCase
{
    public function testMultiStepFormValidation()
    {
        $this->browse(function (Browser $browser) {
            // マルチステップフォームのテスト
            $browser->visit('/register')
                   ->within(new StepOneComponent, function ($browser) {
                        $browser->fillPersonalInfo('John', 'Doe', 'test@example.com')
                               ->nextStep();
                   })
                   ->within(new StepTwoComponent, function ($browser) {
                        $browser->fillAddressInfo('Tokyo', '1-1-1', '100-0001')
                               ->nextStep();
                   })
                   ->within(new StepThreeComponent, function ($browser) {
                        $browser->fillPaymentInfo('4111111111111111', '12/25', '123')
                               ->submit();
                   })
                   ->waitFor('@success-message')
                   ->assertPathIs('/registration-complete');
        });
    }
}

// フォームコンポーネントの実装
class StepOneComponent extends Component
{
    public function fillPersonalInfo($firstName, $lastName, $email)
    {
        return $this->type('@firstName', $firstName)
                    ->type('@lastName', $lastName)
                    ->type('@email', $email)
                    ->assertValid(['@firstName', '@lastName', '@email']);
    }

    // カスタムアサーション
    public function assertValid($fields)
    {
        foreach ($fields as $field) {
            $this->assertMissing("{$field}-error");
        }
        return $this;
    }
}
  1. 動的バリデーションのテスト
class DynamicValidationTest extends DuskTestCase
{
    public function testRealtimeValidation()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/contact')
                   // Eメールのリアルタイムバリデーション
                   ->type('@email', 'invalid-email')
                   ->waitFor('@email-error')
                   ->assertSee('有効なメールアドレスを入力してください')

                   // 正しい値の入力
                   ->type('@email', 'valid@example.com')
                   ->waitUntilMissing('@email-error')

                   // パスワード強度のチェック
                   ->type('@password', 'weak')
                   ->waitFor('@password-strength')
                   ->assertSeeIn('@password-strength', '弱いパスワード')

                   // 確認用パスワードの一致チェック
                   ->type('@password', 'StrongPass123!')
                   ->type('@password-confirmation', 'StrongPass123')
                   ->waitFor('@password-mismatch')
                   ->assertSee('パスワードが一致しません');
        });
    }
}

JavaScript要素を含むUIのテスト方法

モダンなUIコンポーネントのテストパターンを紹介します。

  1. モーダルウィンドウのテスト
class ModalInteractionTest extends DuskTestCase
{
    public function testModalOperations()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/dashboard')
                   // モーダルを開く
                   ->click('@open-modal')
                   ->waitFor('@modal-content')

                   // モーダル内の操作
                   ->within('@modal-content', function ($modal) {
                       $modal->type('@title', '新規プロジェクト')
                            ->type('@description', 'プロジェクトの説明')
                            ->attach('@file-upload', storage_path('tests/files/test.pdf'))
                            ->click('@submit');
                   })

                   // モーダルの閉じる処理
                   ->waitUntilMissing('@modal-content')
                   ->assertSee('プロジェクトが作成されました');
        });
    }
}
  1. ドラッグ&ドロップ操作のテスト
class DragDropTest extends DuskTestCase
{
    public function testKanbanBoardInteraction()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/kanban')
                   ->waitFor('@task-card')
                   // ドラッグ&ドロップ操作
                   ->dragRight('@task-card', 300)
                   // 移動後の状態確認
                   ->assertPresent('@done-column .task-card')
                   // データベースの状態確認
                   ->assertDatabaseHas('tasks', [
                       'id' => 1,
                       'status' => 'done'
                   ]);
        });
    }

    public function testSortableList()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/tasks')
                   ->waitFor('@task-list')
                   // 並び替え操作
                   ->dragDown('@task-1', 100)
                   // 順序の確認
                   ->assertSeeIn('@task-position-1', 'Task 2')
                   ->assertSeeIn('@task-position-2', 'Task 1');
        });
    }
}

認証・認可機能のテスト実行

セキュリティ関連機能のテストパターンを解説します。

  1. 複雑な認証フローのテスト
class AuthenticationTest extends DuskTestCase
{
    public function testTwoFactorAuthentication()
    {
        $this->browse(function (Browser $browser) {
            // 2段階認証のテスト
            $browser->visit('/login')
                   ->type('@email', 'test@example.com')
                   ->type('@password', 'password')
                   ->press('Login')
                   ->waitFor('@2fa-input')
                   // 2FAコードの入力
                   ->type('@2fa-code', $this->generate2FACode())
                   ->press('Verify')
                   ->assertPathIs('/dashboard');
        });
    }

    public function testSocialLoginIntegration()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                   // ソーシャルログインボタンのクリック
                   ->click('@google-login')
                   // ポップアップウィンドウの処理
                   ->waitFor('@google-auth-popup')
                   ->within('@google-auth-popup', function ($popup) {
                       $popup->type('@email', 'test@gmail.com')
                            ->type('@password', 'google-password')
                            ->press('Sign in');
                   })
                   // リダイレクト後の確認
                   ->waitForPath('/dashboard')
                   ->assertAuthenticated();
        });
    }
}
  1. 権限管理のテスト
class AuthorizationTest extends DuskTestCase
{
    public function testRoleBasedAccess()
    {
        $this->browse(function (Browser $browser) {
            // 管理者権限のテスト
            $browser->loginAs(User::factory()->admin()->create())
                   ->visit('/admin/settings')
                   ->assertSee('システム設定')
                   ->assertPresent('@user-management')

                   // 一般ユーザー権限のテスト
                   ->loginAs(User::factory()->create())
                   ->visit('/admin/settings')
                   ->assertSee('アクセス権限がありません');
        });
    }

    public function testDynamicPermissions()
    {
        $this->browse(function (Browser $browser) {
            $user = User::factory()->create();
            $browser->loginAs($user)
                   ->visit('/projects')
                   // 権限の動的な変更
                   ->within('@project-card', function ($project) {
                       $project->assertMissing('@edit-button')
                              ->assertMissing('@delete-button');
                   })
                   // 権限付与後の確認
                   ->tap(function () use ($user) {
                       $user->grantPermission('project.edit');
                   })
                   ->refresh()
                   ->within('@project-card', function ($project) {
                       $project->assertVisible('@edit-button')
                              ->assertMissing('@delete-button');
                   });
        });
    }
}

これらのパターンを理解し、適切に組み合わせることで、実践的な自動テストを効率的に実装できます。次のセクションでは、これらのテストをより効率的に実行するためのパフォーマンス最適化テクニックについて解説します。

Laravel Duskのパフォーマンス最適化

テスト実行時間を50%短縮する設定技法

Laravel Duskのテスト実行時間を大幅に短縮するための具体的な最適化手法を解説します。

  1. データベース最適化
class DuskTestCase extends BaseTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // インメモリデータベースの使用
        $this->app['config']->set('database.default', 'sqlite');
        $this->app['config']->set('database.connections.sqlite', [
            'driver' => 'sqlite',
            'database' => ':memory:',
            'prefix' => '',
        ]);

        // データベーストランザクションの活用
        $this->beginDatabaseTransaction();
    }

    // トランザクション管理の実装
    protected function beginDatabaseTransaction()
    {
        $database = $this->app->make('db');
        foreach ($this->connectionsToTransact() as $name) {
            $connection = $database->connection($name);
            $connection->beginTransaction();
            $this->transactions[] = $name;
        }
    }
}
  1. ブラウザインスタンスの最適化
class OptimizedDuskTestCase extends DuskTestCase
{
    protected function driver(): RemoteWebDriver
    {
        $options = (new ChromeOptions)->addArguments([
            '--disable-gpu',
            '--headless',
            '--no-sandbox',
            '--disable-dev-shm-usage',
            '--disable-extensions',        // 拡張機能の無効化
            '--disable-logging',           // ログ出力の最小化
            '--disable-notifications',     // 通知の無効化
            '--window-size=1920,1080'
        ]);

        // DNSプリフェッチの最適化
        $options->setExperimentalOption('prefs', [
            'dns_prefetching_enabled' => false
        ]);

        return RemoteWebDriver::create(
            $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
            DesiredCapabilities::chrome()->setCapability(
                ChromeOptions::CAPABILITY,
                $options
            )
        );
    }
}
  1. 待機時間の最適化
// config/dusk.php
return [
    'waits' => [
        'ajax' => 5,        // Ajax待機のデフォルト値
        'element' => 3,     // 要素待機のデフォルト値
        'script' => 3       // スクリプト実行待機のデフォルト値
    ]
];

// テストでの使用例
public function testOptimizedWaits()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/dashboard')
                ->waitUsing(3, 100, function () use ($browser) {
                    return $browser->element('.loaded') !== null;
                })
                ->assertSee('Dashboard');
    });
}

パラレルテスト実行による効率化戦略

テストの並列実行を実現するための実装方法を解説します。

  1. シャーディング設定
// tests/DuskTestCase.php
class DuskTestCase extends BaseTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // シャード識別子の取得
        $shardId = getenv('TEST_SHARD_ID') ?: 0;
        $totalShards = getenv('TEST_TOTAL_SHARDS') ?: 1;

        // テストデータベースのシャーディング
        $this->app['config']->set('database.connections.sqlite.database',
            database_path("testing_shard_{$shardId}.sqlite")
        );
    }
}

// parallel-testing.sh
#!/bin/bash
TOTAL_SHARDS=4

for ((i=0; i<$TOTAL_SHARDS; i++))
do
    TEST_SHARD_ID=$i TEST_TOTAL_SHARDS=$TOTAL_SHARDS \
    php artisan dusk --group=shard$i &
done
wait
  1. テストグループの分割
class UserTest extends DuskTestCase
{
    /**
     * @group shard0
     */
    public function testUserRegistration()
    {
        // テストコード
    }
}

class ProductTest extends DuskTestCase
{
    /**
     * @group shard1
     */
    public function testProductListing()
    {
        // テストコード
    }
}

CIパイプラインでの効果的な統合方法

CI/CD環境での効率的なテスト実行方法を解説します。

  1. GitHub Actionsでの実装
# .github/workflows/dusk.yml
name: Laravel Dusk Tests
on: [push, pull_request]

jobs:
  dusk-php:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php: [8.1, 8.2]

    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: testing
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

    steps:
    - uses: actions/checkout@v2

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php }}
        extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, mysql
        coverage: none

    - name: Install Dependencies
      run: |
        composer install --prefer-dist --no-interaction
        npm install
        npm run build

    - name: Setup Laravel
      run: |
        cp .env.example .env
        php artisan key:generate
        chmod -R 777 storage bootstrap/cache

    - name: Upgrade Chrome Driver
      run: php artisan dusk:chrome-driver `/opt/google/chrome/chrome --version | cut -d " " -f3 | cut -d "." -f1`

    - name: Start Chrome Driver
      run: ./vendor/laravel/dusk/bin/chromedriver-linux &

    - name: Run Laravel Server
      run: php artisan serve --no-reload &

    - name: Run Dusk Tests
      env:
        APP_URL: "http://127.0.0.1:8000"
      run: php artisan dusk
  1. 最適化されたDockerfile
# Dockerfile.dusk
FROM php:8.2-cli

# 必要なパッケージのインストール
RUN apt-get update && apt-get install -y \
    chromium \
    chromium-driver \
    zip \
    unzip \
    && rm -rf /var/lib/apt/lists/*

# PHP拡張のインストール
RUN docker-php-ext-install pdo_mysql pcntl

# Composerのインストール
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# アプリケーションのセットアップ
WORKDIR /app
COPY . /app

# 権限の設定
RUN chmod -R 777 /app/storage /app/bootstrap/cache

# テスト実行用のエントリーポイント
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
  1. キャッシュ戦略の実装
# GitHub Actionsでのキャッシュ設定
- name: Cache Dependencies
  uses: actions/cache@v2
  with:
    path: |
      vendor
      node_modules
      ~/.composer/cache
    key: ${{ runner.os }}-deps-${{ hashFiles('**/composer.lock') }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-deps-

これらの最適化技法を適切に組み合わせることで、テスト実行時間を大幅に短縮し、CI/CDパイプラインの効率を向上させることができます。次のセクションでは、実際の開発現場で直面する問題の解決方法について解説します。

現実で解決する問題解決ガイド

環境依存の不安定なテストを改善する方法

実際の開発現場でよく遭遇する環境依存の問題と、その具体的な解決方法を解説します。

  1. タイミング依存の問題解決
class TimingDependentTest extends DuskTestCase
{
    public function testAsyncOperation()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/dashboard')
                   // 問題のある実装
                   // ->pause(2000)  // ← 固定待機は避ける

                   // 改善された実装
                   ->waitUsing(10, 100, function () use ($browser) {
                       return $browser->script(
                           "return document.readyState === 'complete' && !document.querySelector('.loading')"
                       )[0];
                   })
                   // バックグラウンドジョブの完了を待機
                   ->waitUntil('!window.processing');
        });
    }

    // カスタム待機条件の実装
    public function waitForJobCompletion($browser)
    {
        return $browser->waitUsing(30, 1000, function () {
            return Cache::has('job_completed');
        });
    }
}
  1. クロスブラウザ互換性の確保
class BrowserCompatibilityTest extends DuskTestCase
{
    protected function getDriverOptions()
    {
        $options = [];

        switch (env('DUSK_BROWSER', 'chrome')) {
            case 'firefox':
                $options = DesiredCapabilities::firefox();
                break;
            case 'safari':
                $options = DesiredCapabilities::safari();
                break;
            default:
                $options = (new ChromeOptions())->addArguments([
                    '--window-size=1920,1080',
                    '--disable-gpu'
                ]);
        }

        return $options;
    }

    public function testCrossBrowserFeature()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/feature')
                   // ブラウザ固有の処理
                   ->when($this->isFirefox(), function ($browser) {
                       $browser->pause(500); // Firefoxでの追加待機
                   })
                   // 共通のアサーション
                   ->assertVisible('@content');
        });
    }
}

デバッグ効率を上げるベストプラクティス

効率的なデバッグ手法とトラブルシューティング方法を解説します。

  1. 高度なデバッグ手法の実装
class EnhancedDebugging extends DuskTestCase
{
    public function testWithDetailedLogging()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/complex-page')
                   // コンソールログの取得
                   ->tap(function ($browser) {
                       $logs = $browser->driver->manage()->getLog('browser');
                       Log::debug('Browser Console Logs:', $logs);
                   })
                   // DOMスナップショットの取得
                   ->tap(function ($browser) {
                       $html = $browser->driver->getPageSource();
                       Storage::put('debug/page_snapshot.html', $html);
                   })
                   // JavaScript状態の確認
                   ->tap(function ($browser) {
                       $state = $browser->script('return window.appState');
                       Log::debug('Application State:', $state);
                   });
        });
    }

    // カスタムデバッグメソッド
    protected function debugElement($browser, $selector)
    {
        $element = $browser->element($selector);
        $data = [
            'visible' => $element->isDisplayed(),
            'enabled' => $element->isEnabled(),
            'position' => $element->getLocation(),
            'size' => $element->getSize(),
            'classes' => $browser->script(
                "return document.querySelector('{$selector}').classList.toString()"
            )[0]
        ];
        Log::debug("Element Debug [{$selector}]:", $data);
        return $data;
    }
}
  1. エラー情報の詳細収集
trait EnhancedErrorHandling
{
    protected function onFailure($e)
    {
        // スクリーンショットの保存
        $timestamp = now()->format('Y-m-d_H-i-s');
        $this->driver->takeScreenshot(
            storage_path("failures/screenshot_{$timestamp}.png")
        );

        // DOMスナップショットの保存
        file_put_contents(
            storage_path("failures/dom_{$timestamp}.html"),
            $this->driver->getPageSource()
        );

        // ブラウザログの保存
        $logs = collect($this->driver->manage()->getLog('browser'))
            ->map(function ($log) {
                return "[{$log['level']}] {$log['message']}";
            })
            ->implode("\n");

        file_put_contents(
            storage_path("failures/browser_log_{$timestamp}.txt"),
            $logs
        );

        parent::onFailure($e);
    }
}

本番環境に向けたセキュリティ対策

実運用を見据えたセキュリティ対策と注意点を解説します。

  1. 機密情報の安全な取り扱い
class SecureTestingSetup extends DuskTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        // 機密情報の安全な取り扱い
        $this->app['config']->set('services.stripe.key', 'test_key');

        // テスト用の機密情報管理
        $this->withoutEncryption();
    }

    protected function tearDown(): void
    {
        // テスト後のクリーンアップ
        $this->cleanupSensitiveData();
        parent::tearDown();
    }

    private function cleanupSensitiveData()
    {
        // テストで使用した機密情報の削除
        Storage::delete('test_credentials.json');
        Cache::tags(['sensitive'])->flush();
    }
}
  1. セキュアなテスト環境の構築
class SecureEnvironmentSetup
{
    public static function configure()
    {
        return [
            // セキュアなCookie設定
            'session' => [
                'secure' => true,
                'http_only' => true,
                'same_site' => 'strict',
            ],

            // CSRFトークン検証の有効化
            'middleware' => ['web', 'verify_csrf_token'],

            // テスト用ユーザーの権限制限
            'auth' => [
                'defaults' => [
                    'guard' => 'web'
                ],
                'guards' => [
                    'web' => [
                        'driver' => 'session',
                        'provider' => 'test_users'
                    ]
                ]
            ]
        ];
    }
}

// テストでの使用例
class SecureFeatureTest extends DuskTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->app['config']->set(SecureEnvironmentSetup::configure());
    }

    public function testSecureOperation()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/secure-page')
                   // CSRFトークンの検証
                   ->waitFor('meta[name="csrf-token"]')
                   // セキュアなフォーム送信
                   ->type('@password', 'secret')
                   ->press('Submit')
                   // レスポンスヘッダーの確認
                   ->tap(function ($browser) {
                       $headers = $browser->driver->executeScript(
                           'return window.performance.getEntries()[0].responseHeaders'
                       );
                       $this->assertContains('X-Frame-Options: DENY', $headers);
                   });
        });
    }
}

これらの問題解決手法と対策を適切に実装することで、より安定した本番環境でのテスト実行が可能になります。また、問題が発生した際の迅速な原因特定と解決にも役立ちます。