Nightwatch.jsのE2Eテストをgulpベースのプロジェクトで行う

Nightwatch.jsを使ったテストの作成と、gulpから実行する際に工夫した点を紹介します。

## Nightwatch.jsとは

[Nightwatch.js](http://nightwatchjs.org/)は、End-to-Endのブラウザテストを自動化するフレームワークです。
フレームワーク自体は[Node.js](https://nodejs.org/)上で動き、テストの実行は[Selenium WebDriver API](http://code.google.com/p/selenium/wiki/JsonWireProtocol)と連携することで様々なブラウザでのテストを可能にしています。

Node.js製のE2Eテストフレームワークとしては、ユニットテストも含んだ統合型の[Intern](https://theintern.github.io/)、AngularJSとの親和性が高い[Protractor](https://angular.github.io/protractor/)と並び、人気のあるフレームワークです。（2015年9月時点）

- 公式サイト : http://nightwatchjs.org/
- GitHub : https://github.com/nightwatchjs/nightwatch

## gulpベースのプロジェクトで使うには

公式の[Developer Guide](http://nightwatchjs.org/guide)に従うと、Selenium ServerやChromveDriverを個別にインストールする必要があったり、CLIベースの実行方法しか紹介されていませんでした。
ここではgulpプロジェクトに組み込むためのセットアップ手順を紹介します。以下を行います。

- すべての依存モジュールをnpmでインストールする
- APIでNightwatchを実行し、gulpタスクに組み込む

**サンプルコード**
GitHubにてこれらを実装したデモプロジェクトを公開しています。
https://github.com/htanjo/nightwatch-gulp-demo

## npmで依存モジュールのインストール
まず最低限必要なNightwatch、gulp、Selenium Serverをインストールします。
Selenium Serverはjarのファイル名にバージョン番号を含むため、`--save-exact`を使ってバージョンを固定しました。

```
$ npm install --save-dev nightwatch gulp
$ npm install --save-dev --save-exact selenium-server-standalone-jar
```

**package.json**

```js
...
"devDependencies": {
  "gulp": "^3.9.0",
  "nightwatch": "^0.8.3",
  "selenium-server-standalone-jar": "2.47.1"    // 固定バージョン。"^"が付かない。
}
...
```

Nightwatchは、標準ではFirefox上でテストを行います。ChromeやPhantomJSでテストしたい場合は、それぞれのバイナリをインストールします。

```
$ npm install --save-dev chromedriver phantomjs
```

## ディレクトリ構成

この記事では以下のような構成を想定します。
`app`配下が開発中のページで、テストは`test/specs/*.js`に書きます。`test/reports`はNightwatchによって自動生成されるレポートです。
レポートはテストの実行のたびに出力されるので、`.gitignore`で外した方がいいかもしれません。

```
project/
├── app/
│   └── index.html
├── test/
│   ├── specs/
│   │   └── app-spec.js
│   └── reports/
│       └── xxxxx.xml
├── node_modules/
├── gulpfile.js
├── nightwatch.json
└── package.json
```

## Nightwatchの設定

Nightwatchの設定は、`nightwatch.json`というファイルで定義します。仕様はこちらです。
[Nightwatch.js - Configuration](http://nightwatchjs.org/guide#settings-file)

さきほどのディレクトリ構成に基づいて各種パスを設定します。`npm install`を実行するだけで、依存バイナリを含めた環境が再現できるようにします。
またこの例では、テストを実行するブラウザをPhantomJSとし、オプションでChromeとFirefoxを選択できるようにしました。

**nightwatch.json**

```json
{
  "src_folders": ["test/specs"],
  "output_folder": "test/reports",
  "selenium": {
    "start_process": true,
    "server_path": "node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.47.1.jar",
    "host": "127.0.0.1",
    "port": 4444,
    "cli_args": {
      "phantomjs.binary.path": "node_modules/phantomjs/lib/phantom/bin/phantomjs",
      "webdriver.chrome.driver": "node_modules/chromedriver/lib/chromedriver/chromedriver"
    }
  },
  "test_settings": {
    "default": {
      "launch_url": "http://localhost",
      "selenium_port": 4444,
      "selenium_host": "localhost",
      "silent": true,
      "desiredCapabilities": {
        "browserName": "phantomjs"
      }
    },
    "chrome": {
      "desiredCapabilities": {
        "browserName": "chrome"
      }
    },
    "firefox": {
      "desiredCapabilities": {
        "browserName": "firefox"
      }
    }
  }
}
```

## テストの作成

簡単なテストを書いてみます。E2Eテストはユニットテストより敷居が低くていいですね。

**テスト対象のHTML**

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Nightwatch.js Demo</title>
  </head>
  <body>
    <div id="content">Hello, Nightwatch.js!</div>
  </body>
</html>
```

**テスト内容**

- `<title>`が "Nightwatch.js Demo" である
- `<div id="content">`が "Hello, Nightwatch.js!" を含んでいる

**テストコード : test/specs/app-spec.js**

```js
'use strict';

module.exports = {

  'Contents test' : function (browser) {
    browser
      .url('http://localhost:9100/')
      .pause(1000)
      .assert.title('Nightwatch.js Demo')
      .assert.containsText('#content', 'Hello, Nightwatch.js!')
      .end();
  }

};
```

テストコード用のリファレンスはこちらです。`expect`を使ったBDDスタイルの記法もサポートしています。
[Nightwatch.js - API Reference](http://nightwatchjs.org/api)

## NightwatchをAPIから実行

これからNightwatchの実行スクリプトを作ります。

APIベースでテストを実行するには、`nightwatch.runner()`を使います。
実際はこれをgulpタスクに組み込みます。（次項）

```js
var nightwatch = require('nightwatch');

nightwatch.runner({
  config: 'nightwatch.json'
}, function (passed) {
  // passed: true or false
});
```

`.runner()`の第一引数で[CLIのオプション](http://nightwatchjs.org/guide#command-line-options)を渡せます。たとえば、`env: 'chrome'`を追加すれば、Chromeで実行されるようになります。
callbackではBoolean値でテストの実行結果が取得できました。

## gulpfileの作成
gulpfile.jsは以下のようになりました。
`gulp test`を実行すると、`app`をルートにしたWebサーバが起動し、それをテストします。

サーバの作成には[BrowserSync](http://www.browsersync.io/)を使っています。[Connect](https://github.com/senchalabs/connect)でも[Express](http://expressjs.com/)でも構いません。
また、[gulp-nightwatch](https://github.com/tatsuyafw/gulp-nightwatch)というgulpプラグインがありますが、今回のケースではプラグインをかます必要性があまりないためNightwatchを直接使っています。

```js
'use strict';

var gulp = require('gulp');
var bs = require('browser-sync').create();
var nightwatch = require('nightwatch');

gulp.task('serve:test', function (callback) {
  bs.init({
    notify: false,
    port: 9100,
    open: false,
    server: { baseDir: ['app'] },
    snippetOptions: { blacklist: ['/'] },
    ui: false
  }, function() {
    callback();
  });
});

gulp.task('test', ['serve:test'], function () {
  nightwatch.runner({
    config: 'nightwatch.json',
    env: 'default'
  }, function (passed) {
    if (passed) {
      process.exit();
    }
    else {
      process.exit(1);
    }
  });
});
```

## 実行結果

![Nightwatch](https://qiita-image-store.s3.amazonaws.com/0/67488/70ff50d1-2b4a-248b-2530-3cbb41d38edc.png)

## まとめ

- npmのdevDependenciesを活用すると、環境の共有が簡単！
- NightwatchのAPIを使うと、他のタスクと連携しやすい！

**Nightwatch.js**
http://nightwatchjs.org/

**サンプルコード**
https://github.com/htanjo/nightwatch-gulp-demo