Selenium Webdriverをnode.jsで試してみる(firefox, chrome)

# seleniumについて

今までさんざん避けてきたので少し触ってみました。
ググると「Selenium〜」と色々出てきたよく分からなかったので、ざっとまとめると下記のような感じっぽいです。

### Selenium Core
* 米ThoughtWorks社の社内テスト向けツールとして開発されたものが発展してseleniumの原型となったもの

### Selenium RC(Remote Control)
  * Webサーバー上にスクリプトを置かなくても利用できるようになった
  * ローカルPC上で「Selenium Server」という中継サーバーを起動し、このサーバー経由でテストを実行する

### Selenium IDE
  * ユーザーのブラウザ操作を記録して実行したり、Seleniumのスクリプトを生成するFirefoxプラグイン
  * 操作によって生成されたテストをhtmlで出力できる(java,rubyでも出力できたが肝心のテスト部分のコードがないような？)

### Selenium WebDriver
  * 中継サーバなしでブラウザテストができるツール「Webdriver」と統合されたもの
  * Selenium RCとWebDriverではコマンド体系が異なるが、なるべくWebdriverを使う方が良さそう

### Selenium Builder
  * Selenium IDEではSelenium RCのコマンドしか使えないが、Selenium BuilderではSelenium WebDriverのコマンドが使える
    * firefoxの仕様変更がありレコードした操作の再実行ができなくなっている模様・・・
  * ブラウザ操作をレコードしてスクリプトで出力するのは出来るので、これを元に微調整してSelenium WebDriverで実行するのが良さそう

### Selenium Grid
  * テストを並列で実行できる？


**⇛いわゆるコード書いてUIテストっていうのは"Selenium Webdriver"っぽい**

クロスブラウザを考慮しないのであればPhantomJSを使ったり、JSモジュールの単体テスト重視ならkarma等も良さそうです。


# selenium Webdriverを試す

