Salesforceカスタムアプリの自動E2Eテスト: GitHub ActionsとPlaywrightで実現する夜間テスト戦略

Salesforceカスタムアプリの自動E2Eテスト: GitHub ActionsとPlaywrightで実現する夜間テスト戦略

SalesforceのsfコマンドとSandbox組織、GitHub Actions、そしてPlaywrightを活用することで、毎晩のE2Eテストの定期実行を行う環境を構築します。高い品質が求められるアプリケーションの開発においては、デイリーでE2Eテストが実行できることはとても心強い仕組みになります。
Clock Icon2024.11.26

情報システム室の進地@日比谷です。

クラスメソッドでは自社の請求に関するシステムをSalesforceに実装した締処理プログラムを中心に構成しています。長年の保守、運用を経て、この締処理プログラムの技術的負債の側面が無視できない状態となったため、現在、リプレイス処理を進めています。

請求の金額という非常にクリティカルなデータを扱うシステムであるため、QCDで最優先は品質です。開発に伴い壊れた機能がないか、速やかに、また繰り返し検知できることが必須です。

そこで、Salesforceにカスタムアプリケーションとして実装されるこのシステムのE2Eテストを毎晩定時実行する、という要件をGitHub ActionsとPlaywrightを利用して実現しました。

テスト用初期データを配備したSandbox組織を作成する

ステージング用途のSandbox組織を1つ用意します。

この組織には、

  • 開発中(=テスト対象)のカスタムアプリケーション
  • 必要なテストデータ
  • 必要なメタデータ

を作り込みます。

実際のE2Eテストでは、このSandbox組織を対象にはテストを実行しません。この組織は、あくまでテスト実行する別のSandboxの複製元として活用します。

こうすることで、開発メンバーはE2Eテストのことを勘案することなく、あくまでステージング環境としてこの組織を活用できます。

このステージング用Sandbox組織のエイリアスを、以下ではstagingと設定したものとします。

ステージング用Sandbox組織からテスト対象Sandbox組織を作成する

同じ組織ライセンス(例えば、Developer組織同士など)であれば、新規Sandbox作成時にclone(複製元)に既存Sandboxを指定することで、既存Sandboxのメタデータ、レコードをまるっと引き継ぐことができます

これを利用して、E2Eテスト対象のSandbox組織をsfコマンドで作成するには、次のようにします。作成するSandbox組織のエイリアスをe2e、本番組織のエイリアスをproductionとしています。

$ JOB_ID=$(sf org sandbox create --name e2e --alias e2e --clone staging -o production --async --no-prompt --json | jq -r ".result.Id")

ここでは--asyncオプションを指定することでSandbox組織の作成を非同期実行し、コマンドを即座に終了させています。jqを使ってこの処理のジョブIDを取得し、変数JOB_IDに格納しています。

Sandbox組織の作成には大体数時間はかかります。そこで、Sandbox組織の作成状態を確認するには先に取得したJOB_IDを使って次のコマンドを実行します。

$ MESSAGE=$(sf org resume sandbox --job-id $JOB_ID -o production --json | jq -r '.message')

これで変数MESSAGE完了という文字列が入っていればSandbox組織の作成は完了しています。
本来的には jq -r '.status' でステータスを見るのが正しそうですが、私の環境(MacOS + @salesforce/cli/2.65.8 darwin-x64 node-v20.5.0 )ではステータスの値は正しくSandbox組織の作成状況を示してはくれませんでした。

GitHub Actionsを活用して、毎晩テスト対象Sandboxに対してPlaywrightでE2Eテストを走らせる

GitHub Actionsを使ってPlaywrightのE2Eテストを定期実行する方法に関しては、

GitHub ActionsでPlaywright E2Eテストを定期実行し、結果をSlack通知する

を参照してください。

この記事では、E2Eテスト用のSandbox組織を作成し、そのSandbox組織に対してPlaywrightを走らせ、テスト後に不要なSandbox組織を削除する処理をGitHub Actionsで実現する方法を示します。

この要件を実現するには以下の制約を攻略する必要があります。

  1. Sandbox組織の作成には最低でも数時間かかるため、1つのワークフローではタイムアウトになる
  2. タイムアウトを回避するため、2つのワークフローで処理を構成するが、その場合、2つのワークフロー間で情報を受け渡す必要がある

