PuppeteerとJasmineを使ったE2Eテストの自動化

# やりたいこと
Puppeteerでクローリングした内容を元にJasmineで簡易に実装したE2Eテストを自動化したい。

# 背景
- よくある管理画面として、所属によってアクセス可能な画面が分かれていたり（例：社内向けの画面、広告主向けの画面、代理店向けの画面など）、また担当者に与えられる権限によっては画面の表示（例：偉い人には「承認」ボタンを表示など）が異なっていたりします。
- そのため、VIEW側で「こうゆう条件の場合はこうゆう表示をする」などの出し分けが複雑になりがちです。
- 例えば、社内、代理店、広告主で共通して使うElement（CakePHPで開発している場合、Elementは各VIEWの共通するコードをまとめる時に使います。）などにおいて、if文でフラグをチェックして表示を出し分けたりしている場合、そこのコードを変更した際に、社内、代理店、広告主のそれぞれの画面にログインして想定する動作となっているかの確認が非常めんどくさいことになります（そもそも複雑なVIEWにする設計は回避すべきですが...）。
- そこで、そういった画面のテストを自動化し、コマンド一発でUIの動作確認（E2Eテスト）ができるようにしたいという訳です。

# 前提
- `node.js` と `npm` が入っている。
- local の MBP（OSX 10.11.6）で実行する。

# 環境

| ツール  | バージョン | 説明 |
|:-----------------:|:------------------:|:------------------|
| node.js | 8.9.4　　　　|サーバーサイドでも使えるJavaScript　　　　　　　　　　　|
| npm | 5.6.0　　　　| node.jsのパッケージマネージャー　　　　　　　　　　　　　|
| puppeteer |1.9.0　　　　|ヘッドレスに（GUIからでなくても）Chromeを操作できるパッケージ　　　　|
| jasmine |3.2.0　　　　|テストするためのパッケージ　　　　　　　　　　　　|


# 手順
- インストール

```
$ npm i -D puppeteer jasmine
```

- 雛形を作成

```
$ node node_modules/jasmine/bin/jasmine init
```
こちらを `package.json` があるディレクトリで叩くと、Jasmineがいい感じにテストの雛形ファイルを作ってくれますが、最終的な構成はより簡易に以下のようにしました。

```
$ tree
node
├── node_modules
│    └── （略）
├── package-lock.json
├── package.json
└── spec
      ├── ViewSpec.js
      ├── helpers
      │     └── SpecHelper.js
      └── support
            └── jasmine.json

```

- ViewSpec.jsを作成

こちらがテストの本体、テストしたい画面ごとにファイルを作ればいい（？）ものと思います。

```javascript

const URL = 'https://yahoo.co.jp';

describe("View", function() {

    // テストを実行する前に呼ばれる処理
    beforeAll(async function() {
        console.log('beforeAll in ViewSpec.js');
        await page.goto(URL);
    });

    // それぞれのテスト（it）を実行する直前に呼ばれる処理
    beforeEach(async function() {
        console.log('beforeEach in ViewSpec.js');
    });

    // それぞれのテスト（it）を実行した直後に呼ばれる処理
    afterEach(function() {
        console.log('afterEach in ViewSpec.js');
    });

    // テストを全て実行した後に呼ばれる処理
    afterAll(function() {
        console.log('afterAll in ViewSpec.js');
    });
    
    // 期待するタイトルとなっているかのテスト
    it("has title", async function() {
        expect(await page.title()).toEqual('Yahoo! JAPAN');
    });

    // 期待するURLとなっているかのテスト（リダイレクトされているかなど）
    it("has url", async function() {
        // 【参考】 ログインが必要な時はこんな感じ
        
        // id=loginのフォームに「login_id」を入力してくれる
        // await page.type("#login", "login_id"); 
        
        // id=passwordのフォームに「login_password」を入力してくれる
        // await page.type("#password", "login_password");
        
        // class=login_buttonのボタンを押下
        // await page.click('button.login_button');

        expect(await page.url()).toEqual('https://www.yahoo.co.jp/');
    });
    
});

```

- SpecHelper.jsを作成

こちらのファイルは複数の`xxxxSpec.jp`に共通する処理などを書いておきます。
また、`ViewSpec.js` とこちらにある`beforeAll` `beforeEach` `afterEach` `afterAll` のメソッドがそれぞれどのような順序で呼ばれるのかを検証するために、`console.log()`を仕込んでおきます。