* java,ruby,python等でテストを書けますが、今回はjavascript(node.js)で試しました
* [Gitリポジトリ](https://github.com/SeleniumHQ/selenium)
   * node向けreadmeは[こちら](https://github.com/SeleniumHQ/selenium/tree/master/javascript/node/selenium-webdriver)

### 環境

* Max OSX 10.11
* firefox52
* chrome57
* nodebrewはインストール済み（インストール方法は[こちら](http://qiita.com/teratsyk/items/51d64010cb341b54491b)）

### インストール

* nodeバージョン
  * [node-support-policy](https://github.com/SeleniumHQ/selenium/tree/master/javascript/node/selenium-webdriver#node-support-policy)によると、ちゃんとサポートされているらしき `7.5` か `6.9` がよさそう

```
$ nodebrew install-binay v7.5.0
$ nodebrew use v7.5.0
```

* jsライブラリを取得

```
$ npm -g install selenium-webdriver
```

* jsでテストコードを書くのに便利なライブラリを入れる

```
$ npm -g install mocha	// テストフレームワーク
$ npm -g install expect.js  // アサーションライブラリ
```

* firefoxのバージョン47以上ならドライバが必要なので取得する
  * [geckodriver](https://github.com/mozilla/geckodriver/releases/)
  * **v2017/3/17時点では、v0.15.0はhttpレスポンスのフォーマットが変わっておりnode版のselenium-webdriver3.3.0でエラーになります。。 v0.14.0を使って下さい**
  * カレントディレクトリかパスの通っている場所に置いて下さい

### テスト実行

* あるサイトのログインフォームのテストを実施
  * IDのみ入力してログインボタンをクリックして、返ってくるエラーメッセージが一致すること
  * IDと適当なパスワードを入力してログインボタンをクリックして、返ってくるエラーメッセージが一致すること

###### 実行スクリプト

* firefoxTest.js
  * postした後の戻りを待つやり方がこれでいいのかは微妙です..

```javascript
const webdriver = require('selenium-webdriver')
    ,By = webdriver.By
    ,until = webdriver.until
    ,t = require('selenium-webdriver/testing')
    ;
const expect = require('expect.js');
const assert = require('assert');

t.describe('ログインフォーム テストデモ', function () {
  let driver;

  // 全テストの実行前に実行する処理
  t.before(function () {
    driver = new webdriver.Builder()
      .withCapabilities(webdriver.Capabilities.chrome())
      .build();
  });

  // 全テストの実行後に実行する処理
  t.after(function () {
   driver.quit();
  });

  // テスト
  t.it('パスワード未入力テスト', function () {
    driver.get('http://hogehoge.com/login').then(function () {

      // パスワード入力せずにSubmitする
      driver.findElement(By.id('login_name')).sendKeys('hoge');                           // idで要素を取得して値をセット
      driver.findElement(By.xpath('//input[@value="Login" and @type="submit"]')).click(); // xpathで要素を取得してクリック実行

      driver.wait(until.elementLocated(By.id('error')), 2000)                             // 指定要素が取得できるまでwait
        .then(function (elm) {
          elm.getText()                                                                   // 要素のテキストを取得
            .then(function (text){
              expect(text).to.be('ログイン名が存在しません。');
            });
        });
    });

  });

  t.it('パスワードエラーテスト', function () {
    driver.get('http://hogehoge.com/login').then(function () {

      // 名前を入力してSubmitする
      driver.findElement(By.name('login_name')).sendKeys('terada');
      driver.findElement(By.name('login_pass')).sendKeys('dummypassword');
      driver.findElement(By.xpath('//input[@value="Login" and @type="submit"]')).click();

      driver.wait(until.elementLocated(By.id('error')), 2000)
        .then(function (elm) {
          elm.getText()
            .then(function (text){
              expect(text).to.be('パスワードが間違ってます。');
            });
        });
    });
  });
});
```

* selenium-webdriverのAPIは[こちら](http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/)
  * driver.executeScript()でテスト対象画面でスクリプト実行させることも可能
  * スクリーンショットも取れたりする(driver.takeScreenshot()？)らしい

###### 実行コマンド

```
$ mocha firefoxTest.js --timeout=10000


  ログインフォーム テストデモ
    ✓ パスワード未入力テスト (831ms)
    ✓ パスワードエラーテスト (540ms)


  2 passing
```

実際にブラウザが起動して２件のテストをパスしました！


### テスト結果

* mochaコマンドのreporterオプションで出力方法を指定できる
  * `doc` で出力してみたが、正常なときにOKとか出ないのでイマイチ見難い。。。
  * jenkinsに組み込むなら `xunit`

```
$ mocha -h
    ...
    -O, --reporter-options <k=v,k2=v2,...>  reporter-specific options
    -R, --reporter <name>                   specify the reporter to use
    ...
    --reporters                             display available reporters
    ...
$ mocha --reporters

    dot - dot matrix
    doc - html documentation
    spec - hierarchical spec list
    json - single json object
    progress - progress bar
    list - spec-style listing
    tap - test-anything-protocol
    landing - unicode landing strip
    xunit - xunit reporter
    min - minimal reporter (great with --watch)
    json-stream - newline delimited json events
    markdown - markdown documentation (github flavour)
    nyan - nyan cat!
```

* 整形ツールもあるようです
  * [Allure Framework](http://blog.trident-qa.com/2014/12/make-selenium-report-more-stylish/)
  * [Sahagin](http://blog.trident-qa.com/2015/01/sahagin-first-release-1/)

### その他

#### chrome用のドライバーが正しくなかった...

* gitリポジトリのREADMEにあるドライバーのリンクURLが http://chromedriver.storage.googleapis.com/index.html になっているが、実は https://sites.google.com/a/chromium.org/chromedriver/downloads だった。。。
  * 2017/3/17現在で最新バージョンは `2.28` です

* ドライバをパスの通っている場所に置いて、firefoxと同じ書き方でいけます

```
    driver = new webdriver.Builder()
      .forBrowser('chrome')
      .build();
```

#### モバイルは？

Appium？

#### 参考になるページ
  * [全国のSeleniumer必読](http://qiita.com/oh_rusty_nail/items/b8ba525d31ea7c522856)
  * [Seleniumを使ったWeb UI自動テストシステムの構築でやったことまとめ](http://unageanu.hatenablog.com/entry/20111031/1320016739)」