虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

GitHub ActionsでCypressを並列実行して早く終わらせた #虎の穴ラボ Advent Calendar 2023

  • 本記事は 虎の穴ラボ Advent Calendar 2023 16日目の記事です。(予約投稿)
  • 前回は godanさんの「Akihabara入門 at akihabara」でした。
  • 次回は 中村さんの「GPT-4Vを用いた業務活用例」です。 ご期待ください!

こんにちわ、虎の穴ラボのH.Y.です。 今日は、今のプロジェクトで自分が行ったCypressの並列実行について話したいと思います。

Cypressとは

Cypressは直感的で使いやすいエンドツーエンド(E2E)テストフレームワークです。 テストの記述から実行までの流れが簡素でわかりやすく、デバッグも容易です。 ローカル上でリアルタイムにテスト結果閲覧や操作の自動待機機能により、テスト実行の手間が大幅に削減されます。 また複数ブラウザ対応やネットワークリクエストの制御も可能で、テストの範囲と詳細さを自在に調整できます。

構成

フロントエンド:Next.js

  • ビルド:npm
  • テスト:Cypress

バックエンド:Kotlin

  • ビルド:gradle
  • 自動テスト:JUnit

GitHub Actionsでプルリクが作成されるたびにビルド、自動テストを実行という構成です。 mainブランチにマージするには自動テストに通らなければマージできないという仕組みです。

問題点

今回の主題となるフロントエンドのCypressによるテストですが、テストが増えるたびに実行時間が伸びます。 当初3分だったのが今では13分とかなり伸びました。 また、mainブランチに新しいコミットが入るたびに、その変更を取り入れてCypressのテストを再度実行する必要があるので、 かなり時間が掛かってしまいます。

検討

Cypressの時間短縮には、テストの並列実行が考えられます。 しかし並列実行には、CypressのTeamプラン(67USD/月)以上に入る必要があり、加えて1000テストごとに6USD必要なので、なかなかのコストがかかります。

これをもっと安くできないかと考えた結果

GitHub Actionsのmatrixを利用することで実質並列実行にならないかと考えました。 matrixの本来の使い方はnode.jsの異なるバージョンとかOSを同時にテストすることに使うのですが、 これを使ってテストファイルごとにCypressを実行し、時短するという方法です。

単純計算だとテストファイルが33ファイルあるので33並列で実行できるということで速くなりそうです。

実装

まず、プロジェクトの中にあるCypressのテストコード(.cy.tsx)のファイル一覧を取得し、 そのファイル数だけmatrixの部分で同時実行させます。

  # テストコードの一覧を取得する
  prepare_matrix:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.head_ref }}
      - name: Get test file list
        id: gettest
        run: |
          echo "::set-output name=list::$(find ./ -name '*.cy.tsx' -printf '%P\n' | jq -R -s -c 'split("\n")[:-1]' | jq -c '[.[] | {file: .}]')"
        shell: bash
    outputs:
      matrix: ${{ steps.gettest.outputs.list }}

ビルド部分です。 TypeScriptのためビルドしないと、Cypressが実行できないためビルドします。 ただ、後続処理にビルドしたファイルを渡す必要があるため、 ビルドしたファイルはactions/cache@v3でキャッシュします。

ここで、キャッシュしないとあんまり時短ならないのと、 ファイル数*ビルド時間(今回の場合だと2分)のGitHub Actionsの稼働料金をが取られるので、キャッシュした方が良いです。 keyにgithub.run_idを指定している理由は、この実行時のみのキャッシュにするためです。

  build:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./frontend/creator
    steps:
    - name: Checkout
      uses: actions/checkout@v3
      with:
        ref: ${{ github.head_ref }}
    - name: Cache node modules and Cypress binary
      uses: actions/cache@v3
      with:
        path: |
          **/node_modules
          ~/.cache/Cypress
        key: ${{ runner.os }}-modules-${{ github.run_id }}
    - name: Install
      run: |
        npm install
    - name: Lint
      run: |
        npm run lint
    - name: Cache build output
      uses: actions/cache@v3
      with:
        path: ./.next
        key: ${{ runner.os }}-nextjsbuild-${{ github.run_id }}
    - name: Build
      run: |
        npm run build
        npm run generate:client

ここが並列実行部分です。 テストファイルごとにCypressを実行しています。 buildで作ったキャッシュを利用してCypressを実行します。 matrix.fileで指定されたファイルのテストを実行しています。

  cypress-run:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./frontend/creator
    needs: prepare_matrix
    strategy:
      fail-fast: false
      matrix: 
        include: ${{fromJson(needs.prepare_matrix.outputs.matrix)}}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.head_ref }}
      - name: Cache node modules and Cypress binary
        uses: actions/cache@v3
        with:
          path: |
            **/node_modules
            ~/.cache/Cypress
          key: ${{ runner.os }}-modules-${{ github.run_id }}
      - name: Install
        run: |
          npm install
      - name: Cache build output
        uses: actions/cache@v3
        with:
          path: ./.next
          key: ${{ runner.os }}-nextjsbuild-${{ github.run_id }}
      - name: Run
        run: npm run test:component:headless -- --spec ../../${{ matrix.file }}

検証

改善前が11分

改善後が5分43秒

という感じで、5分ほどテストが短縮されました。 しかし、GitHub Actionsのトータル実行時間が13⇨53分と40分増加しました。

GitHub Actionsの課金が2CPUで0.008USD/分のため 0.008USD/分*40分 = 0.32USD(48円)増加でした。

1回200テストぐらいなので、Cypressの場合1.2USDです。 従量課金分でもこちらの方が安い感じです。

まとめ

Cypressでの並列実行ではなく、GitHub Actionsを使って、フロントエンドのテストの並列実行をしてみました。 結果的に、安く、速くなりました。

わかっている問題点として

  • ファイルごとなので、1ファイルに大量のテストがある場合、一番遅いファイルのテストに影響されるので注意が必要
  • GitHub Actionsの仕様で256並列以上では起動できないので注意が必要 です。

注意点を気をつければ、安く自動テストの時短になると思います。