これらを勘案したGitHub Actionsのワークフロー例を以下に示します。ワークフローは2つ作成します。

E2Eテスト実行環境のSandbox組織を作成するワークフロー

まず、E2Eテストを実行するSandbox毎日21時に作成するワークフローです。
.github/workflows/sandbox-creation.ymlとして定義します。

name: Create Salsforce Sandbox for E2E Testing

on:
  schedule:
    - cron: '0 12 * * *'  # 毎日21:00 JST (12:00 UTC)

env:
  SALESFORCE_USERNAME_BASE: ${{ secrets.SALESFORCE_USERNAME_BASE }}
  SALESFORCE_SECURITY_TOKEN: ''
  SALESFORCE_CLONED_SANDBOX_NAME: ${{ secrets.SALESFORCE_CLONED_SANDBOX_NAME }}
  SALESFORCE_CONSUMER_KEY: ${{ secrets.SALESFORCE_CONSUMER_KEY }}

jobs:
  create-sandbox:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Install Salesforce CLI
        run: npm install @salesforce/cli --global
      - name: Determine Sandbox to use
        run: |
          MONTH=$(date +%m)
          DAY=$(date +%d)
          SANDBOX_NUMBER=$((((MONTH * 31 + DAY) % 2) + 1))
          SANDBOX_NAME="${SALESFORCE_CLONED_SANDBOX_NAME}cl${SANDBOX_NUMBER}"
          echo "SANDBOX_NUMBER=$SANDBOX_NUMBER" > .env
          echo "SANDBOX_NAME=${SANDBOX_NAME}" >> .env
          echo "SALESFORCE_USERNAME=${SALESFORCE_USERNAME_BASE}.${SANDBOX_NAME}" >> .env
          echo "SALESFORCE_SECURITY_TOKEN=$SALESFORCE_SECURITY_TOKEN" >> .env
          echo "SALESFORCE_CLONED_SANDBOX_NAME=$SALESFORCE_CLONED_SANDBOX_NAME" >> .env
          echo "SALESFORCE_LOGIN_URI=https://yourdomain--${SANDBOX_NAME}.sandbox.my.salesforce.com/" >> .env
          echo "$MONTH $DAY" > date_info.txt
      - name: Create Sandbox
        run: |
          source .env
          echo "${{ secrets.SALESFORCE_JWT_SECRET_KEY }}" > e2etest.pem
          sf org login jwt --client-id $SALESFORCE_CONSUMER_KEY --jwt-key-file e2etest.pem --username $SALESFORCE_USERNAME_BASE --instance-url https://yourdomain.my.salesforce.com --alias production
          JOB_ID=$(sf org sandbox create --name $SANDBOX_NAME --alias $SANDBOX_NAME --clone $SALESFORCE_CLONED_SANDBOX_NAME -o production --async --no-prompt --json | jq -r ".result.Id")
          echo "JOB_ID=$JOB_ID" >> .env
      - name: Store Sandbox Info
        uses: actions/upload-artifact@v4
        with:
          name: sandbox-info
          include-hidden-files: true
          path: |
            .env
            date_info.txt
      - name: Clean up
        run: rm e2etest.pem

sfコマンドを使用するにあたっては、最初に組織にログインをする必要があるため sf org sandbox create する前に

sf org login jwt --client-id $SALESFORCE_CONSUMER_KEY --jwt-key-file e2etest.pem --username $SALESFORCE_USERNAME_BASE --instance-url https://yourdomain.my.salesforce.com --alias production

で本番組織にログインしています。ログインはOAuth 2.0 JWT Bearer Flowで行っています。こちらの詳しい方法(鍵の作り方やSalesforceでの設定方法)は

Lambda(Python)からSalesforce APIを自己証明書を使って安全にコールしてみた

の記事を参照してください。

また、日付の情報を.envの情報を次のワークフローに引き継ぐために、artifactとしてアップロードしています。.envをartifactに含めるためにinclude-hidden-files: trueを指定していることに注意してください。

作成したSandbox組織でE2EテストをPlaywrightで実行するワークフロー

次に、実際にPlaywrightで作成したSandbox組織でE2Eテストを実行するワークフローです。
.github/workflows/exec-e2etest.ymlとして定義します。

