TypeScript + Cypress + CucumberでE2Eテストをやる

## 前提
- 今回はnode.jsのパッケージマネージャーとしてyarnを使っている
- したがって本稿で使用しているコマンドは`yarn`を使っているが、使っていない方は各自`npm`に変換して読み進めてください

## Cypressとは
- Cypress は、テストのセットアップ、作成、実行、デバッグなどをシンプルにするブラウザテストツール
- Seleniumと似ているがCypressは完全にテスト目的に特化しているのが特徴

## 目的
今回はCypressのプラグインにあるCucumberを導入して実際に簡単なテストを書いて回してみる

※最短で目的を達成するため細かい説明は多少省きますが（必要だと思った部分は一応リンク貼っておきます）、参考サイトにを挙げておきますのでそちらを確認してみてください

## 1. インストール
本稿ではルートのディレクトリを`sample-e2e`とする
初めに`sample-e2e`ディレクトリを作成し、cypressやその依存関係をTypeScriptへトランスパイルのために必要なものをインストール

```sh
mkdir sample-e2e
cd e2e
yarn init -y
yarn add cypress webpack @cypress/webpack-preprocessor typescript ts-loader
```

次にCucumberのプラグインの導入に必要なやつをインストール

```sh
yarn add cypress-cucumber-preprocessor @types/cypress-cucumber-preprocessor tsify
```

## 2. セットアップ
まずはTypeScriptの設定をするため`tsconfig.json`を作成

```json:/tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "sourceMap": true,
    "module": "commonjs",
    "target": "es5",
    "lib": [
      "dom",
      "es6"
    ],
    "jsx": "react",
    "experimentalDecorators": true
  },
  "compileOnSave": false
}
```

次にCypressの最初のdry runを行い、Cypressのフォルダ構造を準備（デフォルトのディレクトリの役割については後述）

```sh
yarn cypress open
```
<img width="912" alt="スクリーンショット 2019-11-19 17.21.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535721/5adbca06-bcb7-f8a4-2439-5fef2df7b4cd.png">

- コマンドを実行するとCypress IDEが勝手に開く
- ウェルカムメッセージが表示されるので、確認したら一旦閉じましょう

プラグインの`index.js`を編集

```js:/cypress/plugins/index.js
const cucumber = require("cypress-cucumber-preprocessor").default;
const browserify = require("@cypress/browserify-preprocessor");

module.exports = (on) => {
  const options = browserify.defaultOptions;

  options.browserifyOptions.plugin.unshift(['tsify']);
  // Or, if you need a custom tsconfig.json for your test files:
  // options.browserifyOptions.plugin.unshift(['tsify', {project: 'path/to/other/tsconfig.json'}]);
  
  on("file:preprocessor", cucumber(options));
};
```

Cucumberの書き方でテストを書く`step`ファイルを置く場所を指定（`step`ファイルについては後述）

```json:/package.json
"cypress-cucumber-preprocessor": {
    "step_definitions": "cypress/support/step_definitions/"
  }
```
- 注意点として説明のしやすさの都合上今回はあえてCucunberのレガシーなスタイルである`/cypress/support/step_definitions`以下で`step`ファイルを作成する方法で進めていく
- 最近は`step`ファイルを`/cypress/integration`以下に任意のディレクトリを作成し、その中に置く方法を取っている場合が多い
- その辺は運用次第なので各々やりやすい方法で進めてください
- その場合は上記の代わりに以下のように`package.json`を編集

```json:/package.json
// /cypress/support/step_definitions以下でstepファイルを作成していく場合は無視してください
　"cypress-cucumber-preprocessor": {
    "nonGlobalStepDefinitions": true
 }
```

任意で`/package.json`にいくつかのスクリプトを追加（今回は参考サイトのスクリプトを真似したのでやらなくてもよい）

```json:/package.json
  "scripts": {
    "cypress:open": "cypress open",
    "cypress:run": "cypress run"
  }
```

## 3. `*.feature`の作成
- Cucumberでは`*.feature`に仕様等を定義していく
- 今回はexampleでよくやるGoogle.comのページに行くテストをやってみようと思うので、とりあえず`Google.feature`を作成

