【Cypress入門】初心者にまず読んでほしい要点まとめ

## この記事について
Cypress初心者の気付きやつまづきポイントの備忘録です。
最近始めた方やこれから始める方へ少しでも参考になれば幸いです。

## 目次
- __Cypressとは？__
- __まずはここから__
    - テスト単位（describe, it）について
    - アサーションについて
    - フックを用いてテスト前後に処理を実行する
    - DOM指定のベストプラクティス
    
- __ステップアップポイント__
    - カスタムコマンドについて
    - テストの動的生成
    - jQueryでのDOM取得

- __覚えておくと便利__
    - 特定のテストだけ実行・スキップ
    - CypressとjQueryのeachの違い
    - コードの実行順序について（同階層にdescribeとitがある場合）
    - 非表示のDOM要素を強制的にクリックする方法
- __おわりに__

## Cypressとは？
https://www.cypress.io/

Cypressはオープンソースのフロントエンドテストツールであり、下記のテストを行うことができます。

- E2E（エンドツーエンド）テスト
- コンポーネントテスト
- 統合テスト
- 単体テスト



今回はE2Eテストという前提で解説していきます。

## まずはここから
#### テスト単位（describe, itなど）について
テストに使う構文はMochaというテストフレームワークを用いており、主に下記を使います。

- describe()
- context()
- it()

contextはdescribeのエイリアスなので動きは同じですが、コードの可読性を上げるために下記のように使い分けるといいと思います。


- describe：テスト対象やテスト内容として使う
- context：条件分岐や前提を書くときに使う
- it：テスト項目の最小単位として使う

※contextとdescribeはネスト可能です。

コードにすると下記のような形になります。
```php
describe('[大項目テストタイトル：例）〇〇画面]', () => {
  context('[条件：例）〇〇であるとき]', () => {
    it('[小項目テストタイトル１：例）〇〇であること]', () => {
      //　テスト内容
    })
  })
})
```
初めは、まず __テスト対象__、__前提があるか__、__テスト項目__ を明確にし、その内容を元に上記のように骨組みを組んでいくと進めやすかったです。

例として、メニューバーのテストを行いたいとします。
まず想定される操作を「前提」として洗い出し、その前提に対してテストすべき項目を洗い出します。

- __テスト対象__：メニューバー
- __前提1__：メニューを押下したとき
- テスト項目1：該当画面に遷移すること
- テスト項目2：押下したメニューの背景が青くなること
- __前提2__：ログアウトボタンを押下したとき
- テスト項目1：ログイン画面に遷移すること


上記のように内容を明確にしたあと、下記のように組んでいきます。

```php
describe('メニューバー', () => {
  context('メニューを押下したとき', () => {
    it('該当画面に遷移すること', () => {
      //　テスト内容
    })
    it('押下したメニューの背景が青くなること', () => {
      //　テスト内容
    })
  })
  context('ログアウトボタンを押下したとき', () => {
    it('ログイン画面に遷移すること', () => {
      //　テスト内容
    })
  })
})
```

これ以外にも様々な書き方や方針があると思いますが、チームで進める場合は最初にチーム内で認識を合わせていた方がいいと思います。

#### アサーションについて
続いて、itの中に書く判定式について解説します。

itの中には実行時に満たされるべき条件として __アサーション__ を記述し、実行時にチェックすることでテストを行います。

例えばボタンの存在チェックを行いたい場合、下記のアサーションコマンドを使用します。
- get() → DOMの取得
- should() → 存在チェック

コマンドはつなげることができるので下記のように記述します。

```php
it('ボタンが存在していること', () => {
  cy.get('button').should('be.visible')
})
```
コマンドの詳しい解説や一覧については下記公式ドキュメントをご参照ください。

https://docs.cypress.io/api/table-of-contents

また、Cypressは標準で用意されているアサーションコマンドだけでなくchaiというアサーションライブラリも使うことができます。

https://docs.cypress.io/guides/references/assertions#Chai

#### フックを用いてテスト前後に処理を実行する
Mochaのフックを用いることでit前後に処理を実行することができます。
__before__、__beforeEach__、__after__、__afterEach__ の4種類があります。

```php
describe('フック', function () {
  before(function () {
    // 最初のitの前に1回だけ実行します
  });

  after(function () {
    // 最後のitの後に1回だけ実行します
  });

  beforeEach(function () {
    // 各itの前に毎回実行します
  });

  afterEach(function () {
    // 各itの後に毎回実行します
  });
});
```
フックすべてとitを記載した場合、実行順序は
__before__ → __beforeEach__  → __it__ → __afterEach__ → __after__
のようになります。

これらはdescribeやitと同じように、アロー関数を用いたり説明を記載することもできます。

```php
before('ここに説明を書くことができます', () => {
  // 任意の処理
});
```

#### DOM指定のベストプラクティス

Cypressでボタンクリックなどの動作に対するテストを行う際には、DOMを取得してイベントを記述します。

DOMに設定されているclassやidでも指定することができますが、classやidはシステムの改修によって変更される可能性が高くテストが壊れやすいため、Cypress公式では推奨されていません。