Sandbox組織作成を行うワークフローの6時間後(夜中の3時)に走らせます。
Sandbox組織の作成にかかる時間は数分から数日まで幅があり、明示されないのですが、大体の場合、弊社の環境では数時間で完了しているので実運用上は6時間の間隔で問題ないと判断しました。

name: Execution E2E Test and Cleanup

on:
  schedule:
    - cron: '0 18 * * *'  # 毎日3:00 JST (18:00 UTC)

env:
  SALESFORCE_USERNAME_BASE: ${{ secrets.SALESFORCE_USERNAME_BASE }}
  SALESFORCE_CONSUMER_KEY: ${{ secrets.SALESFORCE_CONSUMER_KEY }}

jobs:
  e2e-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Install Salesforce CLI
        run: npm install @salesforce/cli --global
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Download Sandbox Info
        uses: dawidd6/action-download-artifact@v6
        with:
          github_token: ${{ secrets.WORKFLOW_PAT }}
          workflow: sandbox-creation.yml
          name: sandbox-info
          workflow_conclusion: success
      - name: Determine Sandbox Names
        run: |
          source .env
          read MONTH DAY < date_info.txt
          SANDBOX_NUMBER=$((((MONTH * 31 + DAY) % 2) + 1))
          OLD_SANDBOX_NUMBER=$((3 - SANDBOX_NUMBER))
          OLD_SANDBOX_NAME="${SALESFORCE_CLONED_SANDBOX_NAME}cl${OLD_SANDBOX_NUMBER}"
          echo "OLD_SANDBOX_NAME=$OLD_SANDBOX_NAME" >> .env
          echo "SF_OLD_USERNAME=${SALESFORCE_USERNAME_BASE}.${OLD_SANDBOX_NAME}" >> .env
      - name: Check Sandbox Status
        run: |
          source .env
          echo "${{ secrets.SALESFORCE_JWT_SECRET_KEY }}" > e2etest.pem
          sf org login jwt --client-id $SALESFORCE_CONSUMER_KEY --jwt-key-file e2etest.pem --username $SALESFORCE_USERNAME_BASE --instance-url https://yourdomain.my.salesforce.com --alias production
          MESSAGE=$(sf org resume sandbox --job-id $JOB_ID -o production --json | jq -r '.message')
          if [[ $MESSAGE == *"完了"* ]]; then
            echo "SANDBOX_READY=true" >> .env
          else
            echo "SANDBOX_READY=false" >> .env
          fi
      - name: Run E2E Tests
        run: |
          source .env
          if [ "$SANDBOX_READY" = "true" ]; then
            npx playwright test
          else
            echo "Sandbox is not ready. Skipping E2E tests."
            exit 1
          fi
      - name: Delete Old Sandbox
        run: |
          source .env
          if [ "$SANDBOX_READY" = "true" ]; then
            sf org delete sandbox -o $OLD_SANDBOX_NAME --no-prompt
          fi
      - name: Clean up
        run: rm e2etest.pem

sandbox-creation.ymlで作成、アップロードしたartifactをダウンロードします。
デフォルトではartifactはワークフロー間で共有できないので、dawidd6/action-download-artifact@v6を使います。

action-download-artifact

プライベートリポジトリで共有するためにはgithub_token: ${{ secrets.WORKFLOW_PAT }}の指定が必要です。WORKFLOW_PATにはrepoworkflowのスコープを与えたGitHubのPersonal Access Tokenを作成して指定しています。

放っておくと毎晩テスト用のSandbox組織が作成されてしまうので、古いテスト用Sandboxは削除しています。

sf org delete sandbox -o $OLD_SANDBOX_NAME --no-prompt

作成したばかりのSandbox組織は作成開始から24時間が経過するまで削除できないので、さらに1日前に作成したSandbox組織を削除する処理にしています。そのため、初回実行時は古いSandbox組織が存在しないため、この部分で必ずエラーになります。

まとめ

sfコマンドとSandbox組織、GitHub Actions、そしてPlaywrightを活用することで、毎晩のE2Eテストの定期実行を行う環境を構築できました。高い品質が求められるアプリケーションの開発においては、デイリーでE2Eテストが実行できることはとても心強い仕組みになります。

Salesforceに限らず、テスト環境を構築、そしてE2Eテストを実行というワークフローを実現する方法の参考になれば幸いです。

See Also

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.