TestCafeで「お礼の品お気に入り登録」をテスト&解説してみる

# はじめに
この記事は[トラストバンクアドベントカレンダー](https://qiita.com/advent-calendar/2021/trustbank)19日目の記事です。

初めまして！トラストバンクでQAエンジニアをしてます@ryry_qaです！
今回はトラストバンクでも使用しているテスト自動化ツールのTestCafeで「お礼の品お気に入り登録」をテストして、チュートリアル的に解説していこうと思います。

# TestCafe環境構築
ローカルから実行する分には下記2点のみで実行可能です。
・Node.js（npm実行のため）
・TestCafe

Node.jsをインストール後npmでTestCafeをインストールします。
```npm install -g testcafe```
下記コマンドでverが表示されていれば環境構築完了です。
```testcafe -v```

非常にシンプルですね。

# TestCafeで使うJavaScript（TypeScript）の記法など
async/await（非同期関数）
コールバック関数
アロー関数
メソッドチェーン
など

# なにを自動化するか
自動化するシナリオは下記になります。
　1. [ふるさとチョイストップページ](https://www.furusato-tax.jp/)へアクセス
　2. フリーワード検索
　3. 検索結果1件目をお礼の品を（ハートマークにて）お気に入り登録
　4. お気に入りリストに登録されていることを確認

# コード全文
下記がコード全文になります。
TestCafeは直感的に分かりやすくシンプルに書けるのでいいですね！
本来であればページごとにクラスを用意してコンストラクター内にセレクターなどを記述したいところですが、今回は解説しやすいようあえてベタ書きにしてみました。

```typescript:test.ts
import { Selector } from 'testcafe';

fixture`お気に入り`;

test('お礼の品をお気に入り登録', async t => {
    const topUrl ='https://www.furusato-tax.jp/';
    const searchWordText = 'クリスマス';
    const searchWord = Selector('#searchword');
    const firstItemFav = Selector('.card-product__heart.addfavorite').nth(0);
    const firstItemId = firstItemFav.getAttribute('data-id');
    const headerFavoriteBtn = Selector('.mf-heart01.nav-user__icon');
    const deleteItem = Selector('.btn_remove-goods');
    const favoriteFirstItemId = deleteItem.nth(0).getAttribute('data-id');

    // トップページ遷移後、ヘッダーフリーワード検索より「クリスマス」で検索を行う
    await t
        .navigateTo(topUrl)
        .typeText(searchWord,searchWordText)
        .pressKey('enter');

    // 検索結果1件目のdata-idを取得しておく
    const firstItemIdValue = await firstItemId;

    // 検索結果一件目のハートマークを押下後、お気に入りリストへ遷移
    await t
        .click(firstItemFav)
        .click(headerFavoriteBtn);

    // お気に入りリストにてお気に入りしたお礼の品が追加されていることを確認
    const favoriteFirstItemIdValue = await favoriteFirstItemId;
    const deleteItemCnt = await deleteItem.count;

    // アサーション
    await t.expect(deleteItemCnt).gte(1,'お気に入りリストのお礼の品が0件です');
    await t.expect(firstItemIdValue).eql(favoriteFirstItemIdValue,'お気に入りしたお礼の品が一致しません');
});
```
#解説
まずモジュールインポートですが、こちらではSelectorのみ使用してます。

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

fixtureはテストカテゴリみたいなものです。
今回は「お気に入り」機能に関するテストでしたので「お気に入り」としました。（fixtureの文字列は標準出力されます）

```typescript
fixture`お気に入り`;
```

testメソッドで1つのテストケースを定義できます。
第一引数はテストケース名を、第二引数に実際に行うテストの内容を記述します。（fixture同様、第一引数の文字列は標準出力されます）

```typescript
test('お礼の品をお気に入り登録', async t => {
~~ });
```

続いてからSelectorになります。
Selectorは、ページの要素を取得するメソッドです。
呼び出し時にページを参照して要素を取得するため予め宣言することが可能です。
引数には、操作や取得したい要素のCSSセレクタ文字列を渡します。
下記はid属性値で取得しているため先頭に「#」を付与してます。
※CSSセレクタは開発ツールから取得

```typescript
    const searchWord = Selector('#searchword');
```

続いて下記はクラス名属性値で取得しているため先頭に「.」を付与してます。
nthメソッドはフィルタと呼ばれるもので、引数にindex番目を指定してあげます。
つまり下記は同ページに複数あるクラス名「.card-product__heart.addfavorite」要素の0番目（一番最初の要素）を取得していることになります。
※複数クラスの指定をする際のスペースは「.」に置換することで取得可能です。

```typescript
   const firstItemFav = Selector('.card-product__heart.addfavorite').nth(0);
```

続いてgetAttributeメソッドですが下記は呼び出し時に属性名data-idのvalueを取得できます。
なぜ取得するかですが、本テストの期待結果である「お気に入りした品と、お気に入りリストに追加された品が等しいこと」を担保するためです。
残りのSelector取得に関しては上記までの説明で理解できると思いますので割愛させて頂きます。

```typescript
   const firstItemId = firstItemFav.getAttribute('data-id');
```

下記のようにawaitを付与することにより各メソッドの処理結果を待つようになってます。（メソッドを順番に実行してくれる）
t にはtestcafeのコントローラーが渡されます。

・navigateTo(topUrl)
⇒引数に渡したページへ遷移してくれます。
　ここでふるさとチョイストップへ遷移してます。

・typeText(searchWord,searchWordText) 
⇒第一引数に渡した要素（Selector）に対して、第二引数に文字列を渡した文字列を入力してくれます。
　ここでフリーワードを入力してます。

・pressKey('enter')
⇒引数に指定されたキーボードを押下してくれます。
　ここでフリーワード入力後のenterキーで検索実行してます。

```typescript
    await t
        .navigateTo(topUrl)
        .typeText(searchWord,searchWordText)
        .pressKey('enter');
```

下記で属性名data-idのvalueを取得してます。
data-idのvalue取得を待たずして次処理を実行してしまうのを防ぐために、firstItemIdにawaitを付与してます。

```typescript
    const firstItemIdValue = await firstItemId;
```

clickは第一引数に渡した要素をクリックしてくれます。
・click(firstItemFav)
⇒ここで検索結果の一番最初のお礼の品のお気に入りボタンをクリックしてます。

・click(headerFavoriteBtn)
⇒ここでヘッダーお気に入りをクリックしてます。

```typescript
    await t
        .click(firstItemFav)
        .click(headerFavoriteBtn);
```

・await favoriteFirstItemId
⇒前述で説明した通り、こちらはお気に入りリストページにて属性名data-idのvalueを取得しています。

・await deleteItem.count
⇒Selectorにcountを使用することにより、要素の数を取得してくれます。
　ここでお気に入りリスト内の品の数を取得してます。

```typescript
    const favoriteFirstItemIdValue = await favoriteFirstItemId;
    const deleteItemCnt = await deleteItem.count;
```

最後に、アサーションになります。
取得した値と予測値のアサーション（比較）を行っており、
expectメソッドでテストケースのOK/NGが決まります。
expect()には実際に取得した値を、eql()には予測値を引数に渡します。

・await t.expect(deleteItemCnt).gte(1,'お気に入りリストのお礼の品が0件です');
⇒gteメソッドにてお気に入りリストの品数が1個以上であることを検証してます。
　NGとなった際に標準出力させたい場合は、第二引数に文字列を渡すことで可能です。

・await t.expect(firstItemIdValue).eql(favoriteFirstItemIdValue,'お気に入りしたお礼の品が一致しません');
⇒eqlメソッドにて検索結果ページでお気に入りした品と、お気に入りリストページに追加されたお礼の品の属性名data-idのvalueが等しいことを検証しています。

```typescript
    await t.expect(deleteItemCnt).gte(1,'お気に入りリストのお礼の品が0件です');
    await t.expect(firstItemIdValue).eql(favoriteFirstItemIdValue,'お気に入りしたお礼の品が一致しません');
```

# 実行
下記コマンドを実行し、passedになっていればOKです。
（第一引数に実行するブラウザを、第二引数に実行するファイルを渡す）

```terminal
$ testcafe 'chrome' test.ts  

 Running tests in:
 - Chrome 96.0.4664.93 / Windows 10

 お気に入り
 √ お礼の品をお気に入り登録

 1 passed (18s)
```


# さいごに
もっと詳しい解説などは[TestCafe公式のリファレンス](https://testcafe.io/documentation/402632/reference)をおすすめします。
今回はチュートリアル的な記事になってしまいましたが、次回はBrowserStackとの連携などもう少し踏み込んだ記事を書けたらなと思います。
QA界隈では最近何かとテスト自動化というキーワードが飛び交っているような気がしますので、やったことない方も是非チャレンジして頂ければと思います。