Not Angularな環境でProtractorを使ってES2015(ES6), ES7なE2Eテストコードを書いてみた

# 追記
注意
サンプルコードなので省いていますがコードでは`await`として`Promise`のコールバックを隠ぺいしています。
実際には`Promise`が`resolve`ではなく`reject`を返すこともあるので、実際のコードでは`try-catch`を使ってちゃんとエラーを捕捉しましょう。 

```
async func() {
  try {
    let t = await funcReturnPromise();
  } catch(err) {
    console.log(err);
  }
}
```

# はじめに
ProtractorはAunglarのためのように使われてますが、[Yahoo Mailでの自動テスト](http://yahooeng.tumblr.com/post/115664629471/automated-testing-in-yahoo-mail)に書いてあるようにReact+Flux環境でもテスト(機能テスト、スモークテスト、インテグレーションテスト)に使われているということで試してみました。

以下の方針でコード例を記述します。

- ES2015を使ってモダンにテストコードを書く
- mocha + power-assertなテスト環境
- E2EテストはPageObjectパターンを利用

# 環境
- Mac OS X Yosemite
- Node 4.1.1


# 準備
## ディレクトリ構造
```
$ tree -I node_modules
.
├── package.json
├── protractor.conf.js
└── spec
    ├── test1
    │   ├── BingPageObject.js
    │   └── bing_spec.js
    └── test2
        ├── GooglePageObject.js
        └── google_spec.js
```

## 設定ファイル等
今回使うライブラリはpackage.jsonの通りです。

```package.json
{
  "name": "example_for_protractor",
  "version": "1.0.0",
  "description": "Example for Protractor",
  "license": "MIT",
  "devDependencies": {
    "babel": "^5.8.23",
    "babel-plugin-espower": "^1.0.0",
    "mocha": "^2.3.3",
    "power-assert": "^1.1.0",
    "protractor": "^2.4.0"
  },
  "scripts": {
    "postinstall": "webdriver-manager update"
  }
}
```

使ったライブラリでミソなのは[power-assert-js/babel-plugin-espower](https://github.com/power-assert-js/babel-plugin-espower)です。
これは以下の`protractor.conf.js`で活用します。

```protractor.conf.js
// Babel Require Hookを通してpower-assertを使ったES2015コードを変換する
require("babel/register")({
  only: /spec/,
  plugins: ['babel-plugin-espower'],
  extensions: ['.es6', '.js']
});

exports.config = {
  // specの実行前に、protractorが準備できたら一度だけ呼ばれる関数
  onPrepare: function () {
    browser.ignoreSynchronization = true;
  },

  directConnect: true,

  capabilities: {
    'browserName': 'chrome'
  },

  framework: 'mocha',

  suites: {
    test1: './spec/test1/*_spec.js',
    test2: './spec/test2/*_spec.js'
  },

  mochaOpts: {
    ui: 'bdd',
    reporter: "spec",
    slow: 3000,
    enableTimeouts: false
  }
};
```

ProtractorでES2015なテストコードを扱う場合は`protractor.conf.js`を起点に呼ばれるので`require("babel/register")`を仕込んでおきます。
またさらに`power-assert`も使いたい場合は`babel-plugin-espower`もフックさせると良いです。
こうしておくことで `$(npm bin)/protractor protractor.conf.js` だけでES2015とpower-assertの変換を掛ける必要がなくなるので書いたコードをすぐ実行できるようになります。

そして`onPrepare`内の `browser.ignoreSynchronization = true;` を書くことでAngularの処理が完了するまで待たない(Not Angular環境でもProtractorが使える)ということになります。



# テストコード
例としてとりあえず書いたコードを以下にあげます。
なんだこのテストは、というものもあるかもしれませんがそこは例ということで。

```spec/test1/BingPageObject.js
"use strict";
export default class BingPageObject {
    constructor(url) {
        this.url = 'https://www.bing.com/';
    }

    async getPage() {
        await this.navigateTo(this.url);
    }

    async navigateTo(url) {
        await browser.get(url);
    }

    async getTitle() {
        let title = await browser.getTitle();
        return title;
    }

    async typeQuery(query) {
        let textbox = await $('#sb_form_q');
        return await textbox.sendKeys(query);
    }

    async search(query) {
        let submit = await $('#sb_form_go');
        await this.typeQuery(query);
        return submit.click();
    }

    async getQuery() {
        let textbox = await $('#sb_form_q');
        return await textbox.getAttribute('value');
    }

    async getAlgo() {
        let algo = await $$('#b_results > li').filter(async(elem) => {
           let cls = await elem.getAttribute('class');
           return cls === 'b_algo';
        });

        return await algo.map(async(elem) => {
            let title = await elem.$('h2').getText();
            return title;
        });
    }
}
```
```bing_spec.js
"use strict";

import assert from "power-assert";

import BingPageObject from "./BingPageObject"

describe('Bing.com', () => {
    let page;
    let query = '天気';

    before(async() => {
        page = new BingPageObject();
        page.getPage();
        await page.search(query);
    });

    it('検索窓に入力したクエリが表示されている', async() => {
         let text = await page.getQuery();
         assert(text === query);
    });

    it('アルゴが表示されている', async() => {
        let algo = await page.getAlgo();
        assert(algo.length === 10);
    });
});
```



# 実行結果
```
$ $(npm bin)/protractor protractor.conf.js --suite test1
Using ChromeDriver directly...
[launcher] Running 1 instances of WebDriver


  Bing.com
    ✓ 検索窓に入力したクエリが表示されている
    ✓ アルゴが表示されている


  2 passing (3s)

[launcher] 0 instance(s) of WebDriver still running
[launcher] chrome #1 passed
```

ちょっとテストコードを失敗するようにしてみると

```
$ $(npm bin)/protractor protractor.conf.js --suite test1
Using ChromeDriver directly...
[launcher] Running 1 instances of WebDriver


  Bing.com
    1) 検索窓に入力したクエリが表示されている
    ✓ アルゴが表示されている


  1 passing (3s)
  1 failing

  1) Bing.com 検索窓に入力したクエリが表示されている:

      AssertionError:   # spec/test1/bing_spec.js:19
  
  assert(text === 'hoge')
         |    |          
         |    false      
         "天気"          
  
  --- [string] 'hoge'
  +++ [string] text
  @@ -1,4 +1,2 @@
  -hoge
  +天気
  
  
      + expected - actual

      -false
      +true
      
[launcher] 0 instance(s) of WebDriver still running
[launcher] chrome #1 failed 1 test(s)
[launcher] overall: 1 failed spec(s)
[launcher] Process exited with error code 1
```

いい感じにpower-assertも動いているようですね！


# まとめ
もともとこの内容は息抜きのつもりでいて、ProtractorとMochaやProtractorとBabelといった内容は見つかったのですが、さらにpower-assertを合わせたり、Angular環境でない場合はどうすればという疑問から試して書き起こしてみました。

余談ですがProtractor以外にも、個人的には(少し試しただけですが)E2Eテストとして[groupon/testium](https://github.com/groupon/testium)も悪くないかと思います。

# 参考資料
- Protractor側の設定とか書き方とか
  - [Using ES2015 in protractor.conf.js · Issue #2463 · angular/protractor · GitHub](https://github.com/angular/protractor/issues/2463)
  - [reactjs - Preprocess e2e tests' files (ES6 styling) for Protractor using webpack - Stack Overflow](http://stackoverflow.com/questions/31045334/preprocess-e2e-tests-files-es6-styling-for-protractor-using-webpack)
  - [Using Page Objects to Organize Tests - angular/protractor · GitHub ](https://github.com/angular/protractor/blob/master/docs/page-objects.md)
  - [javascript - What is browser.ignoreSynchronization in protractor? - Stack Overflow](http://stackoverflow.com/questions/28808463/what-is-browser-ignoresynchronization-in-protractor)
- ProtractorのPromiseをasync/awaitで手軽に扱いたかった
  - [JavaScript - ES async/awaitを全力で使ってみて発見したイディオム - Qiita](http://qiita.com/mizchi/items/95410f4955d94ed103fa)