WebアプリのUI自動化テストでNightwatch.jsを試す

## 前置き

Titanium Mobile使って、趣味で[CraftBeerFan](http://craftbeer-fan.info/)というiPhone＆Androidアプリを作っていて、並行して、Node.jsと[ACSというBaaS](http://www.appcelerator.com/cloud/)を使ったでWebアプリの方も開発してます。

ひとまず、スマホアプリの方は一旦開発を中断してWebアプリの方の実装に力を入れていこうと考えたのですが、UI部分含めた結合テスト（っていうんですかね？）をあまりしっかりしてないのと、仕事（こっちはRails4）でもUI自動化テストの仕組みを整える必要が出てきたのでどんなものがあるのか調べることにしました。

### 自分の環境

- Mac OS X 10.8.5
- Node.js v0.10.26

## UI自動化テストのツールに何を選ぶか？

Titanium Mobileで開発してることもあって、なるべくNode.js界隈の技術に精通してたほうが色々応用聞くと思って最近はNode.jsなツールを選定するように心がけています。

少し前にQiitaに[
Nightwatch.jsで自動ブラウザテスト](http://qiita.com/ta9to/items/46171d2fbb735b98dcd3)という投稿があって、Node.jsで書いたテストコードをSeleniumで実行してくれるという割と自分の求めてることが出来そうだったのでこれを試しました

## 環境構築

### Seleniumサーバ

上記記事だと、jarファイルダウンロード・・となってましたが、Macなので、brew 使ったほうがいいかなと思ったので以下のようにしました。

```sh
~/selenium
% brew install selenium-server-standalone
Warning: Your Xcode (5.0.2) is outdated
Please update to Xcode 5.1.
Xcode can be updated from the App Store.
==> Downloading http://selenium-release.storage.googleapis.com/2.41/selenium-server-standalone-2.41.0.jar
######################################################################## 100.0%
==> Caveats
To have launchd start selenium-server-standalone at login:
    ln -sfv /usr/local/opt/selenium-server-standalone/*.plist ~/Library/LaunchAgents
Then to load selenium-server-standalone now:
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.selenium-server-standalone.plist
Or, if you don't want/need launchctl, you can just run:
    selenium-server -p 4444

WARNING: launchctl will fail when run under tmux.
==> Summary
?  /usr/local/Cellar/selenium-server-standalone/2.41.0: 4 files, 33M, built in 11 seconds
~/selenium
```

最初[これ](http://hrysd.hatenablog.com/entry/2012/11/06/235209)を見て、エイリアスを・・・と思ったけど、上記brew でインストールした時のターミナルに

```sh
To have launchd start selenium-server-standalone at login:
    ln -sfv /usr/local/opt/selenium-server-standalone/*.plist ~/Library/LaunchAgents
Then to load selenium-server-standalone now:
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.selenium-server-standalone.plist
Or, if you don't want/need launchctl, you can just run:
    selenium-server -p 4444
```

とあり、Mac起動時にlaunchd経由でselenium-serverを起動させる方法書いてあったので

```sh
ln -sfv /usr/local/opt/selenium-server-standalone/*.plist ~/Library/LaunchAgents
```

とした後に、すぐに起動させたかったので、書いてある通り

```sh
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.selenium-server-standalone.plist
```

としました。

実際に起動してるのかどうか確認するためには、launchctlに、listオプションをつけてあげればよいみたいだったので、そのオプションをつけて実行しつつ、Unixのコマンドラインのパイプ機能をつかって、grepで、**selenium**が含まれてるものだけ画面表示してみたら以下のようになったのでひとまずOKっぽい

```sh
% launchctl list|grep selenium
62199   -       homebrew.mxcl.selenium-server-standalone
```

### Nightwathc.js

すでにnpm install 出来る環境整えてるのでこっちは簡単に作業できました

```sh
% sudo npm install -g nightwatch
Password:
npm http GET https://registry.npmjs.org/nightwatch
npm http 200 https://registry.npmjs.org/nightwatch
npm http GET https://registry.npmjs.org/nightwatch/-/nightwatch-0.4.17.tgz
npm http 200 https://registry.npmjs.org/nightwatch/-/nightwatch-0.4.17.tgz
npm http GET https://registry.npmjs.org/ejs
npm http GET https://registry.npmjs.org/mkpath
npm http GET https://registry.npmjs.org/minimatch
npm http GET https://registry.npmjs.org/optimist
npm http 200 https://registry.npmjs.org/mkpath
npm http GET https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz
npm http 200 https://registry.npmjs.org/ejs
npm http 200 https://registry.npmjs.org/minimatch
npm http 200 https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz
npm http GET https://registry.npmjs.org/ejs/-/ejs-1.0.0.tgz
npm http 200 https://registry.npmjs.org/optimist
npm http 200 https://registry.npmjs.org/ejs/-/ejs-1.0.0.tgz
npm http GET https://registry.npmjs.org/lru-cache
npm http GET https://registry.npmjs.org/sigmund
npm http GET https://registry.npmjs.org/wordwrap
npm http GET https://registry.npmjs.org/minimist
npm http 304 https://registry.npmjs.org/sigmund
npm http 304 https://registry.npmjs.org/wordwrap
npm http 200 https://registry.npmjs.org/minimist
npm http 304 https://registry.npmjs.org/lru-cache
/usr/local/bin/nightwatch -> /usr/local/lib/node_modules/nightwatch/bin/nightwatch
nightwatch@0.4.17 /usr/local/lib/node_modules/nightwatch
├── mkpath@0.1.0
├── ejs@1.0.0
├── minimatch@0.2.14 (sigmund@1.0.0, lru-cache@2.5.0)
└── optimist@0.6.1 (wordwrap@0.0.2, minimist@0.0.10)
```

## 環境整ったのでNightwatch.jsを試す

上記のQiitaの記事を参考にNightwatch.jsを試そうとしたら、こんなメッセージが出たので、Selenium FirefoxDriverが必要なように見えました


```sh
===========================

Running:  Demo test Google
There was an error while executing the Selenium command - enabling the --verbose option might offer more details.
Cannot find firefox binary in PATH. Make sure firefox is installed. OS appears to be: MAC
        Build info: version: '2.41.0', revision: '3192d8a', time: '2014-03-27 17:17:32'
        System info: host: 'HiroshiOyamada-no-MacBook-Air.local', ip: '10.42.47.111', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.8.5', java.version: '1.6.0_65'
        Driver info: driver.version: FirefoxDriver
```

でも、最近Firefox使ってないし、Chrome使いたいので[Nightwatch.jsでChromeのテスト](http://qiita.com/nakajmg/items/fc586d93910ec08b6fc3)を参考にして、Chrome使う方法模索しました。

### Chrome使うやり方に大分ハマった

ハマったポイントを先に書くと、ChromeのDriverをダウンロードして、それの保存先を間違っていたからでした。
どういうことかというとターミナル上で

```sh
which chromedriver
```
とした時に、chromedriverのロケーションが表示されるところにおけばすぐに動作したのですが、この辺りあまり深く考えず、~/Documents/chromedriverとかに置いてました

[ChromeDriverのGetting started](https://sites.google.com/a/chromium.org/chromedriver/getting-started)を見てたら

> Any of these steps should do the trick:
> include the ChromeDriver location in your PATH environment variable

という記述を見つけて、この時点で

「あ、PATHが通ってるところに置かないとダメっぽいのかな？」

というのに気づき、ひとまず/usr/sbin/にchromedriverをコピーしたら無事に実行できました

### 参考までにディレクトリ構成など

ディレクトリはこんな感じ

```sh
.
├── globals.json
├── reports
│   ├── craftbeerfan.xml
│   └── google.xml
├── screenshots
└── tests
    ├── craftbeerfan.js
    └── google.js
```

上記ディレクトリ内のglobals.jsonはこんな感じ

```javascript
{
  "src_folders" : ["./tests"],
  "output_folder" : "./reports",
  "custom_commands_path" : "",

  "selenium" : {
    "start_process" : false,
    "server_path" : "/usr/local/Cellar/selenium-server-standalone/2.41.0/libexec/selenium-server-standalone-2.41.0.jar",
    "log_path" : "",
    "host" : "127.0.0.1",
    "port" : 4444
  },

  "test_settings" : {
    "default" : {
      "launch_url" : "http://localhost",
      "selenium_host" : "127.0.0.1",
      "selenium_port"  : 4444,
      "silent": true,
      "output": true,
      "firefox_profile": false,
      "chrome_driver" : "/usr/sbin/chromedriver",
      "screenshots" : {
        "enabled" : true,
        "path" : "./screenshots"
      },
      "desiredCapabilities": {
        "browserName": "chrome",
        "javascriptEnabled": true,
        "acceptSslCerts": true
      }
    }
  }
}
```

で実行する時には以下のようにやってます。

```sh
nightwatch -c ./globals.json -t tests/google.js --verbose
```

--verbose オプションは付けることで、

```sh
✔  Testing if element <#main> contains text: "The Night Watch".
LOG     → Completed command getText (1098 ms)
INFO Request: DELETE /wd/hub/session/7358e240-94be-49f9-a2af-c0ecc2fb29f8
 - data:
 - headers:  {"Content-Length":0}
INFO Response 204 DELETE /wd/hub/session/7358e240-94be-49f9-a2af-c0ecc2fb29f8 {}
LOG     → Completed command session (582 ms)
INFO FINISHED
OK. 5 assertions passed. (9568 ms)
```

という感じで詳細の情報がターミナル上に表示されるので一応このオプション付きでやってます

## CraftBeerFanの管理ページ機能の自動テストも一応動作した

上記実行できたので、今度は自分が作っているCraftBeerFanのWebアプリについて

- GoogleMapsが表示されることを確認する
- 管理ページにログイン出来る

という簡単なテストもやってみました。

tests/以下にcraftbeerfan.jsというファイルを作って以下のようにしました

```javascript
module.exports = {
  setup: function() {},
  teardown: function() {},
  "top page": function(client) {
    client.url("http://craftbeer-fan.herokuapp.com/")
      .waitForElementVisible("body", 3000)
      .assert.title("CraftBeerFan")
      .assert.visible("#map_canvas")
      .end();
  },
  "Login": function(client) {
    client.url("http://craftbeer-fan.herokuapp.com/login")
      .waitForElementVisible("body", 3000)
      .setValue('input[type=text]', 'USERID')
      .setValue('input[type=password]', 'PASSWORD')
      .click('.btn-danger')
      .pause(1000)
      .assert.title("管理者サイト")
      .end();
  }
};
```

上記実行すると、ブラウザが勝手に起動して無事にテストもパスしました！

## まとめ

Seleniumは、割と昔から憧れがあり^^;）それが使えるのは個人的に魅力だし、あとはテストコードをNode.jsというかJavaScriptで書けるのは自分としては扱いやすいです

[Nightwathch.jsのサイト見てると、カスタマイズしてコマンド拡張](http://nightwatchjs.org/guide#extending)もできるようなので今後はこれを使ってくことにします。