Cypress × Firebase × Angular の組み合わせのE2EをGitHub Actionsで実行する

前回はCypress × Firebase × Angularの組み合わせのアプリケーションのe2eをLocalで回す方法を説明しました。今回はで開発環境でe2eを実行し、通った場合に、staging環境にデプロイするGitHub Actionsのパイプラインを構築します。 またモノレポ想定でアプリケーションのsourceも、e2eのsourceも同じプロジェクト内にいる構成になっています。 今回のコードはここにおいています。

開発・staging 環境の準備

今回はfirebase hostingを使ってアプリケーションを公開します。環境の切り替えはfirebaseのプロジェクトを複数作ることで実現します。つまり開発環境用プロジェクト、staging環境用プロジェクトを作ってしまって、そこにアプリケーションをデプロイしe2eを回します。なのでプロジェクトを作成していない方はfirebaseのプロジェクトを作成し、firestoreとhostingを実行できるようにしておいてください。

Angularのアプリケーションなので、環境の切り替えはファイルで行うことになります。こちらのサイトを参考にしながらangular.jsonに設定を追加することで様々な環境設定ファイルを切り替えられるようになります。今回はdevとstagingを用意しています。 ファイルで環境を切り替えられるのですがfirebaseのシークレットの情報はrepositoryにpushすべきではないので、pipelinde実行するには少しトリッキーな方法を使う必要があります。このあたりは後ほど解説します。

パイプラインの流れ

大きな流れは以下のようになっています。 f:id:harada-777:20210311202035p:plain

開発環境にデプロイ

まずは開発環境にデプロイする方法について解説します。開発環境にデプロイする用のymlは以下のようになります。

// $PROJECT_ROOT/.github/workflows/pipeline.yml
name: new-app-e2e-pipeline
on: [push]
jobs:
  deploy-to-dev-for-e2e:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v2
      - uses: actions/setup-node@v1
      - run: npm install -g firebase-tools
      - name: Decrypt secret
        run: ./decrypt_secret.sh environment.dev.ts
        working-directory: ./.github/scripts
        env:
          LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }}
      - name: Move secret
        run: mv $HOME/secrets/environment.dev.ts ./new-app/src/environments
      - name: Build and Deploy to dev
        run: ./build_and_deploy.sh dev
        shell: bash
        working-directory: ./enviroments/firesbase
        env:
          FIREBASE_TOKEN: ${{ secrets.SERVICE_ACCOUNT }

はじめの部分ではpushがトリガーであることやfirebase-toolsのinstallをしています。 Decrypt secretMove secret部分は少しややこしいので解説をします。Angularは基本的に環境変数を受け取れない (僕が知る限り) ので、環境ごとの設定の切り替えはファイルで行います。しかし、GitHub Actionsでは安全にファイルをアップロードする仕組みは特にありません。なのでenv ファイルを暗号化してpushを行い、パイプライン上で複合化する必要があります。 暗号化したいファイルはgpgおよびAES256暗号アルゴリズム使って以下のように行います。

gpg --symmetric --cipher-algo AES256 my_secret.json

この時聞かれるパスフレーズは覚えておいてください。 複合化は以下のdecrypt_secret.shを使っています。

// $PROJECT_ROOT/.github/scripts/decrypt_secret.sh
#!/bin/sh
FILE_NAME=$1

# Decrypt the file
mkdir $HOME/secrets
# --batch to prevent interactive command
# --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt --passphrase="$LARGE_SECRET_PASSPHRASE" \
--output $HOME/secrets/$FILE_NAME "${FILE_NAME}.gpg"

$LARGE_SECRET_PASSPHRASEは暗号化する際に聞かれたフレーズを指します。これを予めGitHubのsecretsとして登録しておくことで、環境変数として取り出すことができます。登録方法や詳しい、このあたりの流れはこちらを参考にしてください。

Move secretは複合化されたenv fileをangularのenvironmentsディレクトリに移動させています。 ちなみにちょくちょく見受けられるworking-directory:~はコマンドが実行されるディレクトリ を指定します。

Build and Deploy to devでは予め作成したAngularのアプリをビルドして、firebaseのプロジェクトにデプロイしてくれるシェルを叩いています。

$PROJECT_ROOT/environments/firebaseでfirebase initを行い、firebaseの設定ファイル生成させてbuild、deployを行うシェルをおいています。その中では$PROJECT_ROOT/environments/new-appにあるbuild用のシェルを実行するようにしています。

deployを行うシェルではfirebase deployを実行しているのですが、その際にFIREBASE_TOKENという環境変数が必要となります。これはlocalでfirebase login:ciを叩き、ログインをすると返却されるトークンで、これをsecretとして登録する必要があります。(pipeline.ymlのFIREBASE_TOKEN: ${{ secrets.SERVICE_ACCOUNT }の部分) またfirebase use --addで各環境にdev、stagingというaliasを貼っています。 aliasについては詳しくはこちらをご覧ください。

//  $PROJECT_ROOT/environments/firebase/build_and_deploy.sh
## !/usr/bin/env bash
set -e
env=$1
env=${env:-"dev"}
src=sources
cwd=$(realpath $(dirname $0))
project_root=$(git rev-parse --show-toplevel)

cd ../tuning-front
./build.sh "${env}"
cd ${cwd}

echo "deploy to ${env}"
firebase deploy --project ${env}

rm -rf "${cwd}/public/"
// $PROJECT_ROOT/enviroments/new-app/build.sh
## !/usr/bin/env bash
set -e
env=$1
env=${env:-"dev"}
echo "build ${env} mode"
src=sources
cwd=$(realpath $(dirname $0))
project_root=$(git rev-parse --show-toplevel)

rm -rf "${cwd}/${src}"
echo "copy project.."
cp -R "${project_root}/new-app" "${cwd}/${src}"
cd "${cwd}/${src}"
rm -rf dist/
ls src/environments

echo "build project.."
npm install -g @angular/cli@11.0.1
npm install
ng build -c "${env}"

echo "copy output to firebase public.."
cp -R "${cwd}/${src}/dist/new-app/" "${project_root}/enviroments/firesbase/public"/
rm -rf "${cwd}/${src}"
e2eの実行

e2e実行用のymlは以下のようになります。

   execute-e2e:
    runs-on: ubuntu-20.04
    needs: deploy-to-dev-for-e2e
    steps:
      - uses: actions/checkout@master
      - name: Decrypt secret
        run: ./decrypt_secret.sh serviceAccount.json
        working-directory: ./.github/scripts
        env:
          LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }}
      - name: Move secret
        run: mv $HOME/secrets/serviceAccount.json ./e2e
      - name: Create videos and screenshots directory
        working-directory: ./e2e/cypress
        run: |
          mkdir videos && \
          mkdir screenshots
      - name: Cypress Run
        uses: cypress-io/github-action@v2
        with:
          working-directory: e2e
          start: npm run cypress:run
          wait-on: https://new-app-dev-edb1d.web.app
          browser: chrome
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
          FIREBASE_TOKEN: ${{ secrets.SERVICE_ACCOUNT }}
          GITHUB_HEAD_REF: ${{ github.head_ref }}
          GITHUB_REF: ${{ github.ref }}
      - uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: cypress-screenshots
          path: e2e/cypress/screenshots
      - uses: actions/upload-artifact@v1
        if: failure()
        with:
          name: cypress-videos
          path: e2e/cypress/videos

