Laravel DUSKで既存プロジェクトにブラウザテストを追加する

# はじめに
これは[エキサイト Advent Calender 2018](https://qiita.com/advent-calendar/2018/excite) の記事です。

新卒一年目のbei1998です。初投稿なためお見苦しい点などございますが、何卒ご容赦願います。

今回は既存のLaravelのプロジェクトでテストを書く際、せっかくなのでDUSKを使ってみようというということでDUSKでブラウザテストを書いてみました。

やっている時にハマったポイントが結構あったので、導入するまでの奮闘記を書きました。



# DUSKとは
DUSKとは、Laravel5.4から導入されたブラウザテスト用のライブラリです。
DUSKはデフォルトでChromeDriverが付属しており、GoogleChromeで自動的にテストが実行されます。
ヘッドレスブラウザを使ったテストなため、自身のLaravelで開発しているサイト以外でもテストを行うことができます。


# 導入
### 環境
ubuntu 16.04.1
php 7.2.11
Laravel 5.5
### 準備
基本的には[こちら](https://readouble.com/laravel/5.5/ja/dusk.html)の公式ドキュメントを参考に導入をしました。
今回はLaravel5.5で導入をします。

#### インストール

最新版はLravel5.5に対応していないため、バージョンを指定してインストールします。

```
$ composer require --dev laravel/dusk:2.*
```

#### サンプルコードのインストール

```
$ php artisan dusk:install
```

※本番環境にDuskをインストールしてしまうと、アプリケーションに対する未認証でのアクセスを許すようになるため絶対に本番環境にインストールしてはいけない。

インストールが済むとtestsフォルダ以下にDuskTestCase.phpとBrowserフォルダが作成されます。
Browserフォルダ以下には、サンプルのテストファイルであるExampleTest.phpや、Screenshotフォルダやテスト用ページが置かれているPagesフォルダなどがあります。
今回は既に作成してあるページに対してテストを行うため、Pagesフォルダ以下は使用しません。


# テストの実行

まずテストがちゃんと実行できるかが知りたいので、ブラウザテストを走らせてみます。
Laravelプロジェクト上で下記のコマンドを叩くとLaravelプロジェクト/tests/Browserの配下にあるテストファイルが全て実行されます。
今回はインストール後は何も弄っていないため、デフォルトで入っているExampleTestのみが実行されます。
#### ブラウザテスト実行コマンド

```
$ php artisan dusk
```

```ExampleTest.php
 <?php 

 namespace Tests\Browser;
 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');
         });
     }
 }
```

#### ExamleTest実行結果

```
PHPUnit 6.5.9 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 277 ms, Memory: 12.00MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"desiredCapabilities":{"browserName":"chrome","platform":"ANY"}}

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

失敗。
エラー内容を色々調べてみるとChromeDriverの権限がなかったり、Chromeが使用する9515ポートが競合していることが原因らしいのでまずは権限を与えてみます。

```
$ cd vendor/laravel/dusk/bin; chmod 775 *
```
以上のコマンドで権限は与えられた筈なのですが特に変化はなし、ポートも競合していなかったので何が問題なのか行き詰まる。

まず手動でChromeDriverを実行できるか試してみることに。
ChromeDriverの場所を探してみて、/vendor/laravel/dusk/bin以下に発見。

#### ChromeDriver実行
```
$ ./chromedriver-linux 
```

#### ChromeDriver実行結果
```
./chromedriver-linux: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory
```

libnss3.soがないって怒られた。
libnss3.soにはlibnss3-devというものが必要で、libasoundパッケージも必要らしいのでインストール。

```
$ sudo apt install libnss3-dev
$ sudo apt-get install libasound2-data  libasound2 xdg-utils
```
参考: [GoogleColaboratoryにSeleniumの実行環境を用意する（失敗）](https://qiita.com/taikomegane/items/58184ea359788ff32147)
#### 再度ChromeDriver実行
```
PHPUnit 6.5.9 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 284 ms, Memory: 12.00MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownServerException: unknown error: cannot find Chrome binary
  (Driver info: chromedriver=2.35.528139 (47ead77cb35ad2a9a83248b292151462a66cd881),platform=Linux 4.4.0-53-generic x86_64)
```

エラー内容が変わりました。
cannot find Chrome binaryとありますがDuskをインストール時に確かにChromeDriverをDuskの下にインストールした筈・・・。
どうやらサーバー側にChromeがないから見つからないと怒られているっぽい。以下のコマンドでサーバーにChromeをインストール。

```
$ sudo wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
$ sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
$ sudo apt-get update
$ sudo apt-get install google-chrome-stable
```

参考: [Cloud9 で Laravel（v5.5.0） をインストールして Dusk を動かす方法](https://dog-ears.net/laravel/a170914/)

#### Chromeを入れて再度テスト実行
```
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.

E                                                                   1 / 1 (100%)

Time: 313 ms, Memory: 12.00MB

There was 1 error:

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\WebDriverException: JSON decoding of remote response failed.
Error code: 4
The response: '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<meta type="copyright" content="Copyright (C) 1996-2015 The Squid Software Foundation and contributors">
<meta http-equiv="Content-Type" CONTENT="text/html; charset=utf-8">
<title>ERROR: The requested URL could not be retrieved</title>
<style type="text/css"><!-- 
 /*
 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */
```

エラーは変わりましたが、Chrome自体はきちんと動いているようでscreenshotsフォルダを見ると真っ白な画像が作られてました。
```<title>ERROR: The requested URL could not be retrieved</title>```とある通り、アクセスができていない・・・。

DUSKはdump()でブラウザの結果をdumpして返してくれるのでサンプルテストを以下のように変更。

```ExampleTest.php
<?php

namespace Tests\Browser;

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

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

```
#### dump実行結果
```
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.

"<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>"
```
空っぽ・・・。
DUSKは.env内のAPP_URLを見て接続先を決めているので.envを開いて確認。

```
APP_URL=http://user.project.jp
```

ブラウザ上では正しくアクセスできているのになぜ・・・？
DUSKのAPP_URLはブラウザ上で見れるURLを指定するだけでよいのでは・・・。

自分の環境ではなく、サーバー上からChromeでアクセスできていればOKということに気づく。
試しにCurlを叩いてみる。

```
curl: (6) Could not resolve host:http://user.project.jp
```

そんなホストしらねーよと怒られました。
はい、名前解決ができていませんでした。

サーバーのhostsを弄りちゃんと名前解決できるように設定。

#### テスト実行

```
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.

"""
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja"><head><title>ああああああ</title>\n
```

titleが取得できているので無事成功しているようです。お疲れ様でした！
あとは公式ドキュメントを見てテストをどんどん書いていくだけです！

# Screenshotの文字化けを直す
DUSKはtestに失敗した場合screenshotを取ってくれますが、日本語が文字化けしてしまいます。

```
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.

F                                                                   1 / 1 (100%)

Time: 472 ms, Memory: 12.00MB

There was 1 failure:

1) Tests\Browser\UZRTest::testBasicExample
Did not see expected text [倉本] within element [body].
Failed asserting that false is true.

/var/home/user/project/vendor/laravel/dusk/src/Concerns/MakesAssertions.php:348
/var/home/user/project/vendor/laravel/vendor/laravel/dusk/src/Concerns/MakesAssertions.php:319
/var/home/user/project/vendor/laravel/ests/Browser/UZRTest.php:20
/var/home/user/project/vendor/laravel/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:67
/var/home/user/project/vendor/laravel/tests/Browser/UZRTest.php:21

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

```
#### 上記の失敗したテストに対する結果
|![](https://qiita-image-store.s3.amazonaws.com/0/312115/ce0a74d5-f74a-544a-2140-34c18ac1c925.png)|
|:-:|


これを直すにはseleniumに日本語フォントを入れてやればOKです。

参考 
[HomesteadでLaravel Dusk動かす](http://sickhack.hateblo.jp/entry/2017/06/27/160452) 
[Laravel Dusk メモ](https://www.lancard.com/blog/2017/12/13/laravel-dusk-%E3%83%A1%E3%83%A2/)

#### フォント導入後
|![](https://qiita-image-store.s3.amazonaws.com/0/312115/4cc31095-f24e-f481-4255-a0df5639b08b.png)|
|:-:|

スクショを見ると「倉本」という文字が見つからないためエラーになっているので、UZR.blade.phpの「アレ」を「倉本」に書き換えて再度テスト実行。

#### テスト実行結果

```
PHPUnit 6.5.13 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 565 ms, Memory: 12.00MB

OK (1 test, 1 assertion)
```

無事成功しました。また、screenshotメソッドを使って以下のように書き直すとテストが成功した場合でもテスト結果のスクショが作られます。

```UZRTest.php
class UZRTest extends DuskTestCase
{
    /**
     * A basic browser test example.
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/UZR')
                ->assertSee('倉本')
                ->screenshot("kurara");;
        });
    }
}
```
#### kurara.png
|![](https://qiita-image-store.s3.amazonaws.com/0/312115/408a23fb-288d-5564-a43a-b878385f90b2.png)|
|:-:|

テスト成功時でもきちんとscreenshotsフォルダ以下にスクリーンショットが作成されました。

# 試しにテストを書いてみる
|![](https://qiita-image-store.s3.amazonaws.com/0/312115/22601f63-57c0-90ba-efca-a3a632858937.png)|
|:-:|

こんな感じのページがあります。
ラジオボタンを選択して押すと対応した名前の成績ページに飛ぶようになっています。
このページに対して、「ロマック」を選択するとちゃんとロマックのページに飛ぶかテストを書いてみます。

```gaijinTest.php
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/spot')
                ->radio('url', 'http://user.seiseki/kusogaijin/ロマック')
                ->screenshot('roma');
        });
```
DUSKのradioメソッドは完全一致するセレクタが見つからない場合にnameとvalue属性に一致するラジオボタンを探してくれます。
この場合、fieldがurlでvalueが `http://user.seiseki/kusogaijin/ロマック`　のラジオボタンを押します。

## roma.png
|![](https://qiita-image-store.s3.amazonaws.com/0/312115/3c18cf3d-6ab0-9e9f-8802-7d344681bc3c.png)|
|:-:|
生成されたスクリーンショットを見るとちゃんと選択されていることがわかります。
次に選択後、きちんと遷移先のページに移動してそのページ内にロマックのフルネームが存在するかテストしてみます。

```gaijinTest.php
    public function testBasicExample()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/spot')
                ->radio('url', 'http://user.seiseki/kusogaijin/ロマック')
                ->press('移動')
                ->assertSee('ジェイミー・ロマック')
                ->screenshot('2016-roma');
        });
```

## 2016-roma.png
|![](https://qiita-image-store.s3.amazonaws.com/0/312115/dfe9d679-affb-22ce-8bfa-c975c0b73733.png)|
|:-:|
テストが成功し、無事に成績ページに飛んでいることがわかりました。
このように実際にブラウザを自動で動かして、テストを行ってくれるので非常に便利で書きやすいです。


# おわりに
今回は導入がメインで、あまりテストには触れられませんでしたが、
マウスオーバーや添付ファイルのテスト用メソッドなど多様な操作を行うことができます。
詳細につきましては[公式ドキュメント](https://laravel.com/docs/5.5/dusk)をご参照ください。

既にLaravelを導入している場合は非常に簡単にブラウザテストを書くことができるため、その場合は是非DUSKを導入してみることをおすすめします。