Laravel Dusk+Dockerでブラウザテスト環境を作る

#経緯
「ｶﾁｶﾁｶﾁｶﾁｶﾁｶﾁｶﾁｶﾁｶﾁ」
静寂な開発オフィスに響き渡るマウスクリック。それはキーボードのタイプ音よりも顕著である。なぜこんなにもクリック音が鳴り響くのか？それは動作チェックをしているためである。テストは正しく動作するか確認するとても重要な作業だ。
しかし、この作業に一体どれだけの時間を費やすことになるのだろうか。もちろん開発規模によるところであるが、エディタでコード記述→ブラウザで動作確認・・・(LOOP)
これは大きな時間を費やすことになるだろう。インターフェイスもキーボードからマウスに切り替えなければならない。煩雑な作業である。
IT化を提供する立場であるにもかかわらず、アナログな作業をしてしまうのはなんとも皮肉である。そして何より「これはエンジニアらしくない」と感じることとなった。
この問題をどうにかできないだろうか？LaravelにはDuskというブラウザテストを効率よく行うことができる機能がある。今回はその導入について挑戦したときのメモである。

https://readouble.com/laravel/7.x/ja/dusk.html

#環境

・Windows
・Laravel 7.30.4
・Docker（nginx1.15.6、php7.4.15、mysql5.7）

かねてよりLaravel開発用のDockerを使っているので、それに加える形での環境更新となる。既に上記のセットで動作している前提となる。mysqlは今回の更新作業に特に関係はしてこない。

#今回の流れ
１.　Dockerにseleniumコンテナを追加
２.　Composerで「Laravel/Dusk」をインストール
３.　LarvelでDusk関係のファイルを作成
４.　動作確認用に「VNC　Viewer」をインストール
５.　テストしてみる


#１.　Dockerにseleniumコンテナを追加

selenium・・・webブラウザでのテストを自動化するツール。DockerHubに公式のイメージがあるのでそれを使う。余談だが、pythonでもこれを介してブラウザ操作ができたりする。

https://www.selenium.dev/history/

```yml:docker-compose.yml
\\追加
 selenium:
    image: selenium/standalone-chrome-debug
    ports:
      - 4444:4444
      - 5900:5900
    depends_on:
      - web
    privileged: true
```

今回はデバッグ（テスト）用なので、下記のDebugging部分を参考にした。
ブラウザ操作をするにはdriverが必要だが、Laravelではデフォルトはchromeになっているので、そのままコンテナもchromeにする。

https://github.com/SeleniumHQ/docker-selenium

#２.　Composerで「Laravel/Dusk」をインストール

次はphpのコンテナでの作業。

```
docker exec -it phpのコンテナ名 bash
```
Laravelプロジェクト配下へ移動し、下記コマンドでLaravelDuskをインストールする。

```
composer require --dev laravel/dusk
```

ここで今回下記のエラーが出現。phpの拡張機能としてzipが有効になっていない旨の内容。
実は以前php7.4に更新したのだがzipが有効になっていなかったようである。

```
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - laravel/dusk[v6.13.0, ..., 6.x-dev] require ext-zip * -> it is missing fro
m your system. Install or enable PHP's zip extension.
    - Root composer.json requires laravel/dusk ^6.13 -> satisfiable by laravel/d
usk[v6.13.0, 6.x-dev].

To enable extensions, verify that they are enabled in your .ini files:
    -
    - /usr/local/etc/php/conf.d/docker-php-ext-mysqli.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-pdo_pgsql.ini
    - /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini
You can also run `php --ini` inside terminal to see which files are used by PHP
in CLI mode.

Installation failed, reverting ./composer.json and ./composer.lock to their orig
inal content.
```
下記コマンドでzip有無を確認するも有効でないようである。
一旦コンテナ内での作業は中止でDockerfileを修正する。

```
php --ri zip

Extension 'zip' not present.
```
phpコンテナ用のDockerfileも更新する。RUN apt-getの際、インストールするパッケージを追加した。
zlib1g-dev、libzip-dev、docker-php-ext-install zip
の3つを追加。

```dockerfile:Dockerfile
RUN apt-get update \
&& apt-get install -y \
git \
zlib1g-dev \
libzip-dev \
zip \
unzip \
vim \
&& docker-php-ext-install zip 
```
変更後コンテナコンテナを再構築する。

```
docker-compose build
```

再度コンテナ内に入り、zipの再確認。無事有効になっている。

```
 php --ri zip

zip

Zip => enabled
Zip version => 1.15.6
Libzip headers version => 1.5.1
Libzip library version => 1.5.1
```

再度LaravelDuskをインストール。無事インストール完了。

