Laravel Dusk 事始め [Snipe-ITを例に]

公開:2020/11/09
更新:2020/11/10
10 min読了の目安(約9200字TECH技術記事

Snipe-IT という知る人ぞ知るIT資産管理ツールがあります。
2020/10/26 に v5.0.4 がリリースされ、継続的にアップデートされているこのツール、Laravel 6 ベースで、ブランチリストを眺めたところ dusk というのがありました。見た感じ、E2Eテストを Codeception から切り替えてみようと試みを始めたのではないかと思いました。
しかしE2Eテストコードがあるはずの tests/Browser ディレクトリは見事にほぼカラでプロジェクト中断?のような感じがしたのですが、かえってこれから Laravel Dusk でCIを整備していくというときの"素振り素材"にはちょうど良さそうに思ったので、その始め方を記事としてまとめてみます。

概要

  • オリジナルのコードを fork して少し調整する
  • docker-compose の準備と起動
  • 初期データの投入
  • Dusk Test の実行

試行環境

  • macOS Catalina 10.15.7
  • Docker Desktop for Mac 2.5.0.0(49427)
    • Engine 19.03.13
    • Compose 1.27.4
  • Snipe-IT v5.0.5-pre build 5457
    • PHP 7.2.24
    • Laravel 6.18.14
    • Dusk 6.8

詳細

オリジナルのコードを fork して少し調整する

以下の手順で Dusk が実行可能となるように調整するのが良さそうです

  1. https://github.com/snipe/snipe-it を fork して dusk ブランチに develop ブランチを取り込む
    • fork は リポジトリ右上の Fork ボタンを押して進めればOK
    • fork後の snipe-it リポジトリを作業ディレクトリに移して、以下の手順でコマンドを実行
      • cd /path/to/snipe-it
      • git switch develop
      • git switch dusk
      • git pull origin develop
    • コンフリクトが起きるので解決する
      • オリジナルの最新 develop コードを採用して Resolved としました
        • 2020/11/8 時点ではコンフリクトは1ファイル [1]
  2. Dockerfile の composer install 時にある --no-dev を無効化
    • Laravel 6.x Laravel Dusk のガイド「インストール」 の Note にある通り、本番環境にDuskをインストールするのは「御法度」で、Dusk を実行するには development モードとするのが適切なので、Dusk 用の Dockerfile.dusk をコピーして作成し以下の箇所を変更
      • Dockerfile.diff
        - RUN composer install --no-dev --working-dir=/var/www/html
        + RUN composer install --working-dir=/var/www/html                
        
  3. tests/DuskTestCase.php の調整
    • 諸説ありますが[2]、おそらく手数として最小な selenium/standalone-chrome イメージを利用する方法を今回は選択するので、それに即した調整です
      • DuskTestCase.php.diff.1 : --no-sandbox オプション追加
          $options = (new ChromeOptions)->addArguments([
              '--disable-gpu',
              '--headless',
              '--window-size=1920,1080',
        +     '--no-sandbox',
          ]);
        
      • DuskTestCase.php.diff.2 : seleniumサーバーのURLを変更 [3]
          return RemoteWebDriver::create(
        -     //'http://localhost:9515',
        +     'http://selenium:4444/wd/hub',
              DesiredCapabilities::chrome()->setCapability(
                  ChromeOptions::CAPABILITY, $options
              )
          );
        

docker-compose の準備と起動

複数のコンテナを用いるので docker-compose で管理するのが良いと思うのですが、オリジナルには存在しないので自分で整備する必要があります

  1. 必要な環境変数を定義する

    • 定義が必要な設定例は以下資料の Mysql,Email,Snipe-IT を参考に env-file example (折りたたみ)のような内容になります(// 以降のコメントは削除してご利用ください)
      https://snipe-it.readme.io/docs/docker#dockers-environment-variables

      env-file example
      # Mysql Parameters
      MYSQL_PORT_3306_TCP_ADDR=snipe-mysql  // 必要。docker-compose.yml に合わせる
      MYSQL_PORT_3306_TCP_PORT=3306         // なくてもOK
      MYSQL_ROOT_PASSWORD=YOUR_SUPER_SECRET_PASSWORD
      MYSQL_DATABASE=snipeit
      MYSQL_USER=snipeit
      MYSQL_PASSWORD=YOUR_snipeit_USER_PASSWORD
          
      # Email Parameters
      MAIL_PORT_587_TCP_ADDR=smtp.whatever.com
      MAIL_PORT_587_TCP_PORT=587
      MAIL_ENV_FROM_ADDR=youremail@yourdomain.com
      MAIL_ENV_FROM_NAME=Your_Full_Email_Name
      MAIL_ENV_ENCRYPTION=tcp
      MAIL_ENV_USERNAME=your_email_username
      MAIL_ENV_PASSWORD=your_email_password
          
      # Snipe-IT Settings
      APP_ENV=development          // production から変更
      APP_DEBUG=true               // false から変更
      APP_KEY=<<Fill in Later!>>   // php artisan key:generate で生成
      APP_URL=http://selenium      // 必要。docker-compose.yml に合わせる
      APP_TIMEZONE=Asia/Tokyo
      APP_LOCALE=en
      
    • オリジナルの Dockerfile に以下の記述があり、
      COPY docker/docker.env /var/www/html/.env
      つまり、Laravelが参照する .env の内容はこの docker.env のものになります(それを踏まえて適宜カスタマイズを行えます)
      また、Laravelが参照する .env と区別する意図で、環境変数を定義したファイルは以降 .env.docker-compose と記述します

  2. docker-compose.yml を準備する

    • 本体(snipe-it)とDB(snipe-mysql)に加えて、selenium service を追加し、docker-compose.yml example (折りたたみ)のような内容になります

      docker-compose.yml example
      
      version: '3'
      
      services:
      
        snipe-mysql:
          container_name: snipe-mysql
          image: mysql:5.6
          env_file:
            - ./.env.docker-compose
          volumes:
            - snipesql-vol:/var/lib/mysql
          command: --default-authentication-plugin=mysql_native_password
          expose:
            - "3306"
      
        snipe-it:
          build:
            context: ./
            dockerfile: Dockerfile.dusk
          env_file:
            - ./.env.docker-compose
          volumes:
            - ./composer.json:/var/www/html/composer.json
            - ./composer.lock:/var/www/html/composer.lock
            - ./storage:/var/www/html/storage
            - ./tests/Browser/screenshots:/var/www/html/tests/Browser/screenshots
          ports:
            - "3051:80"
          depends_on:
            - snipe-mysql
      
        selenium:
          image: selenium/standalone-chrome
          ports:
            - "4444:4444"
      
      volumes:
        snipesql-vol:
      
      
    • snipe-mysql service はおそらく他に見つかる情報とほぼ同様な内容ですが、
      snipe-it service に関しては以下の点が独特です

      • Dockerfile.dusk を build する(--no-devをはずしている)
      • composer.json, composer.lock, storage, screenshots ディレクトリ を volumes でマウントしている
        • これはローカルから変更内容やログやスクリーンショットを参照したいケースがあり得ると想定した設定です
  3. docker-compose を起動する

    • docker-compose up -d で環境一式を起動
    • 変更があったときなどは適宜 docker-compose up -d --build
      • 自分の MacBook Air (macOS Catalina 10.15.7 メモリ 16G) では 6分とちょっとで起動完了
    • docker-compose exec snipe-it bash でコンテナ内に入り操作が行える
      • 必要に応じて以下のようなキャッシュクリアコマンド等を実行できます
        • composer dump-autoload
        • composer clear-cache
        • php artisan view:clear
        • php artisan route:clear
        • php artisan clear-compiled
        • php artisan config:cache
        • php artisan cache:clear

初期データの投入

環境が起動完了したら、初期データの投入を通常であろうセットアップウィザードではなくスクリプトで行います
セットアップウィザードのソースコードをひも解いた結果、次の順に実行するのが妥当そうです

  1. Step 1 : migrate (スキーマ投入)
    php artisan migrate --force
  2. Step 2: Laravel Passport のインストール
    php artisan migrate --path='vendor/laravel/passport/database/migrations' --force
    php artisan passport:install
  3. DatabaseSeeder を流す(4〜5分)
    php artisan db:seed
  4. Faker で作成されたランダムなユーザ名をテストしやすいように admin に変更
    php artisan db:seed --class=AdjustFirstAdminUserSeeder
http://localhost:3051/ をリクエストしてログインするとこんな画面

Dust Test の実行

すでに存在している tests/Browser/ExampleTest.php (タイトルにアプリケーション名が含まれているかを検証)に、以下のログインのテストコードを加えて php artisan dusk 実行してみましょう

ログインのテストコード と 実行時のコンソール出力

    public function test_Login()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                ->type('username', BrowserTestConst::USERNAME)
                ->type('password', BrowserTestConst::PASSWORD)
                ->press('Login')
                ->waitForText('Snipe-IT Demo')
                ->assertSee('Snipe-IT Demo')
                ->assertSee('Dashboard')
                ->assertSee('Recent Activity')
                ->assertSee('Assets by Status')
                ->assertSee('Asset Categories')
                ;
        });
    }

  • 意図的に失敗するように、パスワードを書き換えたり想定されている文字列を変えたりしたうえで変えたファイルをコンテナ側に docker cp で転送し、キャッシュのクリアを行って再度 php artisan dusk を実行すると、失敗時のスクリーンショットが残り、それを確認できます

まとめ・その他

ここまでお付き合いいただきありがとうございます。
Snipe-IT というOSSを題材に、Laravel Dusk を利用した E2E テストを始めてみる例を書きました。
実装コードは https://github.com/sogaoh/snipe-it/tree/dusk に公開しているので自由に参照してください。本記事を書いた時点での Tag も作成してあります。

次回は E2E テスト を整備していくときのコツを紹介できればと考えています。
その次に CI にしていくときのあれこれも書いていこうと思っています。

phpcon2020 で

「Dusk で CI」にまつわる話をします。関心あればご視聴ください。エントリーは こちら からになるようです。

脚注
  1. app/Http/Controllers/Assets/AssetsController.php ↩︎

  2. 「dusk selenium」で検索すると複数のやり方が出てきます ↩︎

  3. https://github.com/php-webdriver/php-webdriver/blob/main/lib/Remote/RemoteWebDriver.php#L88 ↩︎