Laravel DuskをWerckerで実行する

## 2018-04-12 追記

出来る限りシンプルな構成で Laravel Dusk x Wercker x Headless Chrome のサンプルを新たに作ってみました。

### Dockerイメージ

#### Docker Hub

https://hub.docker.com/r/madworks/php-testing/

#### GitHub

https://github.com/mamor/docker-php-testing/commit/cb645b089ace6799bfa0f280c6780453e1059d7b

### Laravel Dusk サンプル

https://github.com/mamor/laravel-dusk-wercker-example/commit/a42c6f661738d0fe3b99beb29a24ba21ab3e261f

### Wercker実行結果

https://app.wercker.com/madworks/laravel-dusk-wercker-example/runs/build/5acf5fc4dfd4d2000184c3b6?step=5acf5ff696f3f300018e8a28

尚、以前のWercker実行結果は誤って削除してしまいました orz

## 概要

Laravel DuskをWerckerで動かすためのDockerイメージを作成しました。このイメージはDocker Hubで公開してあります。

* [Laravel Dusk](https://laravel.com/docs/master/dusk)
* [Wercker](http://www.wercker.com/)
* [Docker Hub](https://hub.docker.com/r/madworks/php-ci/)

Werckerで複雑な設定無しに実行できることを意識したので、必要なものが一式揃っているDockerイメージです。主に次の通りです。(シンプルな設定を保ちつつ合理的に分割できるならそうしたい。。。)

* PHP 7.1.2
* PHP-FPM
* Nginx
* MariaDB
* webdriver-manager (for Selenium Server)
* PhantomJS
* Supervisor
* Capistrano

備忘録として、具体的な設定を書いたサンプルプロジェクトもGitHubに置いてあります。
https://github.com/mamor/dusk-on-wercker-example

この記事では、上記のサンプルプロジェクトを用いた実行方法と、躓いたポイント等を説明します。

## 実行方法

### ダウンロード

```shell-session
$ git clone https://github.com/mamor/dusk-on-wercker-example.git
$ cd dusk-on-wercker-example/
$ yarn install # or "npm install"
```

### ローカルでの実行方法

docker-composeを用います。

```shell-session
$ docker-compose run --rm testing bash -i -c "gulp --gulpfile=/var/app/gulpfile.js ci:testing:all"
```
これでdusk(と、ついでにphpunit)が実行できます。"ci:testing:all"の箇所を"ci:testing:phpunit"または"ci:testing:dusk"にすると、それぞれ単独でも実行できます。gulpを用いている理由は、phpunitとduskを並列で実行するためです。

docker-compose.ymlは次の通りです。
https://github.com/mamor/dusk-on-wercker-example/blob/master/docker-compose.yml

```yaml
# PHPUnit
#   docker-compose run --rm testing bash -i -c "gulp --gulpfile=/var/app/gulpfile.js ci:testing:phpunit"
# Laravel Dusk
#   docker-compose run --rm testing bash -i -c "gulp --gulpfile=/var/app/gulpfile.js ci:testing:dusk"
# Both
#   docker-compose run --rm testing bash -i -c "gulp --gulpfile=/var/app/gulpfile.js ci:testing:all"

version: "2"
services:
  testing:
    image: madworks/php-ci
    command: tail -f /dev/null
    volumes:
      - ~/.composer/cache:/root/.composer/cache
      - ./:/var/app
      - .env.ci.app:/var/app/.env
      - /var/app/vendor
```

### Werckerでの実行方法

予め、Wercker側で"build"と"deploy"という名前のpipelineを用意します。"build"は最初からあるので、実質的には"deploy"を新規作成するだけでした。また、workflowで"build"の成功時に"deploy"を実行するように設定しておきます。

wercker.ymlは次の通りです。
https://github.com/mamor/dusk-on-wercker-example/blob/master/wercker.yml

```yaml
box: madworks/php-ci
build:
  steps:
    - script:
      name: build
      code: |
        yarn config set cache-folder ${WERCKER_CACHE_DIR}/yarn
        source /root/.phpbrew/bashrc
        cp -r ./ /var/app
        cp .env.ci.app /var/app/.env
        yarn install --prefer-offline --production
        gulp ci:testing:all

deploy:
  steps:
    - script:
      name: deploy
      code: |
        which cap
```

"ci:testing:all"の箇所は、docker-compose.ymlと同様です。"deploy"の内容は、capコマンドのパスを出力するだけになっています。実際にはCapistranoの設定や実行コマンドを書く想定です。もちろん、Deployerでも構いません。

実行結果は次の通りです。

* [build](https://app.wercker.com/madworks/dusk-on-wercker-example/runs/build/58a90efbf717a60100ba38a7)
* [deploy](https://app.wercker.com/madworks/dusk-on-wercker-example/runs/deploy/58a90fa3f717a60100ba38cd)

以上が、実行方法です。ローカルとWerckerとで異なるのは、ファイルの設置方法だけで、後はgulpで共通化しています。次に、前述したgulpのタスク"ci:testing:all"の流れを説明します。細かなタスクの内容は、ソースをご覧下さい。

### gulp ci:testing:all

```js
gulp.task('ci:testing:all', (callback) => {
  runSequence(
    ['ci:supervisord', 'ci:copy'],     // 1
    ['ci:php'],                        // 2
    ['ci:mysql'],                      // 3
    ['ci:migrate'],                    // 4
    ['ci:run:dusk', 'ci:run:phpunit'], // 5
    callback);
});
```

1. "ci:supervisord"でwebdriver-manager,nginx,php-fpm,mariadbを起動。"ci:copy"でduskが実行される側(nginx配下 = /var/www)のファイルを配置
1. "ci:php"でduskを実行する側(/var/app)と実行される側(/var/www)の準備
1. "ci:mysql"でデータベースの準備
1. "ci:migrate"でマイグレーションを実行
1.  "ci:run:dusk"でduskを実行。"ci:run:phpunit"でphpunitを実行

となります。"/var/app"はdev用ライブラリ込みでcomposer install、"/var/www"は--no-devでcomposer installしています。また、このテストではDBを用いていませんが、疎通確認を兼ねてマイグレーションを実行しています。gulpを使うと並列で良い処理、直列であるべき処理の管理が楽ですね。

## 躓いたポイントや問題点

### "TTY mode requires /dev/tty to be read/writable."エラー

**追記: この問題は https://github.com/laravel/dusk/pull/226 で解決されました。**

dusk実行時に、次と同じ内容のエラーが出ました。
https://laracasts.com/discuss/channels/testing/tty-mode-requires-devtty-to-be-writable

次のissuesを真似して"script"コマンドにすると、確かに実行はできるのですが、テスト失敗時もWerckerのbuildが継続してしまいました。終了コードが伝播されない感じでしょうか。
https://github.com/wercker/wercker/issues/95

この問題を解決するために、とりあえず"DuskCommand"を拡張しました。実は、前述のサンプルプロジェクトでは、duskを"php artisan app:dusk"として実行しています。
https://github.com/mamor/dusk-on-wercker-example/commit/ac0666fa7d470f08fbf02fc162b4415f583ea8ff#diff-2188cf5d4ddf4333bd48355426788696

やりたかったことは"setTty(false)"です。オリジナルでは、Windowsの場合にfalse、それ以外の場合にtrueとなっていて、まだ、その意図をよく理解できていません。一応、PRは出しておきました。 https://github.com/laravel/dusk/pull/136

そして、--no-dev時にduskをインストールしたくなかったので"app/Console/Kernel.php"におけるコマンド登録が次のようになっています。
https://github.com/mamor/dusk-on-wercker-example/commit/ac0666fa7d470f08fbf02fc162b4415f583ea8ff#diff-dc9276478c4a31a6639a7c904c29ac04

### ChromeとPhantomJSの切り分け

実用を考えると、普段はChromeで動かし、CI環境等ではPhantomJSで動かしたかったので、DuskTestCase.phpを次のようにしました。
https://github.com/mamor/dusk-on-wercker-example/commit/ac0666fa7d470f08fbf02fc162b4415f583ea8ff#diff-3d758536f1d68e823ad5ec04b8921a1e

以上となります。