基本的にはcypresの公式を参考にしています。 needs: deploy-to-dev-for-e2eは前のジョブが終わってから行うという意味の記述です。これがないと並列で動いてしまいます。 Decrypt secretMove secretでは予め暗号化した、serciceAccount.jsonを複合化し、所定のディレクトリに移動させています。このファイルはcypressがfirestoreなどを触るために必要な認証情報です。

Create videos and screenshots directoryではcypressがテスト時にscreenshotやvideoを記録するためのディレクトリ を作成しています。(公式では記述を見つけることができなかった、これがないとe2eがコケるので作成しました。)

Cypress Runではテストを実際に実行しています。 npm run cypress:runでテストが実行できるよう、e2eディレクトリのpackage.jsonに以下のように記述しています。

"cypress:run": "cross-env cypress run"

その後の部分はテストが失敗した際にvideosとscreenshotsをアップロードする設定をしています。

stagingへのデプロイ

以下がstaginへのリリース部分です。

  deploy-to-staging:
    runs-on: ubuntu-20.04
    needs: execute-e2e
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v2
      - uses: actions/setup-node@v1
      - run: npm install -g firebase-tools
      - name: Decrypt secret
        run: ./decrypt_secret.sh environment.staging.ts
        working-directory: ./.github/scripts
        env:
          LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }}
      - name: Move secret
        run: mv $HOME/secrets/environment.staging.ts ./tuning-front/src/environments
      - name: Build and Deploy to staging
        run: ./build_and_deploy.sh staging
        shell: bash
        working-directory: ./enviroments/firesbase
        env:
          FIREBASE_TOKEN: ${{ secrets.SERVICE_ACCOUNT }}

needs: execute-e2eにより、execut-e2eが終わった後に実行されます。またe2eが失敗するとこのジョブは実行されません。

まとめと感想

Cypress × Firebase × Angular の組み合わせのE2EをGitHub Actionsで実行する方法を解説しました。改善点として、ファイルを暗号化してアップロードしている部分をできれば環境変数でなんとかでしたいなぁという感じです。もしいい方法あれば是非お願いします!