Webテストを自動で行ってくれるTestCafeを導入してみようと思う

#はじめに
弊社はECサイトの運営を行っています。
ユーザーに影響を与えないようにするためにデバッグも行っています。
が、時間がかかってしまいます。
なので**TestCafe**というNode.jsのツールを導入してデバッグ工数削減を図りたいと思います。
本記事はTsetCafe導入にあたっての備忘録みたいなかんじです。
かなり初心者向けの内容になってると思います。

変なことを書いていたらご指摘お願いします。

#導入環境
MacOS Mojave バージョン10.14.4

#TestCafeとは
Node.jsのツールの1つ。
E2Eでブラウザテストを自動化するためのフレームワークです。
JavaScriptでテストコードを書いていくので、フロントエンドエンジニアがに導入しやすいツールなんじゃないかなと思います。
[TestCafe公式ページ](https://devexpress.github.io/testcafe/)


**E2E（End of End）とは**
User Interface Testとも呼ばれている。
システム全体を通してテストを行う。
参考：[E2Eテストの導入から学んだこと](https://qiita.com/mt0m/items/7e18d8802843d9f60d28#e2e%E3%83%86%E3%82%B9%E3%83%88%E3%81%A8%E3%81%AF)

##特徴
公式ページなどを参考に、下記の事が記載されていました。

- **自動待機メカニズムがある。**
→ テストが開始されると、ページ読み込み、XHRの完了、DOM要素の表示が待機されるように作られている。

- **最新のJS、またはTypeScriptでコードを記載する。**
→ ES2017を含む以前のJS機能をサポートしている。JSでしか記載できない。公式ではTypeScriptを推奨してるっぽい。。

- **コード内のJSエラーを発見する事ができる**
→ TsetCafeで記載したコードではなく、テストしているWebページ内のJSエラーを報告してくれる。この機能を無効にすることもできる。

- **平行テストができる。**
→ 同じブラウザの複数のインスタンスを開いてテストを実行することにより、テスト時間を短縮できる。

- **TestCafe,Node.jsがインストールされていない環境でもテストの実行ができる。**
→ TestCafeが出力するURLを対象の環境ブラウザで開くだけでテストが実行される。

- **ライブモード**
→ テスト中にTestCafeプロセスとブラウザがアクティブな状態になる。変更を加えるとテストが再開される。

ざっくりとこんな感じ、もっと特徴あると思うけど。

###TestCafe以外のテストツール
**Selenium**とか**Nightwatch**とか**Protractor**とかあるみたいです。
E2Eテストを導入するのが初めてなので他のテストツールのことは全く知りません。

#TestCafeをインストールしてみる
早速インストールしてみます。

下記のコマンド実行。
今回はglobalでインストールしました。

````shell
$ npm install -g testcafe

$ testcafe -v
1.2.0
````

プロジェクト単位とかでインストールしたい場合は下記のコマンドを実行。

````shell
npm install --save-dev testcafe
````

これでTestCafeを使用する環境は整いました。

#コードの書き方からテスト実行までの流れ

ざっくりとした説明をしていきます。
見出し通り流れを掴んでいただければと思います。

今回はTypeScriptを使わずにテストコードを書いていこうと思います。

TestCafeのコードを記載していく`.js`ファイルを作成し、必要なtestcafeモジュールをインポートします。
とりあえず`Selector`モジュールをインポート。
`Selector`モジュールに関しては後ほど説明します。

````JavaScript
import { Selector } from 'testcafe';
````

次に`fixture関数`を使ってテストフィクスチャの宣言をしていきます。
ちなにみテストフィクスチャとは、**テストを実行、成功させるために必要な状態や前提条件の集合のこと**だそうです。

````JavaScript
fixture `任意のテスト名`
    .page`対象ページのURL`;

（例）
fixture `ログインテスト`
    .page`https://hogehoge.com`;
````

次に`test関数`を使ってtestコードを記載していきます。

````JavaScript
test('テスト項目名', async t => {
  //テストコード
});
````
TestCafeでは`fixture関数`の後に`test関数`で処理を並べていく書き方が基本です。
ざっくりと下記のような構成になるかと思います。

````JavaScript
// モジュールをインポート
import { Selector } from 'testcafe';

fixture `任意のテスト名`
    .page`対象ページのURL`;

test('テスト項目名1', async t => {
  //テストコード
});

test('テスト項目名2', async t => {
  //テストコード
});
````
テストコードが記載できたらテストを実行してみます。

テスト実行したいブラウザとファイルパスを指定して、コマンドシェルから実行します。

````shell
testcafe chrome test.js
````
コマンドを実行すると選択されたブラウザが開き、テストの実行が開始されます。

またテスト結果は、テスト実行中にコマンドシェル内で確認できます。

#試しに下記のコードで実行してみました。

コードの詳しい解説は後ほどしていきます。

````test.js
import { Selector } from 'testcafe';

fixture`Qittaログインテスト`
    .page`https://qiita.com/`;

test('ログイン画面遷移', async t => {

    /** テストに必要な要素を取得 **/
    const 
         // ログイン画面遷移ボタン
         loginBtn = await Selector('.st-Header_loginLink')
         // ユーザーID or メールアドレスの記入窓
        ,inputUserId = await Selector('#identity')
         // パスワードの記入窓
        ,inputPassword = await Selector('#password')
         // ログインボタン
        ,loginFormBtn = await Selector('.loginSessionsForm_submit')
    ;
    
    /** テストアクション実行 **/
    await t
        // ログイン画面遷移ボタン
        .click(loginBtn)
        // ユーザーID or メールアドレスを記入
        .typeText(inputUserId, 'ユーザーID or メールアドレス')
        // パスワードを記入
        .typeText(inputPassword, 'ログインパスワード')
        // ログインボタンクリック
        .click(loginFormBtn)
    ;
    
    /** テストが成功しているか判断させる **/
    // ログイン完了後の'.st-Header_postButton'のテキスト取得
    const postBtn = await Selector('.st-Header_postButton').innerText;
    // 取得したテキストが「投稿する」だった場合テスト成功とする
    await t.expect(postBtn).eql('投稿する');
});
````
実行するとこんな画面が立ち上がります。
![スクリーンショット 2019-06-18 13.27.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223600/4e4e381a-fa0d-7159-9f7a-357900cca705.png)

ブラウザが立ち上がり、テストが開始されます。
![スクリーンショット 2019-06-18 13.27.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223600/b4e819f2-e746-5a3f-e529-e339a45d11f0.png)

テスト後のシェルはこんな感じです。
失敗した場合は赤文字でエラー表示されます。
![スクリーンショット 2019-06-18 13.27.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223600/8c5d34c9-c44f-0ebb-a678-84352aca3678.png)

##テストコード解説

###Selectorモジュール
`Selector`はテストに必要な要素を取得するためのものです。
JQueryみたいな感じで簡単に要素を取得できるようになります。
TestCafeが提供するメソッドチェーンをつなげて目的の要素まで絞り込めるので便利です。
色々なメソッドが存在するので[こちら](https://devexpress.github.io/testcafe/documentation/test-api/selecting-page-elements/selectors/)をご参照ください。

###ClientFunctionモジュール
今回のテストコードには記載されていないですがよく使いそうなのでメモしておきます。
テスト実行環境のDOMからではなく、JavaScriptからアクセスできるデータを使いたい場合に使用します。
**現在のページURLを確認したい場合**とかに使用します。
今回のテストページで使用すると下記のようなコードになると思います。

````JavaScript
import { Selector } from 'testcafe';
import { ClientFunction } from 'testcafe';

const getWindowLocation = ClientFunction(() => window.location);
//
//途中略
//
test('ログイン画面遷移', async t => {
    //
    //途中略
    //

    /** テストが成功しているか判断する部分 **/
    const location = await getWindowLocation();
    await t.expect(location.href).eql('https://qiita.com/');
});
````
Qiitaの場合ログイン時のURLは下記の通りになります。

ログイン失敗時 : `https://qiita.com/login/`
ログイン成功時 : `https://qiita.com/`

ホームのURLに戻ってきたらログイン成功といえます。

###テストコントローラー

テストコードにあった「await t」の`t`の部分がテストコントローラーと呼ばれるものです。
値の入力やクリックなどのアクションが期待通りの動作をしているか検証するためのアサーションメソッドを提供してくれています。
色んなアクションが存在するので[こちら](https://devexpress.github.io/testcafe/documentation/test-api/actions/)をご参照ください。

またアクションだけでなく`alert`などのダイアナログの操作も行ってくれます。

````JavaScript
await t
    // ダイアログの操作
    // trueを返すと「OK」を選択したことになる
    .setNativeDialogHandler(() => true)

   // 引数の設定もできるみたいですが今回は省きます。
````

###`expect`メソッド
こいつは大事かなって思うので他のメソッドとは個別でピックアップしときます。

この`expect`メソッドを使って、テストされたwebページが想定した状態に一致するかどうかを確認するためのアサーションです。
アサーションってなんぞやって方は[こちら](http://e-words.jp/w/%E3%82%A2%E3%82%B5%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3.html)をご参照ください。

今回のテストコードで使ったアサーションメソッドは`eql`になります。
**値が等しいことを判定**してくれます。

他にも**指定した値が含まれてるか判定**してくれる`contains`メソッドなどもあります。

色んなアサーションメソッドが存在するので[こちら](https://devexpress.github.io/testcafe/documentation/test-api/assertions/assertion-api.html)をご参照ください。

#最後に
TestCafeについて最低限の情報をまとめてみました。
似たようなページたくさん作るし、毎回デバッグも大変ってときはTestCafe使ってあげるといいかもしれないですね。

##参考ページ
- TestCafe 公式
https://devexpress.github.io/testcafe/
英語とか読めないからgoogleさんに翻訳してもらった。

- 第1回 TestCafeとは
https://app.codegrid.net/entry/2017-testcafe-1