CypressをTypeScript × Reactで動かす

最近、フロントエンド界隈で人気上昇中のE2EテストフレームワークのCypressをTypeScript × React環境に導入したので、備忘録も兼ねて、TypeScript × ReactへのCypress導入方法をシェアしたいと思います。

## Cypressについて
「Cypressって一体何？」という方は、公式サイトを含めた、下記の記事などをご参照ください。

- [公式サイト](https://www.cypress.io/)
- [cypressを触ってみた](https://qiita.com/okitan/items/b44882e28006c1be32b7)
- [Web UI の自動テストツール Cypress のご紹介](https://qiita.com/kkakizaki/items/4b6625a56fd2fa4c741c)

本記事では、Cypress自体についてや、他のE2EテストフレームワークのTestCafeと比較した点などについてはあまり言及しません。

が、一応、トレンドに関しては、[npm trends](http://www.npmtrends.com/cypress-vs-testcafe)で調べてみましたので、現時点（2018年1月26日）での結果をおまけ的に付けておきます。

<img width="850" alt="スクリーンショット 2018-01-26 22.00.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/199911/87cfaee5-5b2f-d6c9-9c52-a7335bf4e649.png">


## 実行環境
- Mac OS Sierra: v10.12.6
- node: v8.2.0
- npm: v5.3.0

## TypeScript × React環境を作成
[node.js](https://nodejs.org/ja/)がインストールされていることが前提です。
`create-react-app`を用いて、Reactアプリを構築します。

```
$ cd ~/
# TypeScriptによるReactアプリをmy-appディレクトリに作成
$ create-react-app my-app --scripts-version=react-scripts-ts
# アプリの起動を確認
$ cd my-app
$ npm start
```

アプリの起動まで確認できたら、`Ctrl + C`でアプリを一旦、終了してください。

## Cypressをインストール
[公式ドキュメント](https://docs.cypress.io/guides/getting-started/installing-cypress.html#)に則り、上記で作成したプロジェクトにCypressをインストールします。

```
$ npm install cypress --save-dev
# Cypressの起動を確認
$ npx cypress open
```

Cypressの画面が立ち上がりますので、テストの対象として`example_spec.js`をクリックして、テストを開始します。

テスト終了後、`Ctrl + C`で、Cypressを一旦、終了します。

## アプリの起動を再確認
続いて、`npm start`でアプリを再起動しようとすると、次のようなコンパイルエラーが発生するかと思います。

```
Failed to compile.

/node_modules/@types/jest/index.d.ts
(18,13): Duplicate identifier 'beforeEach'.
```

これはインストールした型定義ファイルのところで問題が発生しているので、型定義ファイルのチェックを無視する設定を行います。

（もしくは、次のようなエラーが発生した場合には、`npm install`で、外部パッケージをインストールし直してから`npm start`を実行してください）

```
sh: react-scripts-ts: command not found
npm ERR! file sh
npm ERR! code ELIFECYCLE
npm ERR! errno ENOENT
npm ERR! syscall spawn
npm ERR! my-app@0.1.0 start: `react-scripts-ts start`
npm ERR! spawn ENOENT
npm ERR! 
npm ERR! Failed at the my-app@0.1.0 start script.
```

## 型定義ファイルのチェックを無視する設定に変更
コンパイルエラーを確認できたら、tsconfig.jsonをエディタで開き、`"skipLibCheck": true,`を`compilerOptions`に追加して保存します。

```tsconfig.json
  "compilerOptions": {
    ...
    "skipLibCheck": true,
    ...
```

`"skipLibCheck": true`の設定は、全ての型定義ファイル（.d.ts）の整合性チェックを行わないようにするオプションなので、次のリンク先で、Cypressの中の人がコメントされている通り、「理想的ではない」とされています。が、「結局のところオススメの解決策だ。」みたいに言われています。

> Confirmed, it was skipLibCheck I added to get around Jest "Set" problem. Ok, so the recommended solution is ultimately
> {
> 　"compilerOptions": {
>   　  　"skipLibCheck": true
> 　}
> }
> in the root tsconfig.json file. Not ideal, but it is hard or impossible to make globals work without clashing.

参考：[Possible type definition conflict on @types/chai](https://github.com/cypress-io/cypress/issues/1087#issuecomment-353370449)

取り敢えず、これでもう一度、`npm start`でアプリを起動し、コンパイルエラーが発生しないことを確認します。

コンパイルエラーが発生しないことを確認できたら、`Ctrl + C`でアプリを終了してください。

## テストコードをJSからTSに変更
ここまでで、`/cypress/integration`以下に存在するテストコードが、
JSファイルのままで良ければこれで終わりですが、TypeScript環境なので、やはりTSファイルとしてテストコードを書いていきたいところです。

試しに、サンプルのテストファイル（/cypress/integration/example_spec.js）の拡張子を`.js`から`.ts`に変更し、ついでに次の内容でサンプルのテストコードを上書きし、簡略化して保存します。

```/cypress/integration/example_spec.ts
describe('Kitchen Sink', function () {
  it('.should() - assert that <title> is correct', function () {
    cy.visit('https://example.cypress.io');
    cy.title().should('include', 'Kitchen Sink');
  });
});

```

しかし、この状態でアプリを再起動しようとすると、次のようなエラーが発生するかと思います。

```
Failed to compile.

/cypress/integration/example_spec.ts
(17,5): Cannot find name 'cy'.
```

これはTypeScriptのコンパイル時において、`example_spec.ts`に書かれているテストコードで、インストールされた型定義ファイル（`/node_modules/cypress/types/index.d.ts`）から、CypressのAPIにアクセスするための定数`cy`を読み込んで使おうとしますが、型定義ファイルが参照できず、`cy`が見つからないことでエラーになってしまっています。

こちらのエラーを回避するためには、型定義ファイルもコンパイルの対象に含めるようにします。

## TypeScriptのコンパイル設定を変更
プロジェクトルートに存在する`tsconfig.json`をエディタで開き、compilerOptionsに並ぶ形で、`include`（コンパイルに含めるもの）と`exclude`（コンパイルに含めないもの）の設定を追加変更します。

`create-react-app`でアプリを作成した場合は、デフォルトで`exclude`に`node_modules`が指定されてしまっているので、コメントアウトまたは削除で、指定から外しておきます。

```tsconfig.json
{
  "compilerOptions": {
    ...
  },
  "include": [
    "node_modules/cypress/types/*.ts",
    "cypress/*/*.ts"
  ],
  "exclude": [ 
    // "node_modules",
    ...
  ]
}
```

参考：[Typescript with Webpack](https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/preprocessors__typescript-webpack#typescript-with-webpack)

設定を保存し、アプリを再起動します。

アプリの再起動が確認できたら、アプリを終了し、`npx cypress open`で、Cypressの起動〜テスト実行も確認します。

これで無事に、TypeScriptで書いたテストコードでCypressが動くようになるはず、です。

ここまでのコードは、次の場所に置いてあります。

https://github.com/galoi/react-typescript-cypress

git clone後、プロジェクトルートに移動して`npm install`でパッケージをインストールすると、アプリの起動〜Cypressのテストが実行可能です。

今後は、このような面倒くさい設定をしなくてもTypeScriptでテストが書けるように、TypeScriptのサポートを厚くしてくれることをCypressに期待しています。