E2EテストをCIでも気軽に利用しよう 〜 Cypress x Docker Compose

**2019/11/18追記 日本語フォント表示用にDockerfileを修正しました。**
# はじめに : Cypressの紹介

[Cypress](https://www.cypress.io/)は、無料で利用できるOSSのEnd-to-End(E2E)のテストツールです。以下のように自分のブラウザを利用してWeb UIテストを実行します。

![](https://paper-attachments.dropbox.com/s_E18370725F60D26B4A085E93547DF0E026713652865231E9CEBAAE2B7922E8A4_1573582976918_combined-selectors.a74f74c4.gif)


以下のようなコードを元に、実際にブラウザで動かしながら、直感的にテストをすることができます。JavaScriptでテストコードを記述できるので、利用しているJSerも多いです。

```javascript
describe('The Login Page', function () {
  beforeEach(function () {
    // reset and seed the database prior to every test
    cy.exec('npm run db:reset && npm run db:seed')
    // seed a user in the DB that we can control from our tests
    // assuming it generates a random password for us
    cy.request('POST', '/test/seed/user', { username: 'jane.lane' })
      .its('body')
      .as('currentUser')
  })

  it('sets auth cookie when logging in via form submission', function () {
    // destructuring assignment of the this.currentUser object
    const { username, password } = this.currentUser
    cy.visit('/login')
    cy.get('input[name=username]').type(username)
    // {enter} causes the form to submit
    cy.get('input[name=password]').type(`${password}{enter}`)
    // we should be redirected to /dashboard
    cy.url().should('include', '/dashboard')
    // our auth cookie should be present
    cy.getCookie('your-session-cookie').should('exist')
    // UI should reflect this user being logged in
    cy.get('h1').should('contain', 'jane.lane')
  })
})
```
参考URL : https://docs.cypress.io/guides/getting-started/testing-your-app.html#Logging-in

# E2EテストをCIサーバで実行する

ローカルブラウザでCypressを利用すると捗るのですが、CIへの組み込みがなかなか進まず、専用のマシンを用意しておこうか、と思ったところCypressの公式コンテナがあることに気が付きました。

そして少しのコードで専用マシンなどを用意せず、以下の構成でCIのフローに乗せることができました。

![](https://paper-attachments.dropbox.com/s_E18370725F60D26B4A085E93547DF0E026713652865231E9CEBAAE2B7922E8A4_1573584477451_.png)

# 追加したファイルの紹介

追加したファイルは以下の3つです。

## フロントエンド用のDockerfile
全環境共通のDockerfileです。環境変数で環境ごとの情報を管理します。

```Dockerfile
# ファイルをビルド
FROM node:13.1.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY . /app
RUN npm install --silent
RUN npm run build

# nginxコンテナにビルドしたファイルをコピー
FROM nginx:1.16.0-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

## Cypress用Dockerfile

```Dockerfile
FROM cypress/base:10
# 日本語フォント表示用にnoto-cjkを追加
RUN apt-get install --no-install-recommends -y fonts-noto fonts-noto-cjk
WORKDIR /app
COPY package.json .
COPY package-lock.json .
ENV CI=1
RUN npm ci
RUN npx cypress verify
```

## docker-compose.yml
```yaml
version: '3'
services:
  web:
    image: frontend:latest
    ports:
      - "8080:80"
  e2e:
    build:
      context: .
      dockerfile: ./Dockerfile
    command: npx cypress run
    depends_on:
      - web
    environment:
      - CYPRESS_baseUrl=http://web
      - CYPRESS_browser=chrome
      - CYPRESS_screenshotsFolder=/results/screenshots
      - CYPRESS_videosFolder=/results/videos
    volumes:
      - ./cypress:/app/cypress
      - ./cypress.json:/app/cypress.json
      - ./results:/results
```
# docker-compose.yamlの解説
[CYPRESS_xxxx](https://docs.cypress.io/guides/references/configuration.html#Environment-Variables) の環境変数部分でConfigurationを設定しています。CYPRESS_browserという変数もありますが、現在選べるのは、[chrome/chromium/canaryのいずれか](https://docs.cypress.io/guides/guides/launching-browsers.html#Chrome-Browsers)です。それ以外のブラウザはサポートされません。

ローカルで `docker-compose up` を実行すれば、マウントされた `results/videos` 以下にスクリーンショットと、動作時の録画データが入ります。


![/results/screenshots に出力される録画データ。 (mp4で出力されたものをgifに変換)](https://paper-attachments.dropbox.com/s_E18370725F60D26B4A085E93547DF0E026713652865231E9CEBAAE2B7922E8A4_1573132089515_screencast.gif)
※ /results/screenshots に出力される録画データ。 (mp4で出力されたものをgifに変換)

CI時、テストが失敗したら終了したい場合、以下のコマンドを利用します。

```shell
docker-compose up --abort-on-container-exit --exit-code-from e2e
```

CI上で試したいときは、Slackなどにファイルを添付して渡すと、原因究明がはかどります。


# 最後に : 実際の利用シーン

Google Chromeがインストールされていない環境でも、簡単にテストが行えるようになりました。

新しくテストコードを書く時は、実際にブラウザで操作しながら書く方が楽なので、ローカルでも動くようにしています。

以下のようなフローでテストを追加しているという一例です。

1. `npx cypress open` を実行して、ローカルブラウザでテストを書く
2. docker-compose upを実行すると、コンテナ内でテストが自動実行される

リクエストが多ければGitHubにひな形を作成するので、興味がある方はリクエストをください。