```javascript
const PUPPETEER = require('puppeteer');

beforeAll(async function() {
    console.log('beforeAll in SpecHelper.js');

    jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; 
    global.browser = await PUPPETEER.launch({
        headless: true // falseにするとブラウザが起動する
    });
    global.page = await browser.newPage();
    
    // Baisic認証が必要な場合
    await page.authenticate({
        username: 'auth_username',
        password: 'auth_password'
    });
});

beforeEach(async function() {
    console.log('beforeEach in SpecHelper.js');
});

afterEach(function() {
    console.log('afterEach in SpecHelper.js');
});

afterAll(function() {
    console.log('afterAll in SpecHelper.js');
});

```

- package.jsonを書き換え（必要なら）

これをやると `npm test` のコマンドが叩けるようになります。
こうしない場合 `jasmine` を叩きます（お好きな方で）。

```json
package.json

{
  "scripts": {
    "test": "jasmine"
  },
}
```
# 実行

- テストが成功する場合

テストファイルとヘルパーファイルに仕込んだ `console.log()` は以下のような順序で実行されています。
見づらいですが、 `.` ←とあるが、成功しているテストです。非同期処理なので、`beforeEach in ViewSpec.js` の直後ではなくて、結果が確認された時点で出力されている模様（？）です。


```
$ npm test

> jasmine

Randomized with seed 05532
Started
beforeAll in SpecHelper.js
beforeAll in ViewSpec.js
beforeEach in SpecHelper.js
beforeEach in ViewSpec.js
afterEach in ViewSpec.js
afterEach in SpecHelper.js
.beforeEach in SpecHelper.js
beforeEach in ViewSpec.js
afterEach in ViewSpec.js
afterEach in SpecHelper.js
.afterAll in ViewSpec.js
afterAll in SpecHelper.js



2 specs, 0 failures
Finished in 2.622 seconds
Randomized with seed 05532 (jasmine --random=true --seed=05532)

```


- テストが失敗する時

期待される結果を `Yahoo! JAPAN` から `Yahoo! USA` に変更してやってみます。
見づらいですが、 `F` ←とあるが、失敗しているテストです。

```
$ npm test

> jasmine

Randomized with seed 02515
Started
beforeAll in SpecHelper.js
beforeAll in ViewSpec.js
beforeEach in SpecHelper.js
beforeEach in ViewSpec.js
afterEach in ViewSpec.js
afterEach in SpecHelper.js
FbeforeEach in SpecHelper.js
beforeEach in ViewSpec.js
afterEach in ViewSpec.js
afterEach in SpecHelper.js
.afterAll in ViewSpec.js
afterAll in SpecHelper.js


Failures:
1) View has title
  Message:
    Expected 'Yahoo! JAPAN' to equal 'Yahoo! USA'.
  Stack:
    Error: Expected 'Yahoo! JAPAN' to equal 'Yahoo! USA'.
        at <Jasmine>
        at UserContext.<anonymous> (/dir/node/spec/ViewSpec.js:29:36)
        at <Jasmine>
        at process._tickCallback (internal/process/next_tick.js:188:7)

2 specs, 1 failure
Finished in 3.138 seconds
Randomized with seed 02515 (jasmine --random=true --seed=02515)
npm ERR! Test failed.  See above for more details.

```

- 個別にテストを実行したい場合

```
$ npm test spec/ViewSpec.js
```

# やってみて
- E2Eテストなので、立ち上げたDockerコンテナから動作確認をしたい開発環境などのURLをクローリングするというやり方でもいいかも知れません。
- クローリングを挟んでいるせいか、実行時間のバラつきが大きい印象を受けました。
- 二つだけのテストにもかかわらず数秒かかっているので、テストケースが増えクローリング対象のURLを増やしていった際に実行時間が大幅に増加しないかやや心配です。
 
# 参考
Puppeteer : [GoogleChrome/puppeteer](https://github.com/GoogleChrome/puppeteer) 
Jasmine : [Using Jasmine with node](https://jasmine.github.io/setup/nodejs.html) 
Jasmine : [Your first suite](https://jasmine.github.io/tutorials/your_first_suite)