docker composeでe2eテストの立ち上がりと終了を管理

# この記事は何か

e2eでよくある、代表的な「待つ」のうち

1. サービスが実行できるのを「待つ」
1. サイトのテスト部分が現れるのを「待つ」

1番目の、サービスが実行できるのを「待つ」を`docker-compose.yml`を使って管理する方法について書いていきます。

# 対象

- e2eテスト前に、何かしらサービスが実行できるまで「[wait-on](https://www.npmjs.com/package/wait-on)」などで、「待つ」処理を挟んでいる人

# やること
- `docker-compose`でe2eテストを実行
- `docker-compose.yml`の「[depends_on](https://docs.docker.com/compose/how-tos/startup-order/)」で立ち上がる順番を管理
- テスト実行するコンテナは別で立ち上げる
- テスト実行するコンテナが終わり次第、すべてのコンテナを終了させる（[--abort-on-container-exit](https://docs.docker.com/reference/cli/docker/compose/up/)）

# 取り扱わない事
- e2eテストフレームワークの詳細や実行内容

# メリット

- [wait-on](https://www.npmjs.com/package/wait-on)や自作shellで、dockerのサービスが「port開く待ち」の記述しなくてOK
- 今ある`docker-compose.yml`を大きく変えなくてOK
- テスト終わったら、テスト実行が終わったことを判定したり、テスト後の「終了」を記述するのも書かなくてOK

# サンプル `docker-compose.yml`

よくある、dbとwebの構成です。

今回、`web`は`next.js`で、テストフレームワークは`playwright`使っています

```yml:docker-compose.yml
services:
  db:
    image: postgres:17
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydatabase
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydatabase"]
      interval: 2s
      timeout: 3s
      retries: 5
    networks:
      - mynetwork

  web:
    build:
      context: .
      target: deps
    ports:
      - "3000:3000"
    volumes:
      - .:/usr/src/app
    depends_on:
      db:
        condition: service_healthy
    command: npx next dev
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
      interval: 2s
      timeout: 3s
      retries: 10
    networks:
      - mynetwork

volumes:
  pgdata:
```

## サービス実行コマンド

```bash
docker-compose up
```

を実行すると、`db`がヘルスチェック通り次第、`web`が立ち上がります。

healthチェックをserviceごとに書いておくと、順番が守られて立ち上がるようになります。

参考：https://docs.docker.com/compose/how-tos/startup-order/

## テスト実行コンテナを用意

```diff_yaml:docker-compose.yml
services:
  db:
    image: postgres:17
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: mydatabase
    ports:
      - '5432:5432'
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U user']
      interval: 2s
      timeout: 3s
      retries: 5
    networks:
      - mynetwork

  web:
    build:
      context: .
      target: deps
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
    depends_on:
      db:
        condition: service_healthy
    command: npm run dev
    healthcheck:
      test: ['CMD-SHELL', 'curl -f http://localhost:3000 || exit 1']
      interval: 2s
      timeout: 3s
      retries: 5
    networks:
      - mynetwork

+  e2e:
+    profiles:
+      - e2e-group
+    build:
+      context: .
+      target: test
+    volumes:
+      - .:/usr/src/app
+    depends_on:
+      web:
+        condition: service_healthy
+    environment:
+      - CI=true
+    command: npm run e2e
+    networks:
+      - mynetwork

volumes:
  pgdata:

networks:
  mynetwork:

```

```diff_docker:dockerfile
FROM node:22 AS deps

# Set the working directory
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install dependencies
RUN npm install

# HEALTHCHECK --start-period=5s CMD curl -fs localhost:3000/health || exit 1


+ FROM node:22 AS test

# Set the working directory
+ WORKDIR /usr/src/app

# Copy the node_modules from the deps stage
+ RUN npx playwright install --with-deps

# Copy the rest of the application code
+ COPY --from=deps /usr/src/app/node_modules ./node_modules

# Copy the rest of the application code
+ COPY . .

```

### ポイント

- `docker-compose.yml`でbuildのターゲット追加のほうが、既存のイメージなどに影響を与えず追加するだけでよくなるのでおすすめです
- `docker-compose.yml`で`profiles`を追加することで、既存のコマンドを変えなくて済みます


### テスト実行コマンド

`docker-compose.yml`にprofilesを追加したので、e2eをテストで実行するとき、下記コマンドになります。

```bash
docker compose --profile test-group up --abort-on-container-exit --exit-code-from e2e
```

`--exit-code-from service`を`e2e`に指定することで、そのサービスの終了コード（exit code）をメインプロセスの終了コードとして反映させるために使用されます。
このオプションを使用すると、テストの成功・失敗結果や、コンテナ内で実行した処理の終了コードを取得できます。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/reference/up/

profilesの指定がないservice + profilesで指定したグループが立ち上がるようになりました。


### 動作の流れ

```bash
docker compose --profile test-group up --abort-on-container-exit --exit-code-from e2e
```

db → web → e2e の順番で立ち上がる

e2eコンテナのテストが終わり次第、e2e、web、dbのコンテナが終了


### Github Actions

終了時のシグナルも流れてくるので（成功 → 0、異常 → 1）、Github Actionsでも条件わけして通知もできます

```.github/workflows/test.yml

name: Test

on:
  push:
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Test
        run: docker compose --profile test-group up --abort-on-container-exit --exit-code-from e2e

      - name: Notify Slack
        if: always() # ステータスに関わらず送信
        # if: success() # 成功したとき送信
        # if: failure() # 失敗したとき送信
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          fields: message,eventName,ref,workflow,job,took
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```

# 参考

- [docker](https://docs.docker.com/)
- [github actions status expression](https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#always)