--- name: E2E Tests Template on: workflow_call: inputs: # NB: this does not support using branch names that belong to forks. # In those cases, you should specify directly the commit SHA that you want to test, or # some wrapper workflow that does it for you (e.g. the slash command for initiating a PR test) commit_sha: type: string required: true status_check_context: type: string required: true workers_number: type: string # Should ideally be a number; see https://github.com/orgs/community/discussions/67182 required: false default: "1" testcase_failure_fatal: type: boolean required: false default: true # NB: the following toggles will skip individual steps, rather than the whole jobs, # to let the dependent jobs run even if these are false run_preflight_checks: type: boolean required: false default: true enable_reporting: type: boolean required: false default: false SERVER: type: string # Valid values are: onprem, cloud required: false default: onprem SERVER_IMAGE: type: string required: false ENABLED_DOCKER_SERVICES: type: string required: false TEST: # Valid values are: cypress, playwright type: string required: false default: "cypress" TEST_FILTER: type: string required: false MM_ENV: type: string required: false BRANCH: type: string required: false BUILD_ID: type: string required: false REPORT_TYPE: type: string required: false ROLLING_RELEASE_commit_sha: type: string required: false ROLLING_RELEASE_SERVER_IMAGE: type: string required: false secrets: MM_LICENSE: required: false AUTOMATION_DASHBOARD_URL: required: false AUTOMATION_DASHBOARD_TOKEN: required: false PUSH_NOTIFICATION_SERVER: required: false REPORT_WEBHOOK_URL: required: false REPORT_TM4J_API_KEY: required: false REPORT_TM4J_TEST_CYCLE_LINK_PREFIX: required: false CWS_URL: required: false CWS_EXTRA_HTTP_HEADERS: required: false outputs: passed: value: "${{ jobs.report.outputs.passed }}" failed: value: "${{ jobs.report.outputs.failed }}" failed_expected: value: "${{ jobs.report.outputs.failed_expected }}" pass_rate: value: "${{ jobs.report.outputs.pass_rate }}" jobs: update-initial-status: runs-on: ubuntu-latest steps: - uses: mattermost/actions/delivery/update-commit-status@main env: GITHUB_TOKEN: ${{ github.token }} with: repository_full_name: ${{ github.repository }} commit_sha: ${{ inputs.commit_sha }} context: ${{ inputs.status_check_context }} description: E2E tests for mattermost server app status: pending cypress-check: runs-on: ubuntu-latest needs: - update-initial-status defaults: run: working-directory: e2e-tests/cypress steps: - name: ci/checkout-repo if: "${{ inputs.run_preflight_checks }}" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/setup-node if: "${{ inputs.run_preflight_checks }}" uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 id: setup_node with: node-version-file: ".nvmrc" cache: npm cache-dependency-path: "e2e-tests/cypress/package-lock.json" - name: ci/cypress/npm-install if: "${{ inputs.run_preflight_checks }}" run: | npm ci - name: ci/cypress/npm-check if: "${{ inputs.run_preflight_checks }}" run: | npm run check playwright-check: runs-on: ubuntu-latest needs: - update-initial-status defaults: run: working-directory: e2e-tests/playwright steps: - name: ci/checkout-repo if: "${{ inputs.run_preflight_checks }}" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/setup-node if: "${{ inputs.run_preflight_checks }}" uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 id: setup_node with: node-version-file: ".nvmrc" cache: npm cache-dependency-path: "e2e-tests/playwright/package-lock.json" - name: ci/get-webapp-node-modules if: "${{ inputs.run_preflight_checks }}" working-directory: webapp # requires build of client and types run: | make node_modules - name: ci/playwright/npm-install if: "${{ inputs.run_preflight_checks }}" run: | npm ci - name: ci/playwright/npm-check if: "${{ inputs.run_preflight_checks }}" run: | npm run check shell-check: runs-on: ubuntu-latest needs: - update-initial-status defaults: run: working-directory: e2e-tests steps: - name: ci/checkout-repo if: "${{ inputs.run_preflight_checks }}" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/shell-check if: "${{ inputs.run_preflight_checks }}" run: make check-shell generate-build-variables: runs-on: ubuntu-latest needs: - update-initial-status defaults: run: shell: bash outputs: workers: "${{ steps.generate.outputs.workers }}" node-cache-dependency-path: "${{ steps.generate.outputs.node-cache-dependency-path }}" steps: - name: ci/checkout-repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/generate-build-variables id: generate env: WORKERS: ${{ inputs.workers_number }} TEST: ${{ inputs.TEST }} run: | [ "$WORKERS" -gt "0" ] # Assert that the workers number is an integer greater than 0 echo "workers="$(jq --slurp --compact-output '[range('"$WORKERS"')] | map(tostring)' /dev/null) >> $GITHUB_OUTPUT echo "node-cache-dependency-path=e2e-tests/${TEST}/package-lock.json" >> $GITHUB_OUTPUT generate-test-cycle: runs-on: ubuntu-latest needs: - generate-build-variables defaults: run: shell: bash working-directory: e2e-tests outputs: status_check_url: "${{ steps.e2e-test-gencycle.outputs.status_check_url }}" steps: - name: ci/checkout-repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/setup-node uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 id: setup_node with: node-version-file: ".nvmrc" cache: npm cache-dependency-path: "e2e-tests/cypress/package-lock.json" # NB: the generate-cycle script is cypress-specific operation for now - name: ci/e2e-test-gencycle id: e2e-test-gencycle env: AUTOMATION_DASHBOARD_URL: "${{ secrets.AUTOMATION_DASHBOARD_URL }}" AUTOMATION_DASHBOARD_TOKEN: "${{ secrets.AUTOMATION_DASHBOARD_TOKEN }}" BRANCH: "${{ inputs.BRANCH }}" BUILD_ID: "${{ inputs.BUILD_ID }}" TEST: "${{ inputs.TEST }}" TEST_FILTER: "${{ inputs.TEST_FILTER }}" run: | set -e -o pipefail make generate-test-cycle | tee generate-test-cycle.out # Extract cycle's dashboard URL, if present TEST_CYCLE_ID=$(sed -nE "s/^.*id: '([^']+)'.*$/\1/p" > $GITHUB_OUTPUT else echo "status_check_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_OUTPUT fi test: continue-on-error: true # Individual runner failures shouldn't prevent the completion of an E2E run strategy: fail-fast: false # Individual runner failures shouldn't prevent the completion of an E2E run matrix: # # Note that E2E tests should be run only on ubuntu, for QA purposes. # But it's useful to be able to run and debug the E2E tests for different OSes. # Notes: # - For MacOS: works on developer machines, but uses too many resources to be able to run on Github Actions # - for Windows: cannot currently run on Github Actions, since the runners do not support running linux containers, at the moment # #os: [ubuntu-latest, windows-2022, macos-12-xl] os: [ubuntu-latest] worker_index: ${{ fromJSON(needs.generate-build-variables.outputs.workers) }} # https://docs.github.com/en/actions/learn-github-actions/expressions#example-returning-a-json-object runs-on: "${{ matrix.os }}" timeout-minutes: 120 needs: - cypress-check - playwright-check - shell-check - generate-build-variables - generate-test-cycle defaults: run: shell: bash working-directory: e2e-tests env: AUTOMATION_DASHBOARD_URL: "${{ secrets.AUTOMATION_DASHBOARD_URL }}" AUTOMATION_DASHBOARD_TOKEN: "${{ secrets.AUTOMATION_DASHBOARD_TOKEN }}" SERVER: "${{ inputs.SERVER }}" SERVER_IMAGE: "${{ inputs.SERVER_IMAGE }}" MM_LICENSE: "${{ secrets.MM_LICENSE }}" ENABLED_DOCKER_SERVICES: "${{ inputs.ENABLED_DOCKER_SERVICES }}" TEST: "${{ inputs.TEST }}" TEST_FILTER: "${{ inputs.TEST_FILTER }}" MM_ENV: "${{ inputs.MM_ENV }}" BRANCH: "${{ inputs.BRANCH }}" BUILD_ID: "${{ inputs.BUILD_ID }}" CI_BASE_URL: "${{ matrix.os }}-${{ matrix.worker_index }}" CYPRESS_pushNotificationServer: "${{ secrets.PUSH_NOTIFICATION_SERVER }}" CWS_URL: "${{ secrets.CWS_URL }}" CWS_EXTRA_HTTP_HEADERS: "${{ secrets.CWS_EXTRA_HTTP_HEADERS }}" ROLLING_RELEASE_COMMIT_SHA: "${{ inputs.ROLLING_RELEASE_commit_sha }}" ROLLING_RELEASE_SERVER_IMAGE: "${{ inputs.ROLLING_RELEASE_SERVER_IMAGE }}" steps: - name: ci/checkout-repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/setup-macos-docker if: runner.os == 'macos' # https://github.com/actions/runner-images/issues/17#issuecomment-1537238473 run: | brew install docker docker-compose colima start mkdir -p ~/.docker/cli-plugins ln -sfn /usr/local/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock - name: ci/setup-node uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 id: setup_node with: node-version-file: ".nvmrc" cache: npm cache-dependency-path: ${{ needs.generate-build-variables.outputs.node-cache-dependency-path }} - name: ci/e2e-test run: | make cloud-init if [ -n "$ROLLING_RELEASE_SERVER_IMAGE" ]; then echo "RollingRelease: checking out E2E test cases from revision ${ROLLING_RELEASE_COMMIT_SHA}, for initial smoketest" git checkout "${ROLLING_RELEASE_COMMIT_SHA}" -- "${TEST}/" && git status ( echo "RollingRelease: running initial smoketest against image $ROLLING_RELEASE_SERVER_IMAGE" export SERVER_IMAGE="$ROLLING_RELEASE_SERVER_IMAGE" export TEST_FILTER="" export AUTOMATION_DASHBOARD_URL="" make ) echo "RollingRelease: asserting smoketest result has zero failures." FAILURES=$(jq -r '.failed' "${TEST}/results/summary.json") if [ "$FAILURES" -ne "0" ]; then echo "RollingRelease: initial smoketest for rolling release E2E run has nonzero ($FAILURES) failures. Aborting test run." >&2 exit 1 fi rm -rfv "${TEST}/{results,logs}" echo "RollingRelease: reset the E2E test cases to the revision to test" git reset --hard HEAD && git status echo "RollingRelease: smoketest completed. Starting full E2E tests." fi make make cloud-teardown - name: ci/e2e-test-store-results uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: always() with: name: e2e-test-results-${{ inputs.TEST }}-${{ matrix.os }}-${{ matrix.worker_index }} path: | e2e-tests/${{ inputs.TEST }}/logs/ e2e-tests/${{ inputs.TEST }}/results/ retention-days: 1 report: runs-on: ubuntu-latest needs: - test - generate-build-variables defaults: run: shell: bash working-directory: e2e-tests outputs: passed: "${{ steps.calculate-results.outputs.passed }}" failed: "${{ steps.calculate-results.outputs.failed }}" failed_expected: "${{ steps.calculate-results.outputs.failed_expected }}" pass_rate: "${{ steps.calculate-results.outputs.pass_rate }}" commit_status_message: "${{ steps.calculate-results.outputs.commit_status_message }}" steps: - name: ci/checkout-repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.commit_sha }} fetch-depth: 0 - name: ci/download-artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: e2e-test-results-${{ inputs.TEST }}-* path: e2e-tests/${{ inputs.TEST }}/ merge-multiple: true - name: ci/upload-report-global uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: e2e-test-results-${{ inputs.TEST }} path: | e2e-tests/${{ inputs.TEST }}/logs/ e2e-tests/${{ inputs.TEST }}/results/ - name: ci/setup-node if: "${{ inputs.enable_reporting }}" uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 id: setup_node with: node-version-file: ".nvmrc" cache: npm cache-dependency-path: ${{ needs.generate-build-variables.outputs.node-cache-dependency-path }} - name: ci/publish-report if: "${{ inputs.enable_reporting }}" env: TYPE: "${{ inputs.REPORT_TYPE }}" TEST: "${{ inputs.TEST }}" SERVER: "${{ inputs.SERVER }}" SERVER_IMAGE: "${{ inputs.SERVER_IMAGE }}" AUTOMATION_DASHBOARD_URL: "${{ secrets.AUTOMATION_DASHBOARD_URL }}" WEBHOOK_URL: "${{ secrets.REPORT_WEBHOOK_URL }}" BRANCH: "${{ inputs.BRANCH }}" BUILD_ID: "${{ inputs.BUILD_ID }}" MM_ENV: "${{ inputs.MM_ENV }}" TM4J_API_KEY: "${{ secrets.REPORT_TM4J_API_KEY }}" TEST_CYCLE_LINK_PREFIX: "${{ secrets.REPORT_TM4J_TEST_CYCLE_LINK_PREFIX }}" run: | make report # The results dir may have been modified as part of the reporting: re-upload - name: ci/upload-report-global if: "${{ inputs.enable_reporting }}" uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: e2e-test-results-${{ inputs.TEST }} path: | e2e-tests/${{ inputs.TEST }}/logs/ e2e-tests/${{ inputs.TEST }}/results/ overwrite: true - name: ci/report-calculate-results id: calculate-results env: TEST: "${{ inputs.TEST }}" run: | AD_CYCLE_FILE="${TEST}/results/ad_cycle.json" if [ -f "$AD_CYCLE_FILE" ]; then # Prefer using the Automation Dashboard's results to calculate failures export PASSED=$(jq -r .pass "$AD_CYCLE_FILE") export FAILED=$(jq -r .fail "$AD_CYCLE_FILE") export FAILED_EXPECTED=$(jq -r ".known + .flaky + .skipped" "$AD_CYCLE_FILE") else # Otherwise, utilize summary.json to calculate the failures # NB: in this job, this file only makes sense if a single worker is used, as with Playwright export PASSED=$(jq '.passed' "${TEST}/results/summary.json") export FAILED=$(jq '.failed' "${TEST}/results/summary.json") export FAILED_EXPECTED=$(jq '.failed_expected' "${TEST}/results/summary.json") fi export TOTAL_SPECS=$(( PASSED + FAILED )) export PASS_RATE=$(jq -r '100 * (env.PASSED | tonumber) / (env.TOTAL_SPECS | tonumber)' <<<'{}' | xargs -l printf '%.2f') if [ "$FAILED" = "0" ]; then export COMMIT_STATUS_MESSAGE="All test cases passed" else export COMMIT_STATUS_MESSAGE="${FAILED} test cases failed. Please check the workflow logs" fi echo "passed=${PASSED:?}" >> $GITHUB_OUTPUT echo "failed=${FAILED:?}" >> $GITHUB_OUTPUT echo "failed_expected=${FAILED_EXPECTED:?}" >> $GITHUB_OUTPUT echo "pass_rate=${PASS_RATE:?}%" >> $GITHUB_OUTPUT echo "commit_status_message=${COMMIT_STATUS_MESSAGE:?}" >> $GITHUB_OUTPUT echo "$COMMIT_STATUS_MESSAGE" - name: ci/e2e-test-assert-results if: "${{ inputs.testcase_failure_fatal }}" run: | # Assert that the run contained 0 failures [ "${{ steps.calculate-results.outputs.failed }}" = "0" ] update-failure-final-status: runs-on: ubuntu-latest if: failure() || cancelled() needs: - generate-test-cycle - test - report steps: - uses: mattermost/actions/delivery/update-commit-status@main env: GITHUB_TOKEN: ${{ github.token }} with: repository_full_name: ${{ github.repository }} commit_sha: ${{ inputs.commit_sha }} context: ${{ inputs.status_check_context }} description: ${{ needs.report.outputs.commit_status_message || 'Error during test execution' }} status: failure target_url: "${{ needs.generate-test-cycle.outputs.status_check_url }}" update-success-final-status: runs-on: ubuntu-latest if: success() needs: - generate-test-cycle - test - report steps: - uses: mattermost/actions/delivery/update-commit-status@main env: GITHUB_TOKEN: ${{ github.token }} with: repository_full_name: ${{ github.repository }} commit_sha: ${{ inputs.commit_sha }} context: ${{ inputs.status_check_context }} description: ${{ needs.report.outputs.commit_status_message || 'Error during test execution' }} status: success target_url: "${{ needs.generate-test-cycle.outputs.status_check_url }}"