CypressOnRails導入手順

# はじめに
E2Eテスト[^1]を行うにあたって、フロントエンドのE2Eテストツールの[Cypress](https://www.cypress.io/)を導入していましたが、例えばユーザーにログインをさせ、ユーザー固有のページを表示させるようなwebシステムでは、**データベースに格納されている値に応じて動的に**画面表示を切り替える事が必要となります。

その時に、Cypressだけでは、データベースの事前テストデータ生成を行った状態でのテストを、テストケース単位で行う事が容易にはできません。

これでは、どうしてもE2Eテストで検証できるパターンが少なくなってしまうため、
データベースの事前テストデータ生成をテストケース単位で実行できるツールとして、
[CypressOnRails](https://github.com/shakacode/cypress-on-rails)を導入する事に決めました。

今回の記事では、CypressOnRailsの導入方法についてまとめたいと思います。

# 環境について
Dockerを用いているため、frontendコンテナ・backendコンテナが存在しています。（他にも存在しますが、今回関係ないため省略します）

開発環境で起動するサーバーは以下を条件とします。

- frontendコンテナは`localhost:8080`で起動
- backendコンテナは`localhost:3000`で起動

バージョンについては以下となります。

- backend
    - ruby 3.2.2
    - rubyonrails 7.0.5
- frontend
    - vue 3.2.13
    - vite 3.2.0

# 前提
ある程度フロントエンドのE2Eテストツールの[Cypress](https://www.cypress.io/)でテストを実施した事がある人向けの内容となります。

また、Docker環境でfrontendコンテナ・backendコンテナを起動しているので、
CypressOnRailsもe2eコンテナを実行環境とし、[xquartz](https://www.xquartz.org/)というxサーバーを用いて`yarn cypress open`させてみたのですが、以下理由により、一旦導入をやめています。

- `yarn cypress open` でブラウザを起動するまでの設定が手間である
- ブラウザを起動した場合カーソルの動作が重たい

そのため、本記事の前半では、
単純にmacの環境で`yarn cypress open`するまでの手順を記載します。

ただ、今後CIツールでcypressを実行する際には、e2eコンテナで`yarn cypress run`させる事が必要となってくるので、本記事の後半部分では、Dockerコンテナ内で`yarn cypress run`を実行するまでの導入手順についても記載します。（なのでコンテナを実行環境としたブラウザの起動までは行いません）


# mac環境で構築する手順
まずは、mac環境で`yarn cypress open`するまでの手順を記述します。

# インストール
Gemfileに以下を記述し、`bundle install`します。

```ruby
group :test, :development do
  gem 'cypress-on-rails', '~> 1.0'
end
```

# セットアップ
以下コマンドにて、必要なファイルをgenerateします。
私の場合、テスト関係のファイル群はspecディレクトリの中で管理したかったので、以下コマンドを実行しました。

```
bin/rails g cypress_on_rails:install　--cypress_folder=spec/cypress
```

そうすると、railsプロジェクトのspecディレクトリ内にcypressディレクトリが生成されます。

次に、`database.yml`を編集します。

CypressOnRailsのドキュメントを見ると、環境変数に `CYPRESS=1` が設定されていると、テスト用のデータベースを参照する仕組みのようです。
Cypressを起動してブラウザを操作する際はdevelopment環境でのテスト実行となるが、実際のデータベースはtest環境を参照する事で、FactoryBotにより生成したテストデータを利用できる形となるようです。

```database.yml
development:
  <<: *default
  database: <%= ENV['CYPRESS'] ? 'my_db_test' : 'my_db_development' %>
test:
  <<: *default
  database: my_db_test
```

次にspecディレクトリ内にpackage.jsonを作成し、以下記述します。

```package.json
{
  "devDependencies": {
    "cypress": "^12.10.0",
  }
}
```

specディレクトリにて、以下実行します。
```
cd spec
```

```
yarn install
```

あとは、私の場合[dotenv-rails](https://rubygems.org/gems/dotenv-rails/versions/2.6.0?locale=ja)を用いてbackendのrailsアプリケーション側の環境変数を管理しているため、`.env`内に以下追加し、railsのサーバーを再起動します。

```.env
CYPRESS=1
```

この状態で、specディレクトリにて、以下コマンドを実行すると、cypressのテストブラウザを起動する事ができます。

```
yarn cypress open
```

これにより、以下流れで**データベースの事前テストデータ生成をテストケース単位で実行できる**状態となります。

- Cypress側でフロントエンド側のサーバーlocalhost:8080にvisitする
- backend側のlocalhost:3000にリクエストすると、test環境のデータベースを参照する形となる
- cy.jsファイル内でFactoryBotテストデータ生成の記述を行う事で、テストケース実行時に事前テストデータが生成されている状態となる

# Docker環境で構築する手順
次に、Docker環境で`yarn cypress run`するまでの手順を記述します。

# インストール
Gemfileへの記述は同様です。コンテナ内で`bundle install`します。

```ruby
group :test, :development do
  gem 'cypress-on-rails', '~> 1.0'
end
```

# セットアップ
以下はmac環境での構築手順と同じです。

```
bin/rails g cypress_on_rails:install　--cypress_folder=spec/cypress
```

```database.yml
development:
  <<: *default
  database: <%= ENV['CYPRESS'] ? 'my_db_test' : 'my_db_development' %>
test:
  <<: *default
  database: my_db_test
```

```package.json
{
  "devDependencies": {
    "cypress": "^12.10.0",
  }
}
```

次に、`spec/Dockerfile.cy` を作成します。
コンテナのイメージは以下を参照ください。

私の場合、当初Dockerコンテナを実行環境として`yarn cypress open`する事も目的としていたため、cypress/browsersイメージを用いています。

https://hub.docker.com/r/cypress/browsers/tags

```Dockerfile.cy
FROM cypress/browsers:node-18.16.0-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1

ARG WORKDIR

ENV HOME=/${WORKDIR}

WORKDIR ${HOME}

RUN apt-get update && \
  apt-get install -y --no-install-recommends fonts-noto fonts-noto-cjk

COPY . ./

RUN yarn install
```

docker-compose.ymlの設定は以下です。

```docker-compose.yml
# WORKDIRはシステム構成に合わせて設定する
services:
  backend:
    build:
      context: ./backend
      args:
        WORKDIR: $WORKDIR
    ...
    ...
    ports:
      - "3000:3000"
    networks:
      app_net:
        ipv4_address: "172.20.0.2"
    depends_on:
    ...

  frontend:
    build:
      context: ./frontend
    ...
    ports:
      - "8080:8080"
    networks:
      app_net:
        ipv4_address: "172.20.0.3"
    depends_on:
      - backend  

  e2e:
    build:
      context: ./backend/spec
      dockerfile: Dockerfile.cy
      args:
        WORKDIR: $WORKDIR/spec
    platform: linux/amd64
    networks:
      app_net:
        ipv4_address: "172.20.0.4"
    depends_on:
      - backend

networks:
  app_net:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 172.20.0.0/24
```
それぞれのコンテナをbridgeで繋ぎ、ネットワークを構成しています。
なので、cypress側の設定ファイルそれぞれに関して、localhostではなく、IPを指定してあげれば設定完了となります。

※インストール時、設定ファイルなどのファイルはデフォルトではjsファイルで生成されますが、その後TypeScriptを導入したため、tsファイルで今回記述しています。ご容赦ください。

```cypress.config.ts
import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    baseUrl: "http://172.20.0.2:3000",
    supportFile: "cypress/support/index.ts",
  },
  env: {
    // frontendページURL用の環境変数
    VITE_APP_URL: "http://172.20.0.3:8080",
  }
})

```

```support/onrails.ts
Cypress.Commands.add('appCommands', function (body) {
  Object.keys(body).forEach(key => body[key] === undefined ? delete body[key] : {});
  const log = Cypress.log({ name: "APP", message: body, autoEnd: false })
  return cy.request({
    method: 'POST',
    // 以下を修正する
    url: "http://172.20.0.2:3000/__cypress__/command",
    body: JSON.stringify(body),
    log: false,
    failOnStatusCode: false
  }).then((response) => {
    log.end();
    if (response.status !== 201) {
      expect(response.body.message).to.equal('')
      expect(response.status).to.be.equal(201)
    }
    return response.body
  });
});

...
...
```

あとはDockerのbuildがうまく完了すれば、構築は終了となります。

# Cypress実行
以下コマンドで、Cypress用実行環境を起動し、CypressOnRailsによるe2eテストがheadlessで実行できます。
```
docker-compose run e2e yarn cypress run
```

# まとめ
ひとまず、CypressOnRailsを用いて、データベースの事前テストデータ生成をテストケース単位で実行するための準備として、今回の記事を書きました。次の機会には、実際に`事前テストデータ生成の書き方`についてまとめてみたいと思います。

[^1]: 利用者の観点に立ったWebブラウザの操作により、システム全体が一貫して想定通りに動作しているかを確認すること