Nightwatch で E2E テストをしたいので Bing 検索のテストを書いてみた

Vue CLI で e2e テストを有効にすると入ってくる Nightwatch ですが、こんな感じで使うよ～というメモです。

## 公式サイト

こちらです。よくまとまったドキュメントもあります。

https://nightwatchjs.org/

## Bing で検索するテストを書いてみよう

サンプルとしては、ecosia のサイトの検索とか Google の検索とかがあります。
プレーンな API を使った例としては Nightwatch の公式を開いた時点でコード例が上がってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/31163b21-2298-f81a-50c8-adc8eaf65a64.png)

ただ、実際に画面系のテストをするときは Page Object Pattern を少なくとも使わないとテストコード内にページの内部実装に依存したコードが散らばって大変なことになります。なので、Page Object の機能を使って Bing で Microsoft と検索して、検索結果のページのテキストボックスに Microsoft という文字列がちゃんと入っているか確認するというテストを書いてみようと思います。

とりあえず必要最低限だけだと以下のドキュメントを見るといいかも。

- [Getting Started](https://nightwatchjs.org/gettingstarted)
- [Working with Page Objects](https://nightwatchjs.org/guide/working-with-page-objects/)
- [使えるアサーション](https://nightwatchjs.org/api)

Chai の expect とかも使えるみたいなので、興味があったらこっちのドキュメントも見ておくといいかもです。

- [Expect API](https://nightwatchjs.org/api/expect/)

Chai の expect って可読性のための Chain が

- to
- be
- been
- is
- that
- which
- and
- has
- have
- with
- at
- of
- same
- but
- does
- still

とあって、個人的には英語圏の人じゃないとフルの恩恵って難しいような気がするんだけどどうだろう？（英語弱者の意見

というわけで Nightwatch で Bing 検索してみようと思います。

## プロジェクトの作成

以下のコマンドでプロジェクトの作成と必要な依存関係をインストールします。

```
$ npm init -y
$ npm install --save-dev nightwatch chromedriver
```

nightwatch.conf.js を以下のような内容で作ります。chrome を使うということと、テスト用のソースコードは tests/e2e フォルダーだということを設定しています。その他にもカスタムコマンド（今回は使わないのでコメントアウト）、カスタムアサーション（今回は使わないのでコメントアウト）、ページオブジェクトのパスを指定しています。

```js:nightwatch.conf.js
const chrome = require('chromedriver')

module.exports = {
    src_folders: ['tests/e2e'],
    //custom_assertions_path: ['tests/custom-assertings'],
    //custom_commands_path: ['tests/custom-commands'],
    page_objects_path: ['tests/page-objects'],
    webdriver: {
        start_process: true,
        server_path: chrome.path,
        port: 9515,
    },
    test_settings: {
        default: {
            desiredCapabilities: {
                browserName: 'chrome',
            },
        },
    },
}
```

では、tests/page-objects/bingTop.js に Bing のトップページのページオブジェクトを作っていきましょう。
ページオブジェクトではセクションというものを使って、いい感じにページを領域単位に区切って管理することが出来ます。例えばヘッダーやメニューや入力フォームやレポート出力領域のような感じです。

セクションを使わずにべたっとエレメントを定義することも出来ますが、小さな画面じゃない限りはセクション区切った方が可読性的にはいいと思います。セクションの使い方を示すために、今回の Bing のトップページはセクションいらないくらいシンプルなのですが、あえてセクションを定義しています。

```js:tests/page-objects/bingTop.js
module.exports = {
    url: 'https://bing.com?cc=jp',
    sections: {
        search: {
            selector: '',
            elements: {
                input: {
                    selector: '',
                },
                submit: {
                    selector: '',
                },
            },
        },
    },
};
```

とりあえず操作項目としては、検索入力エリアと検索ボタンがあればいいので、それを定義しました。selector を指定していないので、Chrome か Chronium Edge で bing を開いて開発者ツールを開いて要素のツリーでセレクターがゲットしたいエレメントで右クリックしてコピー→セレクターでセレクターをゲットします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/f470eb68-c69b-e0f1-c14c-601322b67817.png)

セレクターを入れるとこんな感じです。

```js:tests/page-objects/bingTop.js
module.exports = {
    url: 'https://bing.com?cc=jp',
    sections: {
        search: {
            selector: 'body > div.hp_body > div.hp_cont',
            elements: {
                input: {
                    selector: '#sb_form_q',
                },
                submit: {
                    selector: '#sb_form > label',
                },
            },
        },
    },
};
```

これでテストコードからは、セレクターを指定せずにセクション名やエレメント名でアクセスできます。ページの構造が変わってもページの仕様が大きく変わらなければページオブジェクトの修正だけで対応できます。

後は、コマンドも定義できて例えば今回の例だと検索ボタンを押すとかいうのを以下のようにコマンドで定義できます。

```js:tests/page-objects/bingTop.js
const bingCommands = {
    wait: function() {
        this.section.search.waitForElementVisible('@input');
    },
    submit: function() {
        this.section.search.waitForElementVisible('@submit')
            .click('@submit')
            .waitForElementNotPresent('@submit');
    },
};

module.exports = {
    commands: [bingCommands],
    url: 'https://bing.com?cc=jp',
    sections: {
        search: {
            selector: 'body > div.hp_body > div.hp_cont',
            elements: {
                input: {
                    selector: '#sb_form_q',
                },
                submit: {
                    selector: '#sb_form > label',
                },
            },
        },
    },
};
```

同じ要領で検索結果のページのページオブジェクトも `tests/page-objects/bingSearchResult.js` に以下のように定義しました。

```js:tests/page-objects/bingSearchResult.js
module.exports = {
    sections: {
        header: {
            selector: '#b_header',
            elements: {
                input: {
                    selector: '#sb_form_q',
                },
            },
        },
    },
};
```

ページが出来たのでテストを書いていきます。`tests/bingtest.js` に以下のように書きました。

これで、`npx nightwatch` というコマンドを叩くとブラウザーが起動して画面に文字をうって操作してアサートしてくれます。後は、ものによっては Nightwatch のページオブジェクトのエレメントとかは、日本語にしてもいいかもですね。以下のように。

```javascript:tests/page-objects/bingSearchResult.js
const bingSearchResultCommands = {
    wait: function() {
        this.section.header.waitForElementVisible('@検索欄');
    }
};

module.exports = {
    commands: [bingSearchResultCommands],
    sections: {
        header: {
            selector: '#b_header',
            elements: {
                '検索欄': {
                    selector: '#sb_form_q',
                },
            },
        },
    },
};
```

```js:tests/page-objects/bingTop.js
const bingCommands = {
    wait: function() {
        this.section.search.waitForElementVisible('@検索欄');
    },
    submit: function() {
        this.section.search.waitForElementVisible('@submit')
            .click('@submit')
            .waitForElementNotPresent('@submit');
    },
};

module.exports = {
    commands: [bingCommands],
    url: 'https://bing.com?cc=jp',
    sections: {
        search: {
            selector: 'body > div.hp_body > div.hp_cont',
            elements: {
                '検索欄': {
                    selector: '#sb_form_q',
                },
                submit: {
                    selector: '#sb_form > label',
                },
            },
        },
    },
};
```

こうすると、テストコードは以下のようになります。チームでのルール決めですが日本オンリーならありかも。

```js:tests/e2e/bingtest.js
module.exports = {
    beforeEach: function(browser) {
        browser.page.bingTop()
            .navigate()
            .wait();
    },
    '検索語を入力して検索結果の画面に遷移': function(browser) {
        const bingTop = browser.page.bingTop();
        bingTop.section.search.setValue('@検索欄', 'Microsoft');
        bingTop.submit();

        const bingSearchResult = browser.page.bingSearchResult();
        bingSearchResult.wait();
        bingSearchResult.section.header.assert.value('@検索欄', 'Microsoft');
        browser.end();
    },
    '検索語を入力して検索結果の画面に遷移(日本語版)': function(browser) {
        const bingTop = browser.page.bingTop();
        bingTop.section.search.setValue('@検索欄', 'マイクロソフト');
        bingTop.submit();

        const bingSearchResult = browser.page.bingSearchResult();
        bingSearchResult.wait();
        bingSearchResult.section.header.assert.value('@検索欄', 'マイクロソフト');
        browser.end();
    },
};
```

動かしてみた結果は以下のような感じです。

![e2e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70288/103f12d1-31c0-2bb5-700e-9e8756cb2365.gif)

## まとめ

Nightwatch を使うと割とさくっと簡単に E2E テストが出来そうですね。
GitHub Actions の Windows マシンに入ってるブラウザーのリストにも Chrome がいるので、GitHub Actions でも気軽に走らせることが出来るかも。