```html
<button
  id="main"
  class="btn btn-large"
  name="submission"
  role="button"
  data-cy="submit"
>
  Submit
</button>
```

上記のようなボタンをクリックしたい場合、classやidではなくdata-cyというcypress用の属性を付与したものを指定しましょう。

どの要素がテスト対象であるかがHTML側でわかるようにする目的もあります。

```js
//悪い例
cy.get('.btn.btn-large').click() 
cy.get('#main').click()

//良い例
cy.get('[data-cy="submit"]').click()
```

https://docs.cypress.io/guides/references/best-practices

## ステップアップポイント
#### カスタムコマンドについて
何度も行う必要がある操作（ログイン等）については、カスタムコマンドを使うことで操作や処理を関数のように登録でき、テストファイルで呼び出すことができます。

support内にcommands.tsファイルを作成しカスタムコマンドを登録します。

```php
//ログインコマンド
Cypress.Commands.add('login', () => {
  //サイトにアクセス
  cy.visit('http://localhost:8080/')
  //フォームにユーザ情報を入力
  cy.get('[data-cy="name"]').focus().type('name')
  cy.get('[data-cy="password"]').focus().type('pass')
  //ログインボタンを押下
  cy.get('[data-cy="form-btn"]').click()
})
```

これによりcy.login()で上記のコマンドを呼び出せるようになったので、下記のようにテストファイルで呼び出します。

```php
describe('〇〇画面', () => {
  before(() => {
    cy.login()
  })
  it('test1', () => {
    //テスト内容
  })
})
```
これで毎回ログイン処理を記述しなくてもテスト開始時にログインできるようになります。

#### テストの動的生成
同じ内容のテストを複数の項目に対して行いたいときは下記のようにテストを動的に生成することができます。

```php
describe('fruits check', () => {
  ['Apple', 'Banana', 'Orange'].forEach((val) => {
    it('fruits check: ' + val, () => {
      cy.get('[data-cy="fruits"]')
        .contains(val)
    })
  })
})
```

上記のコードは、実行時に次の3つのテストを生成します。

```php
  > fruits check: 'Apple'
  > fruits check: 'Banana'
  > fruits check: 'Orange'
```
Cypressのコードは膨大になりやすいため、動的に生成できると保守性も高まると思います。

#### jQueryでのDOM取得
CypressではDOM取得の際にjQueryを利用することができます。

```javascript
Cypress.$(selector)
```
上記のようにCypress.$によって指定することでjQueryのDOM要素として取得することができ、jQueryのコマンドをチェーンさせて利用することができます。

属性を取得したいときにattrを利用することが多いです。

## 覚えておくと便利
#### 特定のテストだけ実行・スキップ
開発中は「特定のテストだけ実行して動作確認したい」「仕様未確定のためこのテストは現段階ではスキップしたい」ということが多々あると思います。

そんなときに便利なのが __.skip()__ と __.only()__ です。

- .skip() → 特定のテストをスキップ
- .only() → 特定のテストのみ実行

```php
//　このテストをスキップできる
it.skip('test1', () => {
  console.log('test1')
})

//　このテストのみを実行できる
it.only('test2', () => {
  console.log('test2')
})
```
itだけでなくdescribeにも使うことができ、その場合はネストされたテストも対象となります。

#### CypressとjQueryのeachの違い
オブジェクトをまわす時によく使うeachですが、対象となるオブジェクトがCypressであるかjQueryであるかによって、コールバック関数に渡される引数の順序が異なります。

- Cypress：__オブジェクト__ → __インデックス__ の順
- jQuery：__インデックス__ → __オブジェクト__ の順

```javascript
// Cypress
cy.get('li').each(($el, index) => {
  console.log(index);
})

// jQuery
Cypress.$('li').each((index, element) => {
  console.log(index);
})
```
また、Cypressのeachで返されたオブジェクトはjQueryとして扱われるため、Cypress要素として扱いたい場合はcy.wrap()でラップしてあげる必要があります。

```javascript
cy.get('li').each(($el, index) => {
  // $el はjQueryオブジェクトとして返される
  // cy.wrap()でラップすることでCypressオブジェクトとして扱うことができる
  cy.wrap($el).click()
})
```
Cypressでは、扱いたい要素が何のオブジェクトであるのかを常に意識することが重要となります。

#### コードの実行順序について（同階層にdescribeとitがある場合）
同階層にdescribeとitがある場合の実行順序は、先にdescribeが書いてある場合でも
__it__ → __describe__ の順になるので注意してください。

下記の例では test2 → test1 の順に実行されます。

```php
describe('title', () => {
  describe('subtitle', () => {
    it('test1', () => {
      console.log('test1')
    })
  })
  it('test2', () => {
    console.log('test2')
  })
})
```

#### 非表示のDOM要素を強制的にクリックする方法
disabledな要素をクリックしようとした場合、Cypressでは非表示でなくなるまで待とうとするのでアクティブにならない限りクリックできません。

そんなときは { force: true } を指定することで強制的にクリックすることができます。

```php
 cy.get('button [disabled]').click({ force: true })
```

## おわりに
私自身がCypress初心者として最初に知りたかった要点をまとめてみました。
これから始める方がCypressを楽しめますように！