```
composer require --dev laravel/dusk
```

#３.　LarvelでDusk関係のファイルを作成

引き続きphpコンテナでの作業。Laravelプロジェクト配下で書きのコマンドでDusk関係のファイルを作成する。

```
 php artisan dusk:install
```
無事完了するとtestsディレクトリにBrowserディレクトリとDuskTestCase.phpが作成される。

#４.　動作確認用に「VNC　Viewer」をインストール

せっかくなのでテスト実行中の様子を確認したい。したがってVNC Viewerを使ってコンテナ内で実行されるテストの様子を確認できるようにする。ちなみにテストの実行自体はViewerなくてもできる。
下記からインストール。

https://www.realvnc.com/en/connect/download/viewer/

インストールしたらDockerで指定したlocalhost:5900で接続。passwordを要求されるが、公式通りデフォルトは「secret」でログインできる。
![VNC Viewer 2021_03_06 18_12_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659747/7a1229a8-b7e9-72ee-c90c-fc0e10c0f64f.png)

ログインできて下記のような画面がでればOK。
![localhost_5900 (111074b15212_99.0) - VNC Viewer 2021_03_06 14_54_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659747/9f932b5a-ede8-b085-2f1d-fca60497ac1c.png)

#５.　テストしてみる

Dusk作成時にサンプルテストが出来上がっている。HomeにアクセスしたらLaravelのWelcomeが画面が表示されるかというシンプルなテスト。

```php:ExampleTest.php
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    /**
     * A basic browser test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                    ->assertSee('Laravel');
        });
    }
}

```

下記のコマンドでテスト実行。

```
php artisan dusk
```
ところが下記のようなエラーになった。どうやらルートURLの設定がうまくできていないようである。
ちなみにエラーはtests/Browser/screenshotsにエラー時点のスクショができる。そして無事にテスト追加すると自動で削除される。

```
PHPUnit 8.5.14 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 1.56 seconds, Memory: 18.00 MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: net::ERR_CONNECTION_REFU
SED
  (Session info: chrome=88.0.4324.96)

/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Exception/WebDriverException.p
hp:139
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php
:371
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php:604
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/Remote/RemoteExecuteMethod.php
:27
/var/www/html/app/laravel/vendor/php-webdriver/webdriver/lib/WebDriverNavigation.php:41
/var/www/html/app/laravel/vendor/laravel/dusk/src/Browser.php:153
/var/www/html/app/laravel/tests/Browser/ExampleTest.php:19
/var/www/html/app/laravel/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:68
/var/www/html/app/laravel/tests/Browser/ExampleTest.php:21

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.
```

![failure-Tests_Browser_ExampleTest_testBasicExample-0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/659747/bf6000be-dbd0-f316-b933-85c92d7d44d3.png)

baseUrl()をオーバーライドするとことで対応。webはDockerのnginxコンテナの名前のことである。

```php:DuskTestCase.php
<?php

namespace Tests;

use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;

abstract class DuskTestCase extends BaseTestCase
{
    use CreatesApplication;

    //追加
    protected function baseUrl()
    {
        return 'http://web';
    }

    /**
     * Prepare for Dusk test execution.
     *
     * @beforeClass
     * @return void
     */
    public static function prepare()
    {
        if (! static::runningInSail()) {
            static::startChromeDriver();
        }
    }

    /**
     * Create the RemoteWebDriver instance.
     *
     * @return \Facebook\WebDriver\Remote\RemoteWebDriver
     */
    protected function driver()
    {
        $options = (new ChromeOptions)->addArguments(collect([
            '--window-size=1920,1080',
        ])->unless($this->hasHeadlessDisabled(), function ($items) {
            return $items->merge([
                '--disable-gpu',
                '--headless',
            ]);
        })->all());

        return RemoteWebDriver::create(
            'http://selenium:4444/wd/hub', DesiredCapabilities::chrome()
        );
        // return RemoteWebDriver::create(
        //     $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
        //     DesiredCapabilities::chrome()->setCapability(
        //         ChromeOptions::CAPABILITY, $options
        //     )
        // );
    }

    /**
     * Determine whether the Dusk command has disabled headless mode.
     *
     * @return bool
     */
    protected function hasHeadlessDisabled()
    {
        return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
               isset($_ENV['DUSK_HEADLESS_DISABLED']);
    }
}

```

再度テスト実行。

```
 php artisan dusk

PHPUnit 8.5.14 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.09 seconds, Memory: 18.00 MB

OK (1 test, 1 assertion)
```

無事通過！これで一通りテスト環境が準備できた。公式リファレンスには様々なチェック項目があるのでテストが捗りそう。いろいろ試して行こうと思います。