AVAをブラウザ上で実行するAvaronを作った

この記事は[「WACUL Advent Calendar 2017」](http://qiita.com/advent-calendar/2017/wacul)の20日目です。
WACULでフロントエンドエンジニアをしている@bokuwebと申します。

## はじめに

みなさんはお気に入りのテスト構成はあるでしょうか。よく耳にするのは`jest`、`AVA`、`karma`を用いた構成でしょうか。個人的には最近は`AVA`を使用することが多いのですが、その際の不満を解消するためにモジュールを作った話しを書きたいと思います。

## つくったもの

https://github.com/bokuweb/avaron

## AVAとは

[avajs/ava: Futuristic JavaScript test runner](https://github.com/avajs/ava)

紹介記事は他にも色々ありますので多くは書きませんが、sindresorhus氏作の、曰く未来型のテストランナーです。若干古いですが以下に日本語での記述もあり、`なぜAVAなのか?`に特徴が記載してあります。

https://github.com/avajs/ava-docs/tree/master/ja_JP

例えば`Promise`を使用したテストは以下のように記述できます。同様に`Observable`もサポートしています。

```javascript:samples.spec.js
import test from 'ava';

test('Promise test', async t => {
    const result = await somePromise();
    t.is(result, 'Hogeeeeee');
});
```

以前は自分`Karme + mocha + power-assert`のような構成が多かったんですが、設定が非常に大変だったんですが、`AVA`の場合は設定がシンプルにできるのも良い点です。

また、`AVA`が使用している`magic assert`(これが正式な呼称かは知らないが、PRにはそう書いてあった)は以下のようにテスト失敗時の差分が見やすい点もお気にいりです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91510/9a04735d-e833-d8bf-bee2-6c3d44c4085b.png)

他にも`Jest`と同様のスナップショットテストを行えるなどの利点もあります。

## 解決したかった点

上記では良い点を書きましたが、当然不満な点もあります。`webpack`でローダーを駆使したプロジェクトとの相性が悪かったり、`Typescript`使用の場合一手間必要だったり細かい点はいくつかあるのですが、自分が解決したかったのは以下の2点です。

- 1 本物のブラウザで実行したい
- 2 スクリーンショットを取りたい

上記は独立しておらず、2を解決するには1を解決する必要があるんですが。。。

#### 1 本物のブラウザで実行したい

`AVA`は`NodeJS`のプロセス上で動作する前提で設計されているのでブラウザ上で実行することができません。現状は`browser-env`を使用することでのテストを推奨しています。

https://github.com/avajs/ava/blob/master/docs/recipes/browser-testing.md

メンバーも本物のブラウザで動作させることはlow priorityだと言っておりそこまで関心は高くないようです。（長い間開いてたissueもいつの間にか閉じられてた・・・）

https://github.com/avajs/ava/issues/24#issuecomment-339391199

正直、ドラッグやファイルアップロードなど`browser-env`ではまかなえないケースはレアですし、SPAなどのテストではここまで必要となるケースは少ないかもしれません。（こいったコンポーネントは切り出してPuppeteerなどでテストすることもできますし。）

自分の場合は前述したように、2の解決に1が必要であったのと、たまたまそのようなテストを行いたいコンポーネントがあったため、モチベーションのひとつとして記載しています。

#### 2 スクリーンショットを取りたい

どちらかと言えば、こちらがメインのモチベーションです。弊社としても個人的にも`Visual Testing`を使用することが多いのでこちらは是非とも解決したい点でした。

`Visual Testing`については@Quramyの以下の記事が詳しいです。`Avaron`も紹介してもらってます。

[1日10万枚の画像を検証するためにやったこと - Qiita](https://qiita.com/Quramy/items/46d0b09ae4d8887b0941)

`Visual Testing`というと思い浮かべるスコープは人やチームによってまちまちかと思います。`E2E`レベルでのページ単位のスクリーンショットを思い浮かべる方も多いと思いますが、個人的にはコンポーネント単位で小さな単位でのスクリーンショット、すなわち`jest`の`snapshot test`の画像版のような使用方法がお気に入りです。そのへんの考え方は以下の資料にも記載しています。

[煩悩レスなコンポーネントテストを目指して // Speaker Deck](https://speakerdeck.com/bokuweb/component-test)

## Avaronについて

### 解決方法

資料にも少し記載はありますが、`AVA`が`fork`するprocessを`Electron`に無理やり差し替えています。(現状かなり力技でイケてないんですが、中の人曰くこの辺りを抽象化して`process`を差し替えられるようにするアイディア自体はあるようですが、low priorityとなっており、実現は遠そうです。)

https://github.com/bokuweb/avaron/blob/master/lib/fork.js#L60

`Electron`さえ立ち上がれば後は`renderer process`で実行させてやれば`Chromium`[^0]
上でのテストが可能なりますし、`Electron`の`win.capturePage`を使用すればスクリーンショットも取れるので解決したかった点は両方解決されます。




### 使用方法

以下のように設定することで`renderer`でのテストが可能です。また、もともと`AVA`を使用していた方は`"test": "ava"`となっていた箇所を`"test": "avaron"`とすればそのまま使用可能ですし、`Avaron`が`AVA`本体へ干渉することはないので、併用も可能（なはず）です。

`windowOptions`は`Electron`の`BrowserWindow`へ渡すパラメータです。Windowのサイズなどその他いろいろここから設定可能です。また、`fixture`はテストを実行する`html`を指定することができます。(karmaで言うところの`customContextFile`です)

``` json:package.json
{
  "scripts": {
    "test": "avaron"
  },
  "devDependencies": {
    "avaron": "*"
  },
  "avaron": {
    "renderer": true,
    "fixture": "./fixture.html",
    "windowOptions": {
      "title": "avaron"
    }    
  }
}
```

例えば以下のようなテストでキャプチャが取得できます。

```Javascript:抜粋
import test from 'ava';
import React from 'react';
import { render } from 'react-dom';
import { screenshot } from 'avaron';

test('should capture react component screenshot', async t => {
    render(<Example />, document.querySelector('.main'));
    datePicker.show();
    const path = 'screenshots/date-picker-dialog.png';
    await screenshot(path);
});
```

上記は抜粋ですので、全体は以下を参照ください。

https://github.com/bokuweb/react-avaron-sample/blob/master/test/index.test.js

![image.png](https://qiita-image-store.s3.amazonaws.com/0/91510/9ad42520-28a0-f294-70eb-3a9545677a04.png)

### 注意点

注意点としてはCIなどで動作させる場合、仮想ディスプレイが必要になるので注意してください。具体的には`Xvfb`などを設定する必要があります。以下にサンプルを記載します。

- TravisCI https://github.com/bokuweb/react-avaron-sample/blob/master/.travis.yml
- CircleCI(v1) https://github.com/bokuweb/react-avaron-sample/blob/master/circle.yml
- CircleCI(v2) https://github.com/bokuweb/flownes/blob/master/.circleci/config.yml

## 使用例

`Avaron`自身の紹介からは少しはずれますが、@Quramy作の`reg-suit`を使用した例を紹介してみたいと思います。

https://github.com/reg-viz/reg-suit

リポジトリは以下で上記のカレンダーの日付を変更した際の例になっています。

https://github.com/bokuweb/react-avaron-reg-sample

この例では`material-ui`のコンポーネントを使用しているのですが、コンポーネントの見た目の変更を検出できています。

![スクリーンショット 2017-12-20 8.09.20.png](https://qiita-image-store.s3.amazonaws.com/0/91510/9074462b-3213-61f2-fa5f-363c836740e3.png)

また、先日[ファミコンエミュレータの創り方　- Hello, World!編 -](https://qiita.com/bokuweb/items/1575337bef44ae82f4d3)という記事を書いたんですが、このエミュレータ開発にも`Avaron`と`reg-suit`を使用しています。テスト対象の解像度が低いので見にくいですが、エミュレータ用のテストROMを実行した際に差分がでた場合の例になります。（エミュレータのテストROMはエミュレータ上で実行されるためテストに落ちた場合の検出が困難で、見た目も含めて差分を検出できるこの仕組は有用でした。）

https://github.com/bokuweb/flownes

![スクリーンショット 2017-12-20 8.14.53.png](https://qiita-image-store.s3.amazonaws.com/0/91510/898291b6-17c8-06ff-2836-732f706f4126.png)

## さいごに

AVAをブラウザ上で実行する試みについて紹介しましたが、如何でしたでしょうか。
まだまだ不安定なところはあると思いますが、もし興味がありましたら、フィードバックやPRをいただけると幸いです。

[^0]: versionはElectronで使用しているものに縛られてしまうという欠点もありますが