diff --git a/.github/actions/check-e2e-test-only/action.yml b/.github/actions/check-e2e-test-only/action.yml new file mode 100644 index 0000000000..86cf4ce108 --- /dev/null +++ b/.github/actions/check-e2e-test-only/action.yml @@ -0,0 +1,104 @@ +--- +name: Check E2E Test Only +description: Check if PR contains only E2E test changes and determine the appropriate docker image tag + +inputs: + base_sha: + description: Base commit SHA (PR base) + required: false + head_sha: + description: Head commit SHA (PR head) + required: false + pr_number: + description: PR number (used to fetch SHAs via API if base_sha/head_sha not provided) + required: false + +outputs: + e2e_test_only: + description: Whether the PR contains only E2E test changes (true/false) + value: ${{ steps.check.outputs.e2e_test_only }} + image_tag: + description: Docker image tag to use (base branch ref for E2E-only, short SHA for mixed) + value: ${{ steps.check.outputs.image_tag }} + +runs: + using: composite + steps: + - name: ci/check-e2e-test-only + id: check + shell: bash + env: + GH_TOKEN: ${{ github.token }} + INPUT_BASE_SHA: ${{ inputs.base_sha }} + INPUT_HEAD_SHA: ${{ inputs.head_sha }} + INPUT_PR_NUMBER: ${{ inputs.pr_number }} + run: | + # Resolve SHAs and base branch from PR number if not provided + BASE_REF="" + if [ -z "$INPUT_BASE_SHA" ] || [ -z "$INPUT_HEAD_SHA" ]; then + if [ -z "$INPUT_PR_NUMBER" ]; then + echo "::error::Either base_sha/head_sha or pr_number must be provided" + exit 1 + fi + + echo "Resolving SHAs from PR #${INPUT_PR_NUMBER}" + PR_DATA=$(gh api "repos/${{ github.repository }}/pulls/${INPUT_PR_NUMBER}") + INPUT_BASE_SHA=$(echo "$PR_DATA" | jq -r '.base.sha') + INPUT_HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha') + BASE_REF=$(echo "$PR_DATA" | jq -r '.base.ref') + + if [ -z "$INPUT_BASE_SHA" ] || [ "$INPUT_BASE_SHA" = "null" ] || \ + [ -z "$INPUT_HEAD_SHA" ] || [ "$INPUT_HEAD_SHA" = "null" ]; then + echo "::error::Could not resolve SHAs for PR #${INPUT_PR_NUMBER}" + exit 1 + fi + elif [ -n "$INPUT_PR_NUMBER" ]; then + # SHAs provided but we still need the base branch ref + BASE_REF=$(gh api "repos/${{ github.repository }}/pulls/${INPUT_PR_NUMBER}" --jq '.base.ref') + fi + + # Default to master if base ref could not be determined + if [ -z "$BASE_REF" ] || [ "$BASE_REF" = "null" ]; then + BASE_REF="master" + fi + echo "PR base branch: ${BASE_REF}" + + SHORT_SHA="${INPUT_HEAD_SHA::7}" + + # Get changed files - try git first, fall back to API + CHANGED_FILES=$(git diff --name-only "$INPUT_BASE_SHA"..."$INPUT_HEAD_SHA" 2>/dev/null || \ + gh api "repos/${{ github.repository }}/pulls/${INPUT_PR_NUMBER}/files" --jq '.[].filename' 2>/dev/null || echo "") + + if [ -z "$CHANGED_FILES" ]; then + echo "::warning::Could not determine changed files, assuming not E2E-only" + echo "e2e_test_only=false" >> $GITHUB_OUTPUT + echo "image_tag=${SHORT_SHA}" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Changed files:" + echo "$CHANGED_FILES" + + # Check if all files are E2E-related + E2E_TEST_ONLY="true" + while IFS= read -r file; do + [ -z "$file" ] && continue + if [[ ! "$file" =~ ^e2e-tests/ ]] && \ + [[ ! "$file" =~ ^\.github/workflows/e2e- ]] && \ + [[ ! "$file" =~ ^\.github/actions/ ]]; then + echo "Non-E2E file found: $file" + E2E_TEST_ONLY="false" + break + fi + done <<< "$CHANGED_FILES" + + echo "E2E test only: ${E2E_TEST_ONLY}" + + # Set outputs + echo "e2e_test_only=${E2E_TEST_ONLY}" >> $GITHUB_OUTPUT + if [ "$E2E_TEST_ONLY" = "true" ] && \ + { [ "$BASE_REF" = "master" ] || [[ "$BASE_REF" =~ ^release-[0-9]+\.[0-9]+$ ]]; }; then + echo "image_tag=${BASE_REF}" >> $GITHUB_OUTPUT + else + echo "image_tag=${SHORT_SHA}" >> $GITHUB_OUTPUT + fi diff --git a/.github/workflows/e2e-tests-ci.yml b/.github/workflows/e2e-tests-ci.yml index e6af1f7c74..0835668525 100644 --- a/.github/workflows/e2e-tests-ci.yml +++ b/.github/workflows/e2e-tests-ci.yml @@ -1,19 +1,230 @@ --- -name: E2E Smoketests +name: E2E Tests (pull request) on: - # For PRs, this workflow gets triggered from the Argo Events platform. - # Check the following repo for details: https://github.com/mattermost/delivery-platform + pull_request: + types: + - opened + - synchronize + - reopened + # Argo Events Trigger (automated): + # - Triggered by: Enterprise CI/docker-image status check (success) + # - Payload: { ref: "", inputs: { commit_sha: "" } } + # - Uses commit-specific docker image + # - Checks for relevant file changes before running tests + # + # Manual Trigger: + # - Enter PR number only - commit SHA is resolved automatically from PR head + # - Uses commit-specific docker image + # - E2E tests always run (no file change check) + # workflow_dispatch: inputs: - commit_sha: + pr_number: + description: "PR number to test (for manual triggers)" type: string - required: true + required: false + commit_sha: + description: "Commit SHA to test (for Argo Events)" + type: string + required: false jobs: - e2e-smoketest: - uses: ./.github/workflows/e2e-tests-ci-template.yml + resolve-pr: + runs-on: ubuntu-24.04 + outputs: + PR_NUMBER: "${{ steps.resolve.outputs.PR_NUMBER }}" + COMMIT_SHA: "${{ steps.resolve.outputs.COMMIT_SHA }}" + SERVER_IMAGE_TAG: "${{ steps.e2e-check.outputs.image_tag }}" + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: ci/resolve-pr-and-commit + id: resolve + env: + GH_TOKEN: ${{ github.token }} + INPUT_PR_NUMBER: ${{ inputs.pr_number || github.event.pull_request.number }} + INPUT_COMMIT_SHA: ${{ inputs.commit_sha || github.event.pull_request.head.sha }} + run: | + # Validate inputs + if [ -n "$INPUT_PR_NUMBER" ] && ! [[ "$INPUT_PR_NUMBER" =~ ^[0-9]+$ ]]; then + echo "::error::Invalid PR number format. Must be numeric." + exit 1 + fi + if [ -n "$INPUT_COMMIT_SHA" ] && ! [[ "$INPUT_COMMIT_SHA" =~ ^[a-f0-9]{7,40}$ ]]; then + echo "::error::Invalid commit SHA format. Must be 7-40 hex characters." + exit 1 + fi + + # Manual trigger: PR number provided, resolve commit SHA from PR head + if [ -n "$INPUT_PR_NUMBER" ]; then + echo "Manual trigger: resolving commit SHA from PR #${INPUT_PR_NUMBER}" + PR_DATA=$(gh api "repos/${{ github.repository }}/pulls/${INPUT_PR_NUMBER}") + COMMIT_SHA=$(echo "$PR_DATA" | jq -r '.head.sha') + + if [ -z "$COMMIT_SHA" ] || [ "$COMMIT_SHA" = "null" ]; then + echo "::error::Could not resolve commit SHA for PR #${INPUT_PR_NUMBER}" + exit 1 + fi + + echo "PR_NUMBER=${INPUT_PR_NUMBER}" >> $GITHUB_OUTPUT + echo "COMMIT_SHA=${COMMIT_SHA}" >> $GITHUB_OUTPUT + exit 0 + fi + + # Argo Events trigger: commit SHA provided, resolve PR number + if [ -n "$INPUT_COMMIT_SHA" ]; then + echo "Automated trigger: resolving PR number from commit ${INPUT_COMMIT_SHA}" + PR_DATA=$(gh api "repos/${{ github.repository }}/commits/${INPUT_COMMIT_SHA}/pulls" \ + --jq '.[0] // empty' 2>/dev/null || echo "") + PR_NUMBER=$(echo "$PR_DATA" | jq -r '.number // empty' 2>/dev/null || echo "") + if [ -z "$PR_NUMBER" ]; then + echo "::error::No PR found for commit ${INPUT_COMMIT_SHA}. This workflow is for PRs only." + exit 1 + fi + + echo "Found PR #${PR_NUMBER} for commit ${INPUT_COMMIT_SHA}" + + # Skip if PR is already merged to master or a release branch. + # The e2e-tests-on-merge workflow handles post-merge E2E tests. + PR_MERGED=$(echo "$PR_DATA" | jq -r '.merged_at // empty' 2>/dev/null || echo "") + PR_BASE_REF=$(echo "$PR_DATA" | jq -r '.base.ref // empty' 2>/dev/null || echo "") + if [ -n "$PR_MERGED" ]; then + if [ "$PR_BASE_REF" = "master" ] || [[ "$PR_BASE_REF" =~ ^release-[0-9]+\.[0-9]+$ ]]; then + echo "PR #${PR_NUMBER} is already merged to ${PR_BASE_REF}. Skipping - handled by e2e-tests-on-merge workflow." + echo "PR_NUMBER=" >> $GITHUB_OUTPUT + echo "COMMIT_SHA=" >> $GITHUB_OUTPUT + exit 0 + fi + fi + + echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_OUTPUT + echo "COMMIT_SHA=${INPUT_COMMIT_SHA}" >> $GITHUB_OUTPUT + exit 0 + fi + + # Neither provided + echo "::error::Either pr_number or commit_sha must be provided" + exit 1 + + - name: ci/check-e2e-test-only + if: steps.resolve.outputs.PR_NUMBER != '' + id: e2e-check + uses: ./.github/actions/check-e2e-test-only + with: + pr_number: ${{ steps.resolve.outputs.PR_NUMBER }} + + + check-changes: + needs: resolve-pr + if: needs.resolve-pr.outputs.PR_NUMBER != '' + runs-on: ubuntu-24.04 + outputs: + should_run: "${{ steps.check.outputs.should_run }}" + steps: + - name: ci/checkout-repo + if: inputs.commit_sha != '' || github.event.pull_request + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.resolve-pr.outputs.COMMIT_SHA }} + fetch-depth: 0 + - name: ci/check-relevant-changes + id: check + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ needs.resolve-pr.outputs.PR_NUMBER }} + COMMIT_SHA: ${{ needs.resolve-pr.outputs.COMMIT_SHA }} + INPUT_PR_NUMBER: ${{ inputs.pr_number }} + run: | + # Manual trigger (pr_number provided): always run E2E tests + if [ -n "$INPUT_PR_NUMBER" ]; then + echo "Manual trigger detected - skipping file change check" + echo "should_run=true" >> $GITHUB_OUTPUT + exit 0 + fi + + # Automated trigger (commit_sha provided): check for relevant file changes + echo "Automated trigger detected - checking for relevant file changes" + + # Get the base branch of the PR + BASE_SHA=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}" --jq '.base.sha') + + # Get changed files between base and head + CHANGED_FILES=$(git diff --name-only "${BASE_SHA}...${COMMIT_SHA}") + + echo "Changed files:" + echo "$CHANGED_FILES" + + # Check for relevant changes + SHOULD_RUN="false" + + # Check for server Go files + if echo "$CHANGED_FILES" | grep -qE '^server/.*\.go$'; then + echo "Found server Go file changes" + SHOULD_RUN="true" + fi + + # Check for webapp ts/js/tsx/jsx files + if echo "$CHANGED_FILES" | grep -qE '^webapp/.*\.(ts|tsx|js|jsx)$'; then + echo "Found webapp TypeScript/JavaScript file changes" + SHOULD_RUN="true" + fi + + # Check for e2e-tests ts/js/tsx/jsx files + if echo "$CHANGED_FILES" | grep -qE '^e2e-tests/.*\.(ts|tsx|js|jsx)$'; then + echo "Found e2e-tests TypeScript/JavaScript file changes" + SHOULD_RUN="true" + fi + + + echo "should_run=${SHOULD_RUN}" >> $GITHUB_OUTPUT + echo "Should run E2E tests: ${SHOULD_RUN}" + + e2e-cypress: + needs: + - resolve-pr + - check-changes + if: needs.resolve-pr.outputs.PR_NUMBER != '' + permissions: + statuses: write + uses: ./.github/workflows/e2e-tests-cypress.yml with: - commit_sha: "${{ inputs.commit_sha }}" - status_check_context: "E2E Tests/smoketests" + commit_sha: "${{ needs.resolve-pr.outputs.COMMIT_SHA }}" + server: "onprem" + server_image_tag: "${{ needs.resolve-pr.outputs.SERVER_IMAGE_TAG }}" + enable_reporting: true + report_type: "PR" + pr_number: "${{ needs.resolve-pr.outputs.PR_NUMBER }}" + should_run: "${{ needs.check-changes.outputs.should_run }}" secrets: MM_LICENSE: "${{ secrets.MM_E2E_TEST_LICENSE_ONPREM_ENT }}" + AUTOMATION_DASHBOARD_URL: "${{ secrets.MM_E2E_AUTOMATION_DASHBOARD_URL }}" + AUTOMATION_DASHBOARD_TOKEN: "${{ secrets.MM_E2E_AUTOMATION_DASHBOARD_TOKEN }}" + PUSH_NOTIFICATION_SERVER: "${{ secrets.MM_E2E_PUSH_NOTIFICATION_SERVER }}" + REPORT_WEBHOOK_URL: "${{ secrets.MM_E2E_REPORT_WEBHOOK_URL }}" + CWS_URL: "${{ secrets.MM_E2E_CWS_URL }}" + CWS_EXTRA_HTTP_HEADERS: "${{ secrets.MM_E2E_CWS_EXTRA_HTTP_HEADERS }}" + + e2e-playwright: + needs: + - resolve-pr + - check-changes + if: needs.resolve-pr.outputs.PR_NUMBER != '' + permissions: + statuses: write + uses: ./.github/workflows/e2e-tests-playwright.yml + with: + commit_sha: "${{ needs.resolve-pr.outputs.COMMIT_SHA }}" + server: "onprem" + server_image_tag: "${{ needs.resolve-pr.outputs.SERVER_IMAGE_TAG }}" + enable_reporting: true + report_type: "PR" + pr_number: "${{ needs.resolve-pr.outputs.PR_NUMBER }}" + should_run: "${{ needs.check-changes.outputs.should_run }}" + secrets: + MM_LICENSE: "${{ secrets.MM_E2E_TEST_LICENSE_ONPREM_ENT }}" + AWS_ACCESS_KEY_ID: "${{ secrets.CYPRESS_AWS_ACCESS_KEY_ID }}" + AWS_SECRET_ACCESS_KEY: "${{ secrets.CYPRESS_AWS_SECRET_ACCESS_KEY }}" + REPORT_WEBHOOK_URL: "${{ secrets.MM_E2E_REPORT_WEBHOOK_URL }}" diff --git a/.github/workflows/e2e-tests-cypress-template.yml b/.github/workflows/e2e-tests-cypress-template.yml new file mode 100644 index 0000000000..582e63a154 --- /dev/null +++ b/.github/workflows/e2e-tests-cypress-template.yml @@ -0,0 +1,649 @@ +--- +name: E2E Tests - Cypress Template +on: + workflow_call: + inputs: + # Test configuration + test_type: + description: "Type of test run (smoke or full)" + type: string + required: true + test_filter: + description: "Test filter arguments" + type: string + required: true + workers: + description: "Number of parallel workers" + type: number + required: false + default: 1 + enabled_docker_services: + description: "Space-separated list of docker services to enable" + type: string + required: false + default: "postgres inbucket" + + # Common build variables + commit_sha: + type: string + required: true + branch: + type: string + required: true + build_id: + type: string + required: true + server_image_tag: + description: "Server image tag (e.g., master or short SHA)" + type: string + required: true + server: + type: string + required: false + default: onprem + server_edition: + description: "Server edition: enterprise (default), fips, or team" + type: string + required: false + default: enterprise + server_image_repo: + description: "Docker registry: mattermostdevelopment (default) or mattermost" + type: string + required: false + default: mattermostdevelopment + server_image_aliases: + description: "Comma-separated alias tags for description (e.g., 'release-11.4, release-11')" + type: string + required: false + + # Reporting options + enable_reporting: + type: boolean + required: false + default: false + report_type: + type: string + required: false + ref_branch: + description: "Source branch name for webhook messages (e.g., 'master' or 'release-11.4')" + type: string + required: false + pr_number: + type: string + required: false + # Commit status configuration + context_name: + description: "GitHub commit status context name" + type: string + required: true + + outputs: + passed: + description: "Number of passed tests" + value: ${{ jobs.report.outputs.passed }} + failed: + description: "Number of failed tests" + value: ${{ jobs.report.outputs.failed }} + status_check_url: + description: "URL to test results" + value: ${{ jobs.generate-test-cycle.outputs.status_check_url }} + + 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 + CWS_URL: + required: false + CWS_EXTRA_HTTP_HEADERS: + required: false + +env: + SERVER_IMAGE: "${{ inputs.server_image_repo }}/${{ inputs.server_edition == 'fips' && 'mattermost-enterprise-fips-edition' || inputs.server_edition == 'team' && 'mattermost-team-edition' || 'mattermost-enterprise-edition' }}:${{ inputs.server_image_tag }}" + +jobs: + update-initial-status: + runs-on: ubuntu-24.04 + steps: + - name: ci/set-initial-status + uses: mattermost/actions/delivery/update-commit-status@f324ac89b05cc3511cb06e60642ac2fb829f0a63 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.commit_sha }} + context: ${{ inputs.context_name }} + description: "tests running, image_tag:${{ inputs.server_image_tag }}${{ inputs.server_image_aliases && format(' ({0})', inputs.server_image_aliases) || '' }}" + status: pending + + generate-test-cycle: + runs-on: ubuntu-24.04 + outputs: + status_check_url: "${{ steps.generate-cycle.outputs.status_check_url }}" + workers: "${{ steps.generate-workers.outputs.workers }}" + start_time: "${{ steps.generate-workers.outputs.start_time }}" + steps: + - name: ci/generate-workers + id: generate-workers + run: | + echo "workers=$(jq -nc '[range(${{ inputs.workers }})]')" >> $GITHUB_OUTPUT + echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/cypress/package-lock.json" + + - name: ci/generate-test-cycle + id: generate-cycle + working-directory: e2e-tests + env: + AUTOMATION_DASHBOARD_URL: "${{ secrets.AUTOMATION_DASHBOARD_URL }}" + AUTOMATION_DASHBOARD_TOKEN: "${{ secrets.AUTOMATION_DASHBOARD_TOKEN }}" + BRANCH: "${{ inputs.branch }}-${{ inputs.test_type }}" + BUILD_ID: "${{ inputs.build_id }}" + TEST: cypress + TEST_FILTER: "${{ inputs.test_filter }}" + run: | + set -e -o pipefail + make generate-test-cycle | tee generate-test-cycle.out + 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 + + run-tests: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + continue-on-error: ${{ inputs.workers > 1 }} + needs: + - generate-test-cycle + if: needs.generate-test-cycle.result == 'success' + strategy: + fail-fast: false + matrix: + worker_index: ${{ fromJSON(needs.generate-test-cycle.outputs.workers) }} + defaults: + run: + working-directory: e2e-tests + env: + AUTOMATION_DASHBOARD_URL: "${{ secrets.AUTOMATION_DASHBOARD_URL }}" + AUTOMATION_DASHBOARD_TOKEN: "${{ secrets.AUTOMATION_DASHBOARD_TOKEN }}" + SERVER: "${{ inputs.server }}" + MM_LICENSE: "${{ secrets.MM_LICENSE }}" + ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}" + TEST: cypress + TEST_FILTER: "${{ inputs.test_filter }}" + BRANCH: "${{ inputs.branch }}-${{ inputs.test_type }}" + BUILD_ID: "${{ inputs.build_id }}" + CI_BASE_URL: "${{ inputs.test_type }}-test-${{ matrix.worker_index }}" + CYPRESS_pushNotificationServer: "${{ secrets.PUSH_NOTIFICATION_SERVER }}" + CWS_URL: "${{ secrets.CWS_URL }}" + CWS_EXTRA_HTTP_HEADERS: "${{ secrets.CWS_EXTRA_HTTP_HEADERS }}" + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/cypress/package-lock.json" + - name: ci/run-tests + run: | + make cloud-init + make + - name: ci/cloud-teardown + if: always() + run: make cloud-teardown + - name: ci/upload-results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: always() + with: + name: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-results-${{ matrix.worker_index }} + path: | + e2e-tests/cypress/logs/ + e2e-tests/cypress/results/ + retention-days: 5 + + calculate-results: + runs-on: ubuntu-24.04 + needs: + - generate-test-cycle + - run-tests + if: always() && needs.generate-test-cycle.result == 'success' + outputs: + passed: ${{ steps.calculate.outputs.passed }} + failed: ${{ steps.calculate.outputs.failed }} + pending: ${{ steps.calculate.outputs.pending }} + total_specs: ${{ steps.calculate.outputs.total_specs }} + failed_specs: ${{ steps.calculate.outputs.failed_specs }} + failed_specs_count: ${{ steps.calculate.outputs.failed_specs_count }} + failed_tests: ${{ steps.calculate.outputs.failed_tests }} + commit_status_message: ${{ steps.calculate.outputs.commit_status_message }} + total: ${{ steps.calculate.outputs.total }} + pass_rate: ${{ steps.calculate.outputs.pass_rate }} + color: ${{ steps.calculate.outputs.color }} + test_duration: ${{ steps.calculate.outputs.test_duration }} + end_time: ${{ steps.record-end-time.outputs.end_time }} + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: ci/download-results + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + pattern: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-results-* + path: e2e-tests/cypress/ + merge-multiple: true + - name: ci/calculate + id: calculate + uses: ./.github/actions/calculate-cypress-results + with: + original-results-path: e2e-tests/cypress/results + - name: ci/record-end-time + id: record-end-time + run: echo "end_time=$(date +%s)" >> $GITHUB_OUTPUT + + run-failed-tests: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + needs: + - generate-test-cycle + - run-tests + - calculate-results + if: >- + always() && + needs.calculate-results.result == 'success' && + needs.calculate-results.outputs.failed != '0' && + fromJSON(needs.calculate-results.outputs.failed_specs_count) <= 20 + defaults: + run: + working-directory: e2e-tests + env: + AUTOMATION_DASHBOARD_URL: "${{ secrets.AUTOMATION_DASHBOARD_URL }}" + AUTOMATION_DASHBOARD_TOKEN: "${{ secrets.AUTOMATION_DASHBOARD_TOKEN }}" + SERVER: "${{ inputs.server }}" + MM_LICENSE: "${{ secrets.MM_LICENSE }}" + ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}" + TEST: cypress + BRANCH: "${{ inputs.branch }}-${{ inputs.test_type }}-retest" + BUILD_ID: "${{ inputs.build_id }}-retest" + CYPRESS_pushNotificationServer: "${{ secrets.PUSH_NOTIFICATION_SERVER }}" + CWS_URL: "${{ secrets.CWS_URL }}" + CWS_EXTRA_HTTP_HEADERS: "${{ secrets.CWS_EXTRA_HTTP_HEADERS }}" + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/cypress/package-lock.json" + - name: ci/run-failed-specs + env: + SPEC_FILES: ${{ needs.calculate-results.outputs.failed_specs }} + run: | + echo "Retesting failed specs: $SPEC_FILES" + make cloud-init + make start-server run-specs + - name: ci/cloud-teardown + if: always() + run: make cloud-teardown + - name: ci/upload-retest-results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: always() + with: + name: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-retest-results + path: | + e2e-tests/cypress/logs/ + e2e-tests/cypress/results/ + retention-days: 5 + + report: + runs-on: ubuntu-24.04 + needs: + - generate-test-cycle + - run-tests + - calculate-results + - run-failed-tests + if: always() && needs.calculate-results.result == 'success' + outputs: + passed: "${{ steps.final-results.outputs.passed }}" + failed: "${{ steps.final-results.outputs.failed }}" + commit_status_message: "${{ steps.final-results.outputs.commit_status_message }}" + duration: "${{ steps.duration.outputs.duration }}" + duration_display: "${{ steps.duration.outputs.duration_display }}" + retest_display: "${{ steps.duration.outputs.retest_display }}" + defaults: + run: + working-directory: e2e-tests + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/cypress/package-lock.json" + + # PATH A: run-failed-tests was skipped (no failures to retest) + - name: ci/download-results-path-a + if: needs.run-failed-tests.result == 'skipped' + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + pattern: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-results-* + path: e2e-tests/cypress/ + merge-multiple: true + - name: ci/use-previous-calculation + if: needs.run-failed-tests.result == 'skipped' + id: use-previous + run: | + echo "passed=${{ needs.calculate-results.outputs.passed }}" >> $GITHUB_OUTPUT + echo "failed=${{ needs.calculate-results.outputs.failed }}" >> $GITHUB_OUTPUT + echo "pending=${{ needs.calculate-results.outputs.pending }}" >> $GITHUB_OUTPUT + echo "total_specs=${{ needs.calculate-results.outputs.total_specs }}" >> $GITHUB_OUTPUT + echo "failed_specs=${{ needs.calculate-results.outputs.failed_specs }}" >> $GITHUB_OUTPUT + echo "failed_specs_count=${{ needs.calculate-results.outputs.failed_specs_count }}" >> $GITHUB_OUTPUT + echo "commit_status_message=${{ needs.calculate-results.outputs.commit_status_message }}" >> $GITHUB_OUTPUT + echo "total=${{ needs.calculate-results.outputs.total }}" >> $GITHUB_OUTPUT + echo "pass_rate=${{ needs.calculate-results.outputs.pass_rate }}" >> $GITHUB_OUTPUT + echo "color=${{ needs.calculate-results.outputs.color }}" >> $GITHUB_OUTPUT + echo "test_duration=${{ needs.calculate-results.outputs.test_duration }}" >> $GITHUB_OUTPUT + { + echo "failed_tests<> $GITHUB_OUTPUT + + # PATH B: run-failed-tests ran, need to merge and recalculate + - name: ci/download-original-results + if: needs.run-failed-tests.result != 'skipped' + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + pattern: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-results-* + path: e2e-tests/cypress/ + merge-multiple: true + - name: ci/download-retest-results + if: needs.run-failed-tests.result != 'skipped' + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-retest-results + path: e2e-tests/cypress/retest-results/ + - name: ci/calculate-results + if: needs.run-failed-tests.result != 'skipped' + id: recalculate + uses: ./.github/actions/calculate-cypress-results + with: + original-results-path: e2e-tests/cypress/results + retest-results-path: e2e-tests/cypress/retest-results/results + + # Set final outputs from either path + - name: ci/set-final-results + id: final-results + env: + USE_PREVIOUS_FAILED_TESTS: ${{ steps.use-previous.outputs.failed_tests }} + RECALCULATE_FAILED_TESTS: ${{ steps.recalculate.outputs.failed_tests }} + run: | + if [ "${{ needs.run-failed-tests.result }}" == "skipped" ]; then + echo "passed=${{ steps.use-previous.outputs.passed }}" >> $GITHUB_OUTPUT + echo "failed=${{ steps.use-previous.outputs.failed }}" >> $GITHUB_OUTPUT + echo "pending=${{ steps.use-previous.outputs.pending }}" >> $GITHUB_OUTPUT + echo "total_specs=${{ steps.use-previous.outputs.total_specs }}" >> $GITHUB_OUTPUT + echo "failed_specs=${{ steps.use-previous.outputs.failed_specs }}" >> $GITHUB_OUTPUT + echo "failed_specs_count=${{ steps.use-previous.outputs.failed_specs_count }}" >> $GITHUB_OUTPUT + echo "commit_status_message=${{ steps.use-previous.outputs.commit_status_message }}" >> $GITHUB_OUTPUT + echo "total=${{ steps.use-previous.outputs.total }}" >> $GITHUB_OUTPUT + echo "pass_rate=${{ steps.use-previous.outputs.pass_rate }}" >> $GITHUB_OUTPUT + echo "color=${{ steps.use-previous.outputs.color }}" >> $GITHUB_OUTPUT + echo "test_duration=${{ steps.use-previous.outputs.test_duration }}" >> $GITHUB_OUTPUT + { + echo "failed_tests<> $GITHUB_OUTPUT + else + echo "passed=${{ steps.recalculate.outputs.passed }}" >> $GITHUB_OUTPUT + echo "failed=${{ steps.recalculate.outputs.failed }}" >> $GITHUB_OUTPUT + echo "pending=${{ steps.recalculate.outputs.pending }}" >> $GITHUB_OUTPUT + echo "total_specs=${{ steps.recalculate.outputs.total_specs }}" >> $GITHUB_OUTPUT + echo "failed_specs=${{ steps.recalculate.outputs.failed_specs }}" >> $GITHUB_OUTPUT + echo "failed_specs_count=${{ steps.recalculate.outputs.failed_specs_count }}" >> $GITHUB_OUTPUT + echo "commit_status_message=${{ steps.recalculate.outputs.commit_status_message }}" >> $GITHUB_OUTPUT + echo "total=${{ steps.recalculate.outputs.total }}" >> $GITHUB_OUTPUT + echo "pass_rate=${{ steps.recalculate.outputs.pass_rate }}" >> $GITHUB_OUTPUT + echo "color=${{ steps.recalculate.outputs.color }}" >> $GITHUB_OUTPUT + echo "test_duration=${{ steps.recalculate.outputs.test_duration }}" >> $GITHUB_OUTPUT + { + echo "failed_tests<> $GITHUB_OUTPUT + fi + + - name: ci/compute-duration + id: duration + env: + START_TIME: ${{ needs.generate-test-cycle.outputs.start_time }} + FIRST_PASS_END_TIME: ${{ needs.calculate-results.outputs.end_time }} + RETEST_RESULT: ${{ needs.run-failed-tests.result }} + RETEST_SPEC_COUNT: ${{ needs.calculate-results.outputs.failed_specs_count }} + TEST_DURATION: ${{ steps.final-results.outputs.test_duration }} + run: | + NOW=$(date +%s) + ELAPSED=$((NOW - START_TIME)) + MINUTES=$((ELAPSED / 60)) + SECONDS=$((ELAPSED % 60)) + DURATION="${MINUTES}m ${SECONDS}s" + + # Compute first-pass and re-run durations + FIRST_PASS_ELAPSED=$((FIRST_PASS_END_TIME - START_TIME)) + FP_MIN=$((FIRST_PASS_ELAPSED / 60)) + FP_SEC=$((FIRST_PASS_ELAPSED % 60)) + FIRST_PASS="${FP_MIN}m ${FP_SEC}s" + + if [ "$RETEST_RESULT" != "skipped" ]; then + RERUN_ELAPSED=$((NOW - FIRST_PASS_END_TIME)) + RR_MIN=$((RERUN_ELAPSED / 60)) + RR_SEC=$((RERUN_ELAPSED % 60)) + RUN_BREAKDOWN=" (first-pass: ${FIRST_PASS}, re-run: ${RR_MIN}m ${RR_SEC}s)" + else + RUN_BREAKDOWN="" + fi + + # Duration icons: >20m high alert, >15m warning, otherwise clock + if [ "$MINUTES" -ge 20 ]; then + DURATION_DISPLAY=":rotating_light: ${DURATION}${RUN_BREAKDOWN} | test: ${TEST_DURATION}" + elif [ "$MINUTES" -ge 15 ]; then + DURATION_DISPLAY=":warning: ${DURATION}${RUN_BREAKDOWN} | test: ${TEST_DURATION}" + else + DURATION_DISPLAY=":clock3: ${DURATION}${RUN_BREAKDOWN} | test: ${TEST_DURATION}" + fi + + # Retest indicator with spec count + if [ "$RETEST_RESULT" != "skipped" ]; then + RETEST_DISPLAY=":repeat: re-run ${RETEST_SPEC_COUNT} spec(s)" + else + RETEST_DISPLAY="" + fi + + echo "duration=${DURATION}" >> $GITHUB_OUTPUT + echo "duration_display=${DURATION_DISPLAY}" >> $GITHUB_OUTPUT + echo "retest_display=${RETEST_DISPLAY}" >> $GITHUB_OUTPUT + + - name: ci/upload-combined-results + if: inputs.workers > 1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-results + path: | + e2e-tests/cypress/logs/ + e2e-tests/cypress/results/ + - name: ci/publish-report + if: inputs.enable_reporting && env.REPORT_WEBHOOK_URL != '' + env: + REPORT_WEBHOOK_URL: ${{ secrets.REPORT_WEBHOOK_URL }} + COMMIT_STATUS_MESSAGE: ${{ steps.final-results.outputs.commit_status_message }} + COLOR: ${{ steps.final-results.outputs.color }} + REPORT_URL: ${{ needs.generate-test-cycle.outputs.status_check_url }} + TEST_TYPE: ${{ inputs.test_type }} + REPORT_TYPE: ${{ inputs.report_type }} + COMMIT_SHA: ${{ inputs.commit_sha }} + REF_BRANCH: ${{ inputs.ref_branch }} + PR_NUMBER: ${{ inputs.pr_number }} + DURATION_DISPLAY: ${{ steps.duration.outputs.duration_display }} + RETEST_DISPLAY: ${{ steps.duration.outputs.retest_display }} + run: | + # Capitalize test type + TEST_TYPE_CAP=$(echo "$TEST_TYPE" | sed 's/.*/\u&/') + + # Build source line based on report type + COMMIT_SHORT="${COMMIT_SHA::7}" + COMMIT_URL="https://github.com/${{ github.repository }}/commit/${COMMIT_SHA}" + if [ "$REPORT_TYPE" = "RELEASE_CUT" ]; then + SOURCE_LINE=":github_round: [${COMMIT_SHORT}](${COMMIT_URL}) on \`${REF_BRANCH}\`" + elif [ "$REPORT_TYPE" = "MASTER" ] || [ "$REPORT_TYPE" = "RELEASE" ]; then + SOURCE_LINE=":git_merge: [${COMMIT_SHORT}](${COMMIT_URL}) on \`${REF_BRANCH}\`" + else + SOURCE_LINE=":open-pull-request: [mattermost-pr-${PR_NUMBER}](https://github.com/${{ github.repository }}/pull/${PR_NUMBER})" + fi + + # Build retest part for message + RETEST_PART="" + if [ -n "$RETEST_DISPLAY" ]; then + RETEST_PART=" | ${RETEST_DISPLAY}" + fi + + # Build payload with attachments + PAYLOAD=$(cat <" + echo "${FAILED} failed, ${PASSED} passed" + echo "" + echo "| Test | File |" + echo "|------|------|" + echo "${FAILED_TESTS}" + echo "" + fi + + echo "" + echo "### Calculation Outputs" + echo "" + echo "| Output | Value |" + echo "|--------|-------|" + echo "| passed | ${PASSED} |" + echo "| failed | ${FAILED} |" + echo "| pending | ${PENDING} |" + echo "| total_specs | ${TOTAL_SPECS} |" + echo "| failed_specs_count | ${FAILED_SPECS_COUNT} |" + echo "| commit_status_message | ${COMMIT_STATUS_MESSAGE} |" + echo "| failed_specs | ${FAILED_SPECS:-none} |" + echo "| duration | ${DURATION_DISPLAY} |" + if [ "$RETEST_RESULT" != "skipped" ]; then + echo "| retested | Yes |" + else + echo "| retested | No |" + fi + + echo "" + echo "---" + echo "[View Full Report](${STATUS_CHECK_URL})" + } >> $GITHUB_STEP_SUMMARY + - name: ci/assert-results + run: | + [ "${{ steps.final-results.outputs.failed }}" = "0" ] + + update-success-status: + runs-on: ubuntu-24.04 + if: always() && needs.report.result == 'success' && needs.calculate-results.result == 'success' + needs: + - generate-test-cycle + - calculate-results + - report + steps: + - uses: mattermost/actions/delivery/update-commit-status@f324ac89b05cc3511cb06e60642ac2fb829f0a63 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.commit_sha }} + context: ${{ inputs.context_name }} + description: "${{ needs.report.outputs.commit_status_message }}, ${{ needs.report.outputs.duration }}, image_tag:${{ inputs.server_image_tag }}${{ inputs.server_image_aliases && format(' ({0})', inputs.server_image_aliases) || '' }}" + status: success + target_url: ${{ needs.generate-test-cycle.outputs.status_check_url }} + + update-failure-status: + runs-on: ubuntu-24.04 + if: always() && (needs.report.result != 'success' || needs.calculate-results.result != 'success') + needs: + - generate-test-cycle + - calculate-results + - report + steps: + - uses: mattermost/actions/delivery/update-commit-status@f324ac89b05cc3511cb06e60642ac2fb829f0a63 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.commit_sha }} + context: ${{ inputs.context_name }} + description: "${{ needs.report.outputs.commit_status_message }}, ${{ needs.report.outputs.duration }}, image_tag:${{ inputs.server_image_tag }}${{ inputs.server_image_aliases && format(' ({0})', inputs.server_image_aliases) || '' }}" + status: failure + target_url: ${{ needs.generate-test-cycle.outputs.status_check_url }} diff --git a/.github/workflows/e2e-tests-cypress.yml b/.github/workflows/e2e-tests-cypress.yml new file mode 100644 index 0000000000..485e8340e2 --- /dev/null +++ b/.github/workflows/e2e-tests-cypress.yml @@ -0,0 +1,194 @@ +--- +name: E2E Tests - Cypress +on: + workflow_call: + inputs: + commit_sha: + type: string + required: true + enable_reporting: + type: boolean + required: false + default: false + server: + type: string + required: false + default: onprem + report_type: + type: string + required: false + pr_number: + type: string + required: false + server_image_tag: + type: string + required: false + description: "Server image tag (e.g., master or short SHA)" + server_edition: + type: string + required: false + description: "Server edition: enterprise (default), fips, or team" + server_image_repo: + type: string + required: false + default: mattermostdevelopment + description: "Docker registry: mattermostdevelopment (default) or mattermost" + server_image_aliases: + type: string + required: false + description: "Comma-separated alias tags for context name (e.g., 'release-11.4, release-11')" + ref_branch: + type: string + required: false + description: "Source branch name for webhook messages (e.g., 'master' or 'release-11.4')" + should_run: + type: string + required: false + default: "true" + description: "Set to 'false' to skip tests and post a success status without running E2E" + 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 + CWS_URL: + required: false + CWS_EXTRA_HTTP_HEADERS: + required: false + +jobs: + generate-build-variables: + runs-on: ubuntu-24.04 + outputs: + branch: "${{ steps.build-vars.outputs.branch }}" + build_id: "${{ steps.build-vars.outputs.build_id }}" + server_image_tag: "${{ steps.build-vars.outputs.server_image_tag }}" + server_image: "${{ steps.build-vars.outputs.server_image }}" + context_suffix: "${{ steps.build-vars.outputs.context_suffix }}" + steps: + - name: ci/generate-build-variables + id: build-vars + env: + COMMIT_SHA: ${{ inputs.commit_sha }} + PR_NUMBER: ${{ inputs.pr_number }} + INPUT_SERVER_IMAGE_TAG: ${{ inputs.server_image_tag }} + RUN_ID: ${{ github.run_id }} + RUN_ATTEMPT: ${{ github.run_attempt }} + run: | + # Use provided server_image_tag or derive from commit SHA + if [ -n "$INPUT_SERVER_IMAGE_TAG" ]; then + SERVER_IMAGE_TAG="$INPUT_SERVER_IMAGE_TAG" + else + SERVER_IMAGE_TAG="${COMMIT_SHA::7}" + fi + + # Validate server_image_tag format (alphanumeric, dots, hyphens, underscores) + if ! [[ "$SERVER_IMAGE_TAG" =~ ^[a-zA-Z0-9._-]+$ ]]; then + echo "::error::Invalid server_image_tag format: ${SERVER_IMAGE_TAG}" + exit 1 + fi + echo "server_image_tag=${SERVER_IMAGE_TAG}" >> $GITHUB_OUTPUT + + # Generate branch name + REF_BRANCH="${{ inputs.ref_branch }}" + if [ -n "$PR_NUMBER" ]; then + echo "branch=server-pr-${PR_NUMBER}" >> $GITHUB_OUTPUT + elif [ -n "$REF_BRANCH" ]; then + echo "branch=server-${REF_BRANCH}-${SERVER_IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "branch=server-commit-${SERVER_IMAGE_TAG}" >> $GITHUB_OUTPUT + fi + + # Determine server image name + EDITION="${{ inputs.server_edition }}" + REPO="${{ inputs.server_image_repo }}" + REPO="${REPO:-mattermostdevelopment}" + case "$EDITION" in + fips) IMAGE_NAME="mattermost-enterprise-fips-edition" ;; + team) IMAGE_NAME="mattermost-team-edition" ;; + *) IMAGE_NAME="mattermost-enterprise-edition" ;; + esac + SERVER_IMAGE="${REPO}/${IMAGE_NAME}:${SERVER_IMAGE_TAG}" + echo "server_image=${SERVER_IMAGE}" >> $GITHUB_OUTPUT + + # Validate server_image_aliases format if provided + ALIASES="${{ inputs.server_image_aliases }}" + if [ -n "$ALIASES" ] && ! [[ "$ALIASES" =~ ^[a-zA-Z0-9._,\ -]+$ ]]; then + echo "::error::Invalid server_image_aliases format: ${ALIASES}" + exit 1 + fi + + # Generate build ID + if [ -n "$EDITION" ] && [ "$EDITION" != "enterprise" ]; then + echo "build_id=${RUN_ID}_${RUN_ATTEMPT}-${SERVER_IMAGE_TAG}-cypress-onprem-${EDITION}" >> $GITHUB_OUTPUT + else + echo "build_id=${RUN_ID}_${RUN_ATTEMPT}-${SERVER_IMAGE_TAG}-cypress-onprem-ent" >> $GITHUB_OUTPUT + fi + + # Generate context name suffix based on report type + REPORT_TYPE="${{ inputs.report_type }}" + case "$REPORT_TYPE" in + MASTER) echo "context_suffix=/master" >> $GITHUB_OUTPUT ;; + RELEASE) echo "context_suffix=/release" >> $GITHUB_OUTPUT ;; + RELEASE_CUT) echo "context_suffix=/release-cut" >> $GITHUB_OUTPUT ;; + *) echo "context_suffix=" >> $GITHUB_OUTPUT ;; + esac + + skip: + needs: + - generate-build-variables + if: inputs.should_run == 'false' + runs-on: ubuntu-24.04 + permissions: + statuses: write + steps: + - name: ci/post-skip-status + env: + GH_TOKEN: ${{ github.token }} + COMMIT_SHA: ${{ inputs.commit_sha }} + CONTEXT_NAME: "e2e-test/cypress-full/${{ inputs.server_edition || 'enterprise' }}${{ needs.generate-build-variables.outputs.context_suffix }}" + run: | + gh api repos/${{ github.repository }}/statuses/${COMMIT_SHA} \ + -f state=success \ + -f context="${CONTEXT_NAME}" \ + -f description="No E2E-relevant changes - skipped" \ + -f target_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + echo "Posted success for ${CONTEXT_NAME}" + + cypress-full: + needs: + - generate-build-variables + if: inputs.should_run != 'false' + uses: ./.github/workflows/e2e-tests-cypress-template.yml + with: + test_type: full + test_filter: '--stage="@prod" --excludeGroup="@te_only,@cloud_only,@high_availability" --sortFirst="@compliance_export,@elasticsearch,@ldap_group,@ldap" --sortLast="@saml,@keycloak,@plugin,@plugins_uninstall,@mfa,@license_removal"' + workers: 40 + enabled_docker_services: "postgres inbucket minio openldap elasticsearch keycloak" + commit_sha: ${{ inputs.commit_sha }} + branch: ${{ needs.generate-build-variables.outputs.branch }} + build_id: ${{ needs.generate-build-variables.outputs.build_id }} + server_image_tag: ${{ needs.generate-build-variables.outputs.server_image_tag }} + server_edition: ${{ inputs.server_edition }} + server_image_repo: ${{ inputs.server_image_repo }} + server_image_aliases: ${{ inputs.server_image_aliases }} + server: ${{ inputs.server }} + enable_reporting: ${{ inputs.enable_reporting }} + report_type: ${{ inputs.report_type }} + ref_branch: ${{ inputs.ref_branch }} + pr_number: ${{ inputs.pr_number }} + context_name: "e2e-test/cypress-full/${{ inputs.server_edition || 'enterprise' }}${{ needs.generate-build-variables.outputs.context_suffix }}" + secrets: + MM_LICENSE: ${{ secrets.MM_LICENSE }} + AUTOMATION_DASHBOARD_URL: ${{ secrets.AUTOMATION_DASHBOARD_URL }} + AUTOMATION_DASHBOARD_TOKEN: ${{ secrets.AUTOMATION_DASHBOARD_TOKEN }} + PUSH_NOTIFICATION_SERVER: ${{ secrets.PUSH_NOTIFICATION_SERVER }} + REPORT_WEBHOOK_URL: ${{ secrets.REPORT_WEBHOOK_URL }} + CWS_URL: ${{ secrets.CWS_URL }} + CWS_EXTRA_HTTP_HEADERS: ${{ secrets.CWS_EXTRA_HTTP_HEADERS }} diff --git a/.github/workflows/e2e-tests-playwright-template.yml b/.github/workflows/e2e-tests-playwright-template.yml new file mode 100644 index 0000000000..ed064758a0 --- /dev/null +++ b/.github/workflows/e2e-tests-playwright-template.yml @@ -0,0 +1,583 @@ +--- +name: E2E Tests - Playwright Template +on: + workflow_call: + inputs: + # Test configuration + test_type: + description: "Type of test run (smoke or full)" + type: string + required: true + test_filter: + description: "Test filter arguments (e.g., --grep @smoke)" + type: string + required: true + workers: + description: "Number of parallel shards" + type: number + required: false + default: 2 + enabled_docker_services: + description: "Space-separated list of docker services to enable" + type: string + required: false + default: "postgres inbucket" + + # Common build variables + commit_sha: + type: string + required: true + branch: + type: string + required: true + build_id: + type: string + required: true + server_image_tag: + description: "Server image tag (e.g., master or short SHA)" + type: string + required: true + server: + type: string + required: false + default: onprem + server_edition: + description: "Server edition: enterprise (default), fips, or team" + type: string + required: false + default: enterprise + server_image_repo: + description: "Docker registry: mattermostdevelopment (default) or mattermost" + type: string + required: false + default: mattermostdevelopment + server_image_aliases: + description: "Comma-separated alias tags for description (e.g., 'release-11.4, release-11')" + type: string + required: false + + # Reporting options + enable_reporting: + type: boolean + required: false + default: false + report_type: + type: string + required: false + ref_branch: + description: "Source branch name for webhook messages (e.g., 'master' or 'release-11.4')" + type: string + required: false + pr_number: + type: string + required: false + + # Commit status configuration + context_name: + description: "GitHub commit status context name" + type: string + required: true + + outputs: + passed: + description: "Number of passed tests" + value: ${{ jobs.report.outputs.passed }} + failed: + description: "Number of failed tests" + value: ${{ jobs.report.outputs.failed }} + report_url: + description: "URL to test report on S3" + value: ${{ jobs.report.outputs.report_url }} + + secrets: + MM_LICENSE: + required: false + REPORT_WEBHOOK_URL: + required: false + AWS_ACCESS_KEY_ID: + required: true + AWS_SECRET_ACCESS_KEY: + required: true + +env: + SERVER_IMAGE: "${{ inputs.server_image_repo }}/${{ inputs.server_edition == 'fips' && 'mattermost-enterprise-fips-edition' || inputs.server_edition == 'team' && 'mattermost-team-edition' || 'mattermost-enterprise-edition' }}:${{ inputs.server_image_tag }}" + +jobs: + update-initial-status: + runs-on: ubuntu-24.04 + steps: + - name: ci/set-initial-status + uses: mattermost/actions/delivery/update-commit-status@f324ac89b05cc3511cb06e60642ac2fb829f0a63 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.commit_sha }} + context: ${{ inputs.context_name }} + description: "tests running, image_tag:${{ inputs.server_image_tag }}${{ inputs.server_image_aliases && format(' ({0})', inputs.server_image_aliases) || '' }}" + status: pending + + generate-test-variables: + runs-on: ubuntu-24.04 + outputs: + workers: "${{ steps.generate-workers.outputs.workers }}" + start_time: "${{ steps.generate-workers.outputs.start_time }}" + steps: + - name: ci/generate-workers + id: generate-workers + run: | + echo "workers=$(jq -nc '[range(1; ${{ inputs.workers }} + 1)]')" >> $GITHUB_OUTPUT + echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + + run-tests: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + continue-on-error: true + needs: + - generate-test-variables + if: needs.generate-test-variables.result == 'success' + strategy: + fail-fast: false + matrix: + worker_index: ${{ fromJSON(needs.generate-test-variables.outputs.workers) }} + defaults: + run: + working-directory: e2e-tests + env: + SERVER: "${{ inputs.server }}" + MM_LICENSE: "${{ secrets.MM_LICENSE }}" + ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}" + TEST: playwright + TEST_FILTER: "${{ inputs.test_filter }}" + PW_SHARD: "${{ format('--shard={0}/{1}', matrix.worker_index, inputs.workers) }}" + BRANCH: "${{ inputs.branch }}-${{ inputs.test_type }}" + BUILD_ID: "${{ inputs.build_id }}" + CI_BASE_URL: "${{ inputs.test_type }}-test-${{ matrix.worker_index }}" + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/playwright/package-lock.json" + - name: ci/get-webapp-node-modules + working-directory: webapp + run: make node_modules + - name: ci/run-tests + run: | + make cloud-init + make + - name: ci/cloud-teardown + if: always() + run: make cloud-teardown + - name: ci/upload-results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: always() + with: + name: playwright-${{ inputs.test_type }}-${{ inputs.server_edition }}-results-${{ matrix.worker_index }} + path: | + e2e-tests/playwright/logs/ + e2e-tests/playwright/results/ + retention-days: 5 + + calculate-results: + runs-on: ubuntu-24.04 + needs: + - generate-test-variables + - run-tests + if: always() && needs.generate-test-variables.result == 'success' + outputs: + passed: ${{ steps.calculate.outputs.passed }} + failed: ${{ steps.calculate.outputs.failed }} + flaky: ${{ steps.calculate.outputs.flaky }} + skipped: ${{ steps.calculate.outputs.skipped }} + total_specs: ${{ steps.calculate.outputs.total_specs }} + failed_specs: ${{ steps.calculate.outputs.failed_specs }} + failed_specs_count: ${{ steps.calculate.outputs.failed_specs_count }} + failed_tests: ${{ steps.calculate.outputs.failed_tests }} + commit_status_message: ${{ steps.calculate.outputs.commit_status_message }} + total: ${{ steps.calculate.outputs.total }} + pass_rate: ${{ steps.calculate.outputs.pass_rate }} + passing: ${{ steps.calculate.outputs.passing }} + color: ${{ steps.calculate.outputs.color }} + test_duration: ${{ steps.calculate.outputs.test_duration }} + end_time: ${{ steps.record-end-time.outputs.end_time }} + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/playwright/package-lock.json" + - name: ci/download-shard-results + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + pattern: playwright-${{ inputs.test_type }}-${{ inputs.server_edition }}-results-* + path: e2e-tests/playwright/shard-results/ + merge-multiple: true + - name: ci/merge-shard-results + working-directory: e2e-tests/playwright + run: | + mkdir -p results/reporter + + # Merge blob reports using Playwright merge-reports (per docs) + npm install --no-save @playwright/test + npx playwright merge-reports --config merge.config.mjs ./shard-results/results/blob-report/ + - name: ci/calculate + id: calculate + uses: ./.github/actions/calculate-playwright-results + with: + original-results-path: e2e-tests/playwright/results/reporter/results.json + - name: ci/upload-merged-results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: playwright-${{ inputs.test_type }}-${{ inputs.server_edition }}-results + path: e2e-tests/playwright/results/ + retention-days: 5 + - name: ci/record-end-time + id: record-end-time + run: echo "end_time=$(date +%s)" >> $GITHUB_OUTPUT + + run-failed-tests: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + needs: + - run-tests + - calculate-results + if: >- + always() && + needs.calculate-results.result == 'success' && + needs.calculate-results.outputs.failed != '0' && + fromJSON(needs.calculate-results.outputs.failed_specs_count) <= 20 + defaults: + run: + working-directory: e2e-tests + env: + SERVER: "${{ inputs.server }}" + MM_LICENSE: "${{ secrets.MM_LICENSE }}" + ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}" + TEST: playwright + BRANCH: "${{ inputs.branch }}-${{ inputs.test_type }}-retest" + BUILD_ID: "${{ inputs.build_id }}-retest" + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 0 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/playwright/package-lock.json" + - name: ci/get-webapp-node-modules + working-directory: webapp + run: make node_modules + - name: ci/run-failed-specs + env: + SPEC_FILES: ${{ needs.calculate-results.outputs.failed_specs }} + run: | + echo "Retesting failed specs: $SPEC_FILES" + make cloud-init + make start-server run-specs + - name: ci/cloud-teardown + if: always() + run: make cloud-teardown + - name: ci/upload-retest-results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: always() + with: + name: playwright-${{ inputs.test_type }}-${{ inputs.server_edition }}-retest-results + path: | + e2e-tests/playwright/logs/ + e2e-tests/playwright/results/ + retention-days: 5 + + report: + runs-on: ubuntu-24.04 + needs: + - generate-test-variables + - run-tests + - calculate-results + - run-failed-tests + if: always() && needs.calculate-results.result == 'success' + outputs: + passed: "${{ steps.final-results.outputs.passed }}" + failed: "${{ steps.final-results.outputs.failed }}" + commit_status_message: "${{ steps.final-results.outputs.commit_status_message }}" + report_url: "${{ steps.upload-to-s3.outputs.report_url }}" + duration: "${{ steps.duration.outputs.duration }}" + duration_display: "${{ steps.duration.outputs.duration_display }}" + retest_display: "${{ steps.duration.outputs.retest_display }}" + defaults: + run: + working-directory: e2e-tests + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + cache: npm + cache-dependency-path: "e2e-tests/playwright/package-lock.json" + + # Download merged results (uploaded by calculate-results) + - name: ci/download-results + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: playwright-${{ inputs.test_type }}-${{ inputs.server_edition }}-results + path: e2e-tests/playwright/results/ + + # Download retest results (only if retest ran) + - name: ci/download-retest-results + if: needs.run-failed-tests.result != 'skipped' + uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 + with: + name: playwright-${{ inputs.test_type }}-${{ inputs.server_edition }}-retest-results + path: e2e-tests/playwright/retest-results/ + + # Calculate results (with optional merge of retest results) + - name: ci/calculate-results + id: final-results + uses: ./.github/actions/calculate-playwright-results + with: + original-results-path: e2e-tests/playwright/results/reporter/results.json + retest-results-path: ${{ needs.run-failed-tests.result != 'skipped' && 'e2e-tests/playwright/retest-results/results/reporter/results.json' || '' }} + + - name: ci/aws-configure + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 + with: + aws-region: us-east-1 + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - name: ci/upload-to-s3 + id: upload-to-s3 + env: + AWS_REGION: us-east-1 + AWS_S3_BUCKET: mattermost-cypress-report + PR_NUMBER: "${{ inputs.pr_number }}" + RUN_ID: "${{ github.run_id }}" + COMMIT_SHA: "${{ inputs.commit_sha }}" + TEST_TYPE: "${{ inputs.test_type }}" + run: | + LOCAL_RESULTS_PATH="playwright/results/" + + # Use PR number if available, otherwise use commit SHA prefix + if [ -n "$PR_NUMBER" ]; then + S3_PATH="server-pr-${PR_NUMBER}/e2e-reports/playwright-${TEST_TYPE}/${RUN_ID}" + else + S3_PATH="server-commit-${COMMIT_SHA::7}/e2e-reports/playwright-${TEST_TYPE}/${RUN_ID}" + fi + + if [[ -d "$LOCAL_RESULTS_PATH" ]]; then + aws s3 sync "$LOCAL_RESULTS_PATH" "s3://${AWS_S3_BUCKET}/${S3_PATH}/results/" \ + --acl public-read --cache-control "no-cache" + fi + + REPORT_URL="https://${AWS_S3_BUCKET}.s3.amazonaws.com/${S3_PATH}/results/reporter/index.html" + echo "report_url=$REPORT_URL" >> "$GITHUB_OUTPUT" + - name: ci/compute-duration + id: duration + env: + START_TIME: ${{ needs.generate-test-variables.outputs.start_time }} + FIRST_PASS_END_TIME: ${{ needs.calculate-results.outputs.end_time }} + RETEST_RESULT: ${{ needs.run-failed-tests.result }} + RETEST_SPEC_COUNT: ${{ needs.calculate-results.outputs.failed_specs_count }} + TEST_DURATION: ${{ steps.final-results.outputs.test_duration }} + run: | + NOW=$(date +%s) + ELAPSED=$((NOW - START_TIME)) + MINUTES=$((ELAPSED / 60)) + SECONDS=$((ELAPSED % 60)) + DURATION="${MINUTES}m ${SECONDS}s" + + # Compute first-pass and re-run durations + FIRST_PASS_ELAPSED=$((FIRST_PASS_END_TIME - START_TIME)) + FP_MIN=$((FIRST_PASS_ELAPSED / 60)) + FP_SEC=$((FIRST_PASS_ELAPSED % 60)) + FIRST_PASS="${FP_MIN}m ${FP_SEC}s" + + if [ "$RETEST_RESULT" != "skipped" ]; then + RERUN_ELAPSED=$((NOW - FIRST_PASS_END_TIME)) + RR_MIN=$((RERUN_ELAPSED / 60)) + RR_SEC=$((RERUN_ELAPSED % 60)) + RUN_BREAKDOWN=" (first-pass: ${FIRST_PASS}, re-run: ${RR_MIN}m ${RR_SEC}s)" + else + RUN_BREAKDOWN="" + fi + + # Duration icons: >20m high alert, >15m warning, otherwise clock + if [ "$MINUTES" -ge 20 ]; then + DURATION_DISPLAY=":rotating_light: ${DURATION}${RUN_BREAKDOWN} | test: ${TEST_DURATION}" + elif [ "$MINUTES" -ge 15 ]; then + DURATION_DISPLAY=":warning: ${DURATION}${RUN_BREAKDOWN} | test: ${TEST_DURATION}" + else + DURATION_DISPLAY=":clock3: ${DURATION}${RUN_BREAKDOWN} | test: ${TEST_DURATION}" + fi + + # Retest indicator with spec count + if [ "$RETEST_RESULT" != "skipped" ]; then + RETEST_DISPLAY=":repeat: re-run ${RETEST_SPEC_COUNT} spec(s)" + else + RETEST_DISPLAY="" + fi + + echo "duration=${DURATION}" >> $GITHUB_OUTPUT + echo "duration_display=${DURATION_DISPLAY}" >> $GITHUB_OUTPUT + echo "retest_display=${RETEST_DISPLAY}" >> $GITHUB_OUTPUT + + - name: ci/publish-report + if: inputs.enable_reporting && env.REPORT_WEBHOOK_URL != '' + env: + REPORT_WEBHOOK_URL: ${{ secrets.REPORT_WEBHOOK_URL }} + COMMIT_STATUS_MESSAGE: ${{ steps.final-results.outputs.commit_status_message }} + COLOR: ${{ steps.final-results.outputs.color }} + REPORT_URL: ${{ steps.upload-to-s3.outputs.report_url }} + TEST_TYPE: ${{ inputs.test_type }} + REPORT_TYPE: ${{ inputs.report_type }} + COMMIT_SHA: ${{ inputs.commit_sha }} + REF_BRANCH: ${{ inputs.ref_branch }} + PR_NUMBER: ${{ inputs.pr_number }} + DURATION_DISPLAY: ${{ steps.duration.outputs.duration_display }} + RETEST_DISPLAY: ${{ steps.duration.outputs.retest_display }} + run: | + # Capitalize test type + TEST_TYPE_CAP=$(echo "$TEST_TYPE" | sed 's/.*/\u&/') + + # Build source line based on report type + COMMIT_SHORT="${COMMIT_SHA::7}" + COMMIT_URL="https://github.com/${{ github.repository }}/commit/${COMMIT_SHA}" + if [ "$REPORT_TYPE" = "RELEASE_CUT" ]; then + SOURCE_LINE=":github_round: [${COMMIT_SHORT}](${COMMIT_URL}) on \`${REF_BRANCH}\`" + elif [ "$REPORT_TYPE" = "MASTER" ] || [ "$REPORT_TYPE" = "RELEASE" ]; then + SOURCE_LINE=":git_merge: [${COMMIT_SHORT}](${COMMIT_URL}) on \`${REF_BRANCH}\`" + else + SOURCE_LINE=":open-pull-request: [mattermost-pr-${PR_NUMBER}](https://github.com/${{ github.repository }}/pull/${PR_NUMBER})" + fi + + # Build retest part for message + RETEST_PART="" + if [ -n "$RETEST_DISPLAY" ]; then + RETEST_PART=" | ${RETEST_DISPLAY}" + fi + + # Build payload with attachments + PAYLOAD=$(cat <" + echo "${FAILED} failed, ${PASSED} passed" + echo "" + echo "| Test | File |" + echo "|------|------|" + echo "${FAILED_TESTS}" + echo "" + fi + + echo "" + echo "### Calculation Outputs" + echo "" + echo "| Output | Value |" + echo "|--------|-------|" + echo "| passed | ${PASSED} |" + echo "| failed | ${FAILED} |" + echo "| flaky | ${FLAKY} |" + echo "| skipped | ${SKIPPED} |" + echo "| total_specs | ${TOTAL_SPECS} |" + echo "| failed_specs_count | ${FAILED_SPECS_COUNT} |" + echo "| commit_status_message | ${COMMIT_STATUS_MESSAGE} |" + echo "| failed_specs | ${FAILED_SPECS:-none} |" + echo "| duration | ${DURATION_DISPLAY} |" + if [ "$RETEST_RESULT" != "skipped" ]; then + echo "| retested | Yes |" + else + echo "| retested | No |" + fi + + echo "" + echo "---" + echo "[View Full Report](${REPORT_URL})" + } >> $GITHUB_STEP_SUMMARY + - name: ci/assert-results + run: | + [ "${{ steps.final-results.outputs.failed }}" = "0" ] + + update-success-status: + runs-on: ubuntu-24.04 + if: always() && needs.report.result == 'success' && needs.calculate-results.result == 'success' + needs: + - calculate-results + - report + steps: + - uses: mattermost/actions/delivery/update-commit-status@f324ac89b05cc3511cb06e60642ac2fb829f0a63 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.commit_sha }} + context: ${{ inputs.context_name }} + description: "${{ needs.report.outputs.commit_status_message }}, ${{ needs.report.outputs.duration }}, image_tag:${{ inputs.server_image_tag }}${{ inputs.server_image_aliases && format(' ({0})', inputs.server_image_aliases) || '' }}" + status: success + target_url: ${{ needs.report.outputs.report_url }} + + update-failure-status: + runs-on: ubuntu-24.04 + if: always() && (needs.report.result != 'success' || needs.calculate-results.result != 'success') + needs: + - calculate-results + - report + steps: + - uses: mattermost/actions/delivery/update-commit-status@f324ac89b05cc3511cb06e60642ac2fb829f0a63 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + repository_full_name: ${{ github.repository }} + commit_sha: ${{ inputs.commit_sha }} + context: ${{ inputs.context_name }} + description: "${{ needs.report.outputs.commit_status_message }}, ${{ needs.report.outputs.duration }}, image_tag:${{ inputs.server_image_tag }}${{ inputs.server_image_aliases && format(' ({0})', inputs.server_image_aliases) || '' }}" + status: failure + target_url: ${{ needs.report.outputs.report_url }} diff --git a/.github/workflows/e2e-tests-playwright.yml b/.github/workflows/e2e-tests-playwright.yml new file mode 100644 index 0000000000..a92bd3d094 --- /dev/null +++ b/.github/workflows/e2e-tests-playwright.yml @@ -0,0 +1,185 @@ +--- +name: E2E Tests - Playwright +on: + workflow_call: + inputs: + commit_sha: + type: string + required: true + enable_reporting: + type: boolean + required: false + default: false + server: + type: string + required: false + default: onprem + report_type: + type: string + required: false + pr_number: + type: string + required: false + server_image_tag: + type: string + required: false + description: "Server image tag (e.g., master or short SHA)" + server_edition: + type: string + required: false + description: "Server edition: enterprise (default), fips, or team" + server_image_repo: + type: string + required: false + default: mattermostdevelopment + description: "Docker registry: mattermostdevelopment (default) or mattermost" + server_image_aliases: + type: string + required: false + description: "Comma-separated alias tags for context name (e.g., 'release-11.4, release-11')" + ref_branch: + type: string + required: false + description: "Source branch name for webhook messages (e.g., 'master' or 'release-11.4')" + should_run: + type: string + required: false + default: "true" + description: "Set to 'false' to skip tests and post a success status without running E2E" + secrets: + MM_LICENSE: + required: false + REPORT_WEBHOOK_URL: + required: false + AWS_ACCESS_KEY_ID: + required: true + AWS_SECRET_ACCESS_KEY: + required: true + +jobs: + generate-build-variables: + runs-on: ubuntu-24.04 + outputs: + branch: "${{ steps.build-vars.outputs.branch }}" + build_id: "${{ steps.build-vars.outputs.build_id }}" + server_image_tag: "${{ steps.build-vars.outputs.server_image_tag }}" + server_image: "${{ steps.build-vars.outputs.server_image }}" + context_suffix: "${{ steps.build-vars.outputs.context_suffix }}" + steps: + - name: ci/generate-build-variables + id: build-vars + env: + COMMIT_SHA: ${{ inputs.commit_sha }} + PR_NUMBER: ${{ inputs.pr_number }} + INPUT_SERVER_IMAGE_TAG: ${{ inputs.server_image_tag }} + RUN_ID: ${{ github.run_id }} + RUN_ATTEMPT: ${{ github.run_attempt }} + run: | + # Use provided server_image_tag or derive from commit SHA + if [ -n "$INPUT_SERVER_IMAGE_TAG" ]; then + SERVER_IMAGE_TAG="$INPUT_SERVER_IMAGE_TAG" + else + SERVER_IMAGE_TAG="${COMMIT_SHA::7}" + fi + + # Validate server_image_tag format (alphanumeric, dots, hyphens, underscores) + if ! [[ "$SERVER_IMAGE_TAG" =~ ^[a-zA-Z0-9._-]+$ ]]; then + echo "::error::Invalid server_image_tag format: ${SERVER_IMAGE_TAG}" + exit 1 + fi + echo "server_image_tag=${SERVER_IMAGE_TAG}" >> $GITHUB_OUTPUT + + # Generate branch name + REF_BRANCH="${{ inputs.ref_branch }}" + if [ -n "$PR_NUMBER" ]; then + echo "branch=server-pr-${PR_NUMBER}" >> $GITHUB_OUTPUT + elif [ -n "$REF_BRANCH" ]; then + echo "branch=server-${REF_BRANCH}-${SERVER_IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "branch=server-commit-${SERVER_IMAGE_TAG}" >> $GITHUB_OUTPUT + fi + + # Determine server image name + EDITION="${{ inputs.server_edition }}" + REPO="${{ inputs.server_image_repo }}" + REPO="${REPO:-mattermostdevelopment}" + case "$EDITION" in + fips) IMAGE_NAME="mattermost-enterprise-fips-edition" ;; + team) IMAGE_NAME="mattermost-team-edition" ;; + *) IMAGE_NAME="mattermost-enterprise-edition" ;; + esac + SERVER_IMAGE="${REPO}/${IMAGE_NAME}:${SERVER_IMAGE_TAG}" + echo "server_image=${SERVER_IMAGE}" >> $GITHUB_OUTPUT + + # Validate server_image_aliases format if provided + ALIASES="${{ inputs.server_image_aliases }}" + if [ -n "$ALIASES" ] && ! [[ "$ALIASES" =~ ^[a-zA-Z0-9._,\ -]+$ ]]; then + echo "::error::Invalid server_image_aliases format: ${ALIASES}" + exit 1 + fi + + # Generate build ID + if [ -n "$EDITION" ] && [ "$EDITION" != "enterprise" ]; then + echo "build_id=${RUN_ID}_${RUN_ATTEMPT}-${SERVER_IMAGE_TAG}-playwright-onprem-${EDITION}" >> $GITHUB_OUTPUT + else + echo "build_id=${RUN_ID}_${RUN_ATTEMPT}-${SERVER_IMAGE_TAG}-playwright-onprem-ent" >> $GITHUB_OUTPUT + fi + + # Generate context name suffix based on report type + REPORT_TYPE="${{ inputs.report_type }}" + case "$REPORT_TYPE" in + MASTER) echo "context_suffix=/master" >> $GITHUB_OUTPUT ;; + RELEASE) echo "context_suffix=/release" >> $GITHUB_OUTPUT ;; + RELEASE_CUT) echo "context_suffix=/release-cut" >> $GITHUB_OUTPUT ;; + *) echo "context_suffix=" >> $GITHUB_OUTPUT ;; + esac + + skip: + needs: + - generate-build-variables + if: inputs.should_run == 'false' + runs-on: ubuntu-24.04 + permissions: + statuses: write + steps: + - name: ci/post-skip-status + env: + GH_TOKEN: ${{ github.token }} + COMMIT_SHA: ${{ inputs.commit_sha }} + CONTEXT_NAME: "e2e-test/playwright-full/${{ inputs.server_edition || 'enterprise' }}${{ needs.generate-build-variables.outputs.context_suffix }}" + run: | + gh api repos/${{ github.repository }}/statuses/${COMMIT_SHA} \ + -f state=success \ + -f context="${CONTEXT_NAME}" \ + -f description="No E2E-relevant changes - skipped" \ + -f target_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + echo "Posted success for ${CONTEXT_NAME}" + + playwright-full: + needs: + - generate-build-variables + if: inputs.should_run != 'false' + uses: ./.github/workflows/e2e-tests-playwright-template.yml + with: + test_type: full + test_filter: '--grep-invert "@visual"' + workers: 4 + enabled_docker_services: "postgres inbucket minio openldap elasticsearch keycloak" + commit_sha: ${{ inputs.commit_sha }} + branch: ${{ needs.generate-build-variables.outputs.branch }} + build_id: ${{ needs.generate-build-variables.outputs.build_id }} + server_image_tag: ${{ needs.generate-build-variables.outputs.server_image_tag }} + server_edition: ${{ inputs.server_edition }} + server_image_repo: ${{ inputs.server_image_repo }} + server_image_aliases: ${{ inputs.server_image_aliases }} + server: ${{ inputs.server }} + enable_reporting: ${{ inputs.enable_reporting }} + report_type: ${{ inputs.report_type }} + ref_branch: ${{ inputs.ref_branch }} + pr_number: ${{ inputs.pr_number }} + context_name: "e2e-test/playwright-full/${{ inputs.server_edition || 'enterprise' }}${{ needs.generate-build-variables.outputs.context_suffix }}" + secrets: + MM_LICENSE: ${{ secrets.MM_LICENSE }} + REPORT_WEBHOOK_URL: ${{ secrets.REPORT_WEBHOOK_URL }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/server/Makefile b/server/Makefile index 26817e88f0..ca5499461e 100644 --- a/server/Makefile +++ b/server/Makefile @@ -218,7 +218,7 @@ ifneq ($(DOCKER_SERVICES_OVERRIDE),true) ENABLED_DOCKER_SERVICES:=$(ENABLED_DOCKER_SERVICES) $(TEMP_DOCKER_SERVICES) endif -start-docker: ## Starts the docker containers for local development. +start-docker: setup-go-work ## Starts the docker containers for local development. ifneq ($(IS_CI),false) @echo CI Build: skipping docker start else ifeq ($(MM_NO_DOCKER),true) @@ -238,7 +238,7 @@ else endif endif -update-docker: stop-docker ## Updates the docker containers for local development. +update-docker: setup-go-work stop-docker ## Updates the docker containers for local development. @echo Updating docker containers $(GO) run ./build/docker-compose-generator/main.go $(ENABLED_DOCKER_SERVICES) | docker compose -f docker-compose.makefile.yml -f /dev/stdin $(DOCKER_COMPOSE_OVERRIDE) up --no-start @@ -273,7 +273,7 @@ else docker compose rm -v endif -plugin-checker: +plugin-checker: setup-go-work $(GO) run $(GOFLAGS) ./public/plugin/checker prepackaged-plugins: ## Populate the prepackaged-plugins directory. @@ -298,7 +298,7 @@ golang-versions: ## Install Golang versions used for compatibility testing (e.g. done export GO_COMPATIBILITY_TEST_VERSIONS="${GO_COMPATIBILITY_TEST_VERSIONS}" -golangci-lint: ## Run golangci-lint on codebase +golangci-lint: setup-go-work ## Run golangci-lint on codebase $(GO) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 ifeq ($(BUILD_ENTERPRISE_READY),true) $(GOBIN)/golangci-lint run ./... ./public/... $(BUILD_ENTERPRISE_DIR)/... @@ -315,11 +315,11 @@ i18n-check: ## Exit on empty translation strings and translation source strings $(GOBIN)/mmgotool i18n clean-empty --portal-dir="" --check $(GOBIN)/mmgotool i18n check-empty-src --portal-dir="" -store-mocks: ## Creates mock files. +store-mocks: setup-go-work ## Creates mock files. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config channels/store/.mockery.yaml -cache-mocks: +cache-mocks: setup-go-work $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config platform/services/cache/.mockery.yaml @@ -327,7 +327,7 @@ telemetry-mocks: ## Creates mock files. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config platform/services/telemetry/.mockery.yaml -store-layers: ## Generate layers for the store +store-layers: setup-go-work ## Generate layers for the store $(GO) generate $(GOFLAGS) ./channels/store new-migration: ## Creates a new migration. Run with make new-migration name=<> @@ -338,47 +338,47 @@ new-migration: ## Creates a new migration. Run with make new-migration name=<> @echo "Generating new migration for postgres" $(GOBIN)/morph new script $(name) --driver postgres --dir channels/db/migrations --sequence -filestore-mocks: ## Creates mock files. +filestore-mocks: setup-go-work ## Creates mock files. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config platform/shared/filestore/.mockery.yaml -ldap-mocks: ## Creates mock files for ldap. +ldap-mocks: setup-go-work ## Creates mock files for ldap. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --dir $(BUILD_ENTERPRISE_DIR)/ldap --all --inpackage --note 'Regenerate this file using `make ldap-mocks`.' -plugin-mocks: ## Creates mock files for plugins. +plugin-mocks: setup-go-work ## Creates mock files for plugins. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config public/plugin/.mockery.yaml -einterfaces-mocks: ## Creates mock files for einterfaces. +einterfaces-mocks: setup-go-work ## Creates mock files for einterfaces. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config einterfaces/.mockery.yaml -searchengine-mocks: ## Creates mock files for searchengines. +searchengine-mocks: setup-go-work ## Creates mock files for searchengines. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config platform/services/searchengine/.mockery.yaml -sharedchannel-mocks: ## Creates mock files for shared channels. +sharedchannel-mocks: setup-go-work ## Creates mock files for shared channels. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config platform/services/sharedchannel/.mockery.yaml -misc-mocks: ## Creates mocks for misc interfaces. +misc-mocks: setup-go-work ## Creates mocks for misc interfaces. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config channels/utils/.mockery.yaml -email-mocks: ## Creates mocks for misc interfaces. +email-mocks: setup-go-work ## Creates mocks for misc interfaces. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config channels/app/email/.mockery.yaml -platform-mocks: ## Creates mocks for platform interfaces. +platform-mocks: setup-go-work ## Creates mocks for platform interfaces. $(GO) install github.com/vektra/mockery/v2/...@v2.53.4 $(GOBIN)/mockery --config channels/app/platform/.mockery.yaml -mmctl-mocks: ## Creates mocks for mmctl +mmctl-mocks: setup-go-work ## Creates mocks for mmctl $(GO) install github.com/golang/mock/mockgen@v1.6.0 $(GOBIN)/mockgen -destination=cmd/mmctl/mocks/client_mock.go -copyright_file=cmd/mmctl/mocks/copyright.txt -package=mocks github.com/mattermost/mattermost/server/v8/cmd/mmctl/client Client -pluginapi: ## Generates api and hooks glue code for plugins +pluginapi: setup-go-work ## Generates api and hooks glue code for plugins cd ./public && $(GO) generate $(GOFLAGS) ./plugin mocks: store-mocks telemetry-mocks filestore-mocks ldap-mocks plugin-mocks einterfaces-mocks searchengine-mocks sharedchannel-mocks misc-mocks email-mocks platform-mocks mmctl-mocks mocks-public cache-mocks @@ -394,15 +394,22 @@ endif setup-go-work: export BUILD_ENTERPRISE_READY := $(BUILD_ENTERPRISE_READY) setup-go-work: ## Sets up your go.work file -ifneq ($(IGNORE_GO_WORK_IF_EXISTS),true) - @echo "Creating a go.work file" - rm -f go.work - $(GO) work init - $(GO) work use . - $(GO) work use ./public -ifeq ($(BUILD_ENTERPRISE_READY),true) - $(GO) work use $(BUILD_ENTERPRISE_DIR) -endif +ifneq ($(SKIP_SETUP_GO_WORK),true) + @set -e; \ + if [ -f go.work ]; then cp -p go.work go.work.bak; fi; \ + rm -f go.work; \ + $(GO) work init; \ + $(GO) work use .; \ + $(GO) work use ./public; \ + if [ "$(BUILD_ENTERPRISE_READY)" = "true" ]; then \ + $(GO) work use $(BUILD_ENTERPRISE_DIR); \ + fi; \ + if [ -f go.work.bak ] && cmp -s go.work go.work.bak; then \ + mv go.work.bak go.work; \ + else \ + rm -f go.work.bak; \ + echo "Created go.work file"; \ + fi endif check-style: plugin-checker vet golangci-lint ## Runs style/lint checks @@ -410,7 +417,7 @@ check-style: plugin-checker vet golangci-lint ## Runs style/lint checks gotestsum: $(GO) install gotest.tools/gotestsum@v1.11.0 -test-compile: gotestsum ## Compile tests. +test-compile: setup-go-work gotestsum ## Compile tests. @echo COMPILE TESTS for package in $(TE_PACKAGES) $(EE_PACKAGES); do \ @@ -481,10 +488,10 @@ else $(GOBIN)/gotestsum --packages="$(TE_PACKAGES)" -- $(GOFLAGS) -short endif -internal-test-web-client: ## Runs web client tests. +internal-test-web-client: setup-go-work ## Runs web client tests. $(GO) run $(GOFLAGS) $(PLATFORM_FILES) test web_client_tests -run-server-for-web-client-tests: ## Tests the server for web client. +run-server-for-web-client-tests: setup-go-work ## Tests the server for web client. $(GO) run $(GOFLAGS) $(PLATFORM_FILES) test web_client_tests_server test-client: ## Test client app. @@ -566,7 +573,7 @@ run-server: setup-go-work prepackaged-binaries validate-go-version start-docker mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files $(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' $(PLATFORM_FILES) $(RUN_IN_BACKGROUND) -debug-server: start-docker ## Compile and start server using delve. +debug-server: setup-go-work start-docker ## Compile and start server using delve. mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files $(DELVE) debug $(PLATFORM_FILES) --build-flags="-ldflags '\ -X github.com/mattermost/mattermost/server/public/model.BuildNumber=$(BUILD_NUMBER)\ @@ -576,7 +583,7 @@ debug-server: start-docker ## Compile and start server using delve. -X github.com/mattermost/mattermost/server/public/model.BuildEnterpriseReady=$(BUILD_ENTERPRISE_READY)'\ -tags '$(BUILD_TAGS)'" -debug-server-headless: start-docker ## Debug server from within an IDE like VSCode or IntelliJ. +debug-server-headless: setup-go-work start-docker ## Debug server from within an IDE like VSCode or IntelliJ. mkdir -p $(BUILD_WEBAPP_DIR)/channels/dist/files $(DELVE) debug --headless --listen=:2345 --api-version=2 --accept-multiclient $(PLATFORM_FILES) --build-flags="-ldflags '\ -X github.com/mattermost/mattermost/server/public/model.BuildNumber=$(BUILD_NUMBER)\ @@ -593,12 +600,12 @@ run-node: export MM_SERVICESETTINGS_LOCALMODESOCKETLOCATION=/var/tmp/mattermost_ run-node: export MM_SQLSETTINGS_DRIVERNAME=postgres run-node: export MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mostest@localhost/mattermost_node_test?sslmode=disable&sslmode=disable&connect_timeout=10&binary_parameters=yes -run-node: start-docker ## Runs a shared channel node. +run-node: setup-go-work start-docker ## Runs a shared channel node. @echo Running mattermost node $(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' $(PLATFORM_FILES) $(RUN_IN_BACKGROUND) -run-cli: start-docker ## Runs CLI. +run-cli: setup-go-work start-docker ## Runs CLI. @echo Running mattermost for development @echo Example should be like 'make ARGS="-version" run-cli' @@ -660,7 +667,7 @@ restart-haserver: restart-client: | stop-client run-client ## Restarts the webapp. -run-job-server: ## Runs the background job server. +run-job-server: setup-go-work ## Runs the background job server. @echo Running job server for development $(GO) run $(GOFLAGS) -ldflags '$(LDFLAGS)' -tags '$(BUILD_TAGS)' $(PLATFORM_FILES) jobserver & @@ -701,7 +708,7 @@ config-openid: ## Configures OpenID. @echo Finished setting up configuration for local OpenID with keycloak -config-reset: ## Resets the config/config.json file to the default production values. +config-reset: setup-go-work ## Resets the config/config.json file to the default production values. @echo Resetting configuration to production default rm -f config/config.json OUTPUT_CONFIG=$(PWD)/config/config.json $(GO) $(GOFLAGS) run -tags production ./scripts/config_generator @@ -709,7 +716,7 @@ config-reset: ## Resets the config/config.json file to the default production va diff-config: ## Compares default configuration between two mattermost versions @./scripts/diff-config.sh -clean: stop-docker ## Clean up everything except persistent server data. +clean: setup-go-work stop-docker ## Clean up everything except persistent server data. @echo Cleaning rm -Rf $(DIST_ROOT) @@ -758,7 +765,7 @@ ifeq ($(BUILD_ENTERPRISE_READY),true) mv enterprise/external_imports.go.orig enterprise/external_imports.go endif -vet: ## Run mattermost go vet specific checks +vet: setup-go-work ## Run mattermost go vet specific checks ## Note that it is pinned to a specific commit, rather than a branch. This is to prevent ## having to backport the fix to multiple release branches for any new change. $(GO) install github.com/mattermost/mattermost-govet/v2@8e4d46e3fad88497dbfe073788a87e75bbae717c @@ -778,7 +785,7 @@ vet-api: ## Run mattermost go vet to verify api4 documentation, currently not pa ./scripts/vet-api-check.sh gen-serialized: export LICENSE_HEADER:=$(LICENSE_HEADER) -gen-serialized: ## Generates serialization methods for hot structs +gen-serialized: setup-go-work ## Generates serialization methods for hot structs # This tool only works at a file level, not at a package level. # There will be some warnings about "unresolved identifiers", # but that is because of the above problem. Since we are generating @@ -810,10 +817,10 @@ ifeq ($(BUILD_ENTERPRISE_READY),true) @! ag --ignore Makefile --ignore-dir runtime '(TODO|XXX|FIXME|"FIX ME")[: ]+' $(BUILD_ENTERPRISE_DIR)/ endif -mmctl-build: ## Compiles and generates the mmctl binary +mmctl-build: setup-go-work ## Compiles and generates the mmctl binary go build -trimpath -ldflags '$(MMCTL_LDFLAGS)' -o bin/mmctl ./cmd/mmctl -mmctl-docs: ## Generate the mmctl docs +mmctl-docs: setup-go-work ## Generate the mmctl docs rm -rf ./cmd/mmctl/docs cd ./cmd/mmctl && go run mmctl.go docs @@ -858,5 +865,5 @@ test-migration: # we also exlude systems table temporarily due to adding some keys while running the initial migration bin/dbcmp --source "${MYSQL_DSN}" --target "${POSTGRES_DSN}" --exclude="db_migrations","ir_","focalboard","systems","attributeview" -test-local-filestore: # Run tests for local filestore +test-local-filestore: setup-go-work # Run tests for local filestore $(GO) test ./platform/shared/filestore -run '^TestLocalFileBackend' -v diff --git a/server/build/release.mk b/server/build/release.mk index 42f523525c..c3142e2907 100644 --- a/server/build/release.mk +++ b/server/build/release.mk @@ -2,7 +2,7 @@ dist: | check-style test package build-linux: build-linux-amd64 build-linux-arm64 -build-linux-amd64: +build-linux-amd64: setup-go-work @echo Build Linux amd64 ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64") env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... @@ -11,7 +11,7 @@ else env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN)/linux_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... endif -build-linux-arm64: +build-linux-arm64: setup-go-work @echo Build Linux arm64 ifeq ($(BUILDER_GOOS_GOARCH),"linux_arm64") env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... @@ -20,7 +20,7 @@ else env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... endif -build-osx: +build-osx: setup-go-work @echo Build OSX amd64 ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64") env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... @@ -36,7 +36,7 @@ else env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... endif -build-windows: +build-windows: setup-go-work @echo Build Windows amd64 ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64") env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... @@ -45,7 +45,7 @@ else env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN)/windows_amd64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./... endif -build-cmd-linux: +build-cmd-linux: setup-go-work @echo Build CMD Linux amd64 ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64") env GOOS=linux GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... @@ -61,7 +61,7 @@ else env GOOS=linux GOARCH=arm64 $(GO) build -o $(GOBIN)/linux_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... endif -build-cmd-osx: +build-cmd-osx: setup-go-work @echo Build CMD OSX amd64 ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64") env GOOS=darwin GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... @@ -77,7 +77,7 @@ else env GOOS=darwin GOARCH=arm64 $(GO) build -o $(GOBIN)/darwin_arm64 $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... endif -build-cmd-windows: +build-cmd-windows: setup-go-work @echo Build CMD Windows amd64 ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64") env GOOS=windows GOARCH=amd64 $(GO) build -o $(GOBIN) $(GOFLAGS) -trimpath -tags '$(BUILD_TAGS) production' -ldflags '$(LDFLAGS)' ./cmd/... @@ -97,7 +97,7 @@ build-client: cd $(BUILD_WEBAPP_DIR) && $(MAKE) dist -package-prep: +package-prep: setup-go-work @ echo Packaging mattermost @# Remove any old files rm -Rf $(DIST_ROOT) diff --git a/server/config.mk b/server/config.mk index 30d8f21fab..de26607766 100644 --- a/server/config.mk +++ b/server/config.mk @@ -26,3 +26,10 @@ LDAP_DATA ?= test # Mock the CWS. MM_ENABLE_CWS_MOCK ?= false + +# Skip running setup-go-work automatically. +# IGNORE_GO_WORK_IF_EXISTS is supported for backwards compatibility. +ifdef IGNORE_GO_WORK_IF_EXISTS +SKIP_SETUP_GO_WORK ?= $(IGNORE_GO_WORK_IF_EXISTS) +endif +SKIP_SETUP_GO_WORK ?= false