```:/cypress/integration/Google.feature
Feature: Access Google
    Googleの検索ページのテスト
    
    Scenario: Googleの検索ページにアクセス
        Given google.comにアクセスする
        Then タイトルにGoogleがある
```
- Feature、Scenario、諸々の書き方等は[Cucumber公式](https://cucumber.io/docs/gherkin/reference/)を参照
- とりあえず大事なのはStep（例では`Given`、`Then`だが他にも`When`等がある）で`Given`、`When`などで前提の処理を記述し`Then`で期待する動作を記述する

## 4. `*.step.ts`の作成

- Cucumberでは`step`ファイルで`*.feature`で定義した内容に対応したテストを実装する
- 今回は`step`ファイルを`/cypress/support/step_definitions`以下に`accessingGoogle.step.ts`を作成しそこでCypressを使いつつテストを実装していく


```typescript:/cypress/support/step_definitions/accessingGoogle.step.ts
/// <reference types="cypress"/>

import {Given, Then} from "cypress-cucumber-preprocessor/steps";

Given(`google.comにアクセスする`, () => {
    cy.visit("https://google.com")
});

Then(`タイトルにGoogleがある`, () => {
    cy.title().should("include", "Google")
});
```
注意点として以下の2点がある

- TypeScriptでやる場合、ファイルの先頭に`/// <reference types="cypress"/>`を記述する
- 各Stepの第一引数の文字列は`*.feature`と一致させる必要がある

2点目についてはよく正規表現を使うことが多いが今回は必要ないと思ったためやっていない

### Cypress Apiについて
- `step`ファイルの例でしれっと`cy.visit(url)`等が出てきているが、他にも色々なメソッドが用意されている
- どのようなメソッドがあるのかは[Cypress公式](https://docs.cypress.io/api/api/table-of-contents.html)が全てなので各自確認するといいと思います
- とりあえず大事なのは`cy.should(arg)`で、これは明示的なアサーションに使う
- この引数にはアサーションツールの「Chai」をベースにテストでアサーションを行う
- `cy.should(arg)`についても[Cypress公式のshould](https://docs.cypress.io/api/commands/should.html#Syntax)のページに書いてあるので確認してみてください


## 5. テストの実行
手っ取り早くテストの実行の方法は以下の2つのやり方がある

- 実際にブラウザが起動されてStepで実装した挙動をして、テストする
- コマンドラインで任意のコマンドによってテストする

ローカルでテストする場合は1つ目の方法でテストし、CI上でテストする等ブラウザを使わずにテストする必要がある場合は2つ目の方法でテストする
他にもタグをつけて細かくテスト対象を変えることなどもできるので色々調べてみてください

1つ目の方法でテストする場合は以下のコマンドでウィンドウが開く（今回はscriptを用意したのでそれを使用）

```sh
yarn cypress:open
```
<img width="912" alt="スクリーンショット 2019-11-19 17.53.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535721/8eb88d49-288d-62e2-5d7e-66d8a2d922c4.png">


ウィンドウが開いたら`Google.feature`をクリックするとテストが走ります
<img width="1312" alt="スクリーンショット 2019-11-19 17.51.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535721/1306306f-7951-cf7d-3487-4dec5639f964.png">


2つ目の方法で実行する場合は

```sh
yarn cypress:run
```
ちなみにコマンドで動かした場合`/cypress/videos`以下にビデオが追加され、ブラウザでの挙動がビデオで確認できる
必要ない場合は設定で追加しないようにできる

## 参考. ディレクトリについて
最初のdry runをしてウィンドウを閉じたらデフォルトで以下のようにディレクトリが作成される

```:ディレクトリ構成
e2e
 └─cypress
      ├─fixtures
      │    └─examples
      ├─integration
      │    └─examples
      ├─plugins
      └─support
```
とりあえず大事なのは`cypress/fixture`と`cypress/integration`

- `cypress/fixture`  : `*.json`ファイルに複数のテストで使用するサンプルデータを用意しておくことができる
- `cypress/integration`  : 実際にテストを記述するディレクトリ
- `examples`はただの例なので安全に削除可能

### fixturesについて

```json:例　fixture/foo.json
{
    "name": "foo"
}
```
このように用意しておくと次のようにするとテストで呼び出せる

```typescript
// テストの中で
cy.fixture('foo').then((fooJson) => {
    ...
})
```
 - 文法的には`cy.fixture(filePath)`で`filePath`はデフォルトで`cypress/fixture`
 - `fooJson`に`foo.json`のデータが入る
 
呼び出しの方法は他にもありますが、詳細は省きます

## 参考にしたサイト
https://www.cypress.io/
https://docs.cypress.io/api/api/table-of-contents.html
https://github.com/TheBrainFamily/cypress-cucumber-preprocessor
https://typescript-jp.gitbook.io/deep-dive/intro-1/cypress
https://www.kaizenprogrammer.com/entry/2017/12/23/144242