mirror of
https://github.com/mattermost/mattermost.git
synced 2026-11-03 12:48:37 -06:00
499 lines
20 KiB
YAML
499 lines
20 KiB
YAML
---
|
|
name: E2E Tests - Cypress Template (test system io dispatch)
|
|
|
|
# Delegates Cypress spec dispatch + reporting to test system io.
|
|
# Authenticates via GitHub Actions OIDC; calling job MUST grant
|
|
# `id-token: write`.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
test_type:
|
|
description: "Type of test run (smoke or full)"
|
|
type: string
|
|
required: true
|
|
workers:
|
|
description: "Number of parallel test system io dispatch workers"
|
|
type: number
|
|
required: false
|
|
default: 40
|
|
enabled_docker_services:
|
|
description: "Space-separated list of docker services to enable"
|
|
type: string
|
|
required: false
|
|
default: "postgres inbucket minio openldap elasticsearch keycloak"
|
|
|
|
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"
|
|
type: string
|
|
required: false
|
|
|
|
enable_reporting:
|
|
type: boolean
|
|
required: false
|
|
default: false
|
|
report_type:
|
|
type: string
|
|
required: false
|
|
ref_branch:
|
|
type: string
|
|
required: false
|
|
pr_number:
|
|
type: string
|
|
required: false
|
|
context_name:
|
|
description: "GitHub commit status context name"
|
|
type: string
|
|
required: true
|
|
|
|
cypress_stage:
|
|
description: "Comma-separated `// Stage:` tags; spec must share at least one. Empty disables filter."
|
|
type: string
|
|
required: false
|
|
default: "@prod"
|
|
cypress_include_group:
|
|
description: "Comma-separated `// Group:` tags; spec must share at least one. Empty disables filter."
|
|
type: string
|
|
required: false
|
|
default: ""
|
|
cypress_exclude_group:
|
|
description: "Comma-separated `// Group:` tags; spec dropped if it shares any."
|
|
type: string
|
|
required: false
|
|
default: "@te_only,@cloud_only,@high_availability"
|
|
cypress_skip_on:
|
|
description: "Comma-separated active-env tag(s); spec dropped if its `// Skip:` line shares any."
|
|
type: string
|
|
required: false
|
|
default: "@headless"
|
|
cypress_sort_first:
|
|
description: "Comma-separated `// Group:` tags; matching specs dispatch first."
|
|
type: string
|
|
required: false
|
|
default: "@compliance_export,@elasticsearch,@ldap_group,@ldap"
|
|
cypress_sort_last:
|
|
description: "Comma-separated `// Group:` tags; matching specs dispatch last."
|
|
type: string
|
|
required: false
|
|
default: "@saml,@keycloak,@plugin,@plugins_uninstall,@mfa,@license_removal"
|
|
retest_on_fail:
|
|
description: "Re-dispatch failed dispatch units once (whole-spec retry, on top of cypress.config retries)"
|
|
type: boolean
|
|
required: false
|
|
default: true
|
|
|
|
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
|
|
ANTHROPIC_API_KEY:
|
|
description: "Optional; enables the second judge in E2E triage"
|
|
required: false
|
|
|
|
# Callers must grant: contents: read, statuses: write, id-token: write
|
|
permissions:
|
|
contents: read
|
|
statuses: write
|
|
id-token: write
|
|
|
|
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:
|
|
prepare-run:
|
|
# Commit statuses are SHA-scoped. Keep every publisher (including the
|
|
# aggregate snapshot) under this same lock; never discard queued publishers.
|
|
concurrency:
|
|
group: e2e-status-publish-${{ inputs.commit_sha }}
|
|
cancel-in-progress: false
|
|
queue: max
|
|
runs-on: ubuntu-24.04
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
statuses: write
|
|
outputs:
|
|
composite-identity-json: ${{ steps.composite-identity.outputs.composite-identity-json }}
|
|
workers-matrix: ${{ steps.matrix.outputs.workers }}
|
|
start_time: ${{ steps.matrix.outputs.start_time }}
|
|
steps:
|
|
- name: ci/checkout-repo
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
ref: ${{ inputs.commit_sha }}
|
|
fetch-depth: 1
|
|
- name: ci/composite-identity
|
|
id: composite-identity
|
|
env:
|
|
CONTEXT_NAME: ${{ inputs.context_name }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
MM_BRANCH: ${{ inputs.branch }}
|
|
MM_SHA: ${{ inputs.commit_sha }}
|
|
PR_NUMBER: ${{ inputs.pr_number }}
|
|
run: |
|
|
# Derive the test-system-io run name from the GitHub commit-status
|
|
# context: drop the `e2e-test/` prefix (the framework name already
|
|
# implies E2E in the dashboard) and swap remaining `/` for `-` so
|
|
# the dashboard URL is path-safe. The commit-status context itself
|
|
# stays unchanged elsewhere — branch protection rules depend on it.
|
|
NAME="${CONTEXT_NAME#e2e-test/}"
|
|
NAME="${NAME//\//-}"
|
|
# gh_pr_number is optional; include it only when present.
|
|
if [ -n "$PR_NUMBER" ]; then
|
|
COMPOSITE_IDENTITY=$(jq -nc \
|
|
--arg repo "${GITHUB_REPOSITORY}" \
|
|
--arg sha "${MM_SHA}" \
|
|
--arg run_id "${GITHUB_RUN_ID}" \
|
|
--arg name "${NAME}" \
|
|
--arg attempt "${GITHUB_RUN_ATTEMPT}" \
|
|
--arg branch "${MM_BRANCH}" \
|
|
--arg pr "${PR_NUMBER}" \
|
|
'{repository:$repo, commit_sha:$sha, gh_run_id:$run_id, name:$name, gh_run_attempt:$attempt, branch:$branch, gh_pr_number:$pr}')
|
|
else
|
|
COMPOSITE_IDENTITY=$(jq -nc \
|
|
--arg repo "${GITHUB_REPOSITORY}" \
|
|
--arg sha "${MM_SHA}" \
|
|
--arg run_id "${GITHUB_RUN_ID}" \
|
|
--arg name "${NAME}" \
|
|
--arg attempt "${GITHUB_RUN_ATTEMPT}" \
|
|
--arg branch "${MM_BRANCH}" \
|
|
'{repository:$repo, commit_sha:$sha, gh_run_id:$run_id, name:$name, gh_run_attempt:$attempt, branch:$branch}')
|
|
fi
|
|
echo "composite-identity-json=${COMPOSITE_IDENTITY}" >> $GITHUB_OUTPUT
|
|
- name: ci/matrix
|
|
id: matrix
|
|
env:
|
|
INPUT_WORKERS: ${{ inputs.workers }}
|
|
run: |
|
|
echo "workers=$(jq -nc --argjson n "${INPUT_WORKERS}" '[range(1; $n+1)]')" >> $GITHUB_OUTPUT
|
|
echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT
|
|
|
|
# The lane list and the reader come from this workflow's own ref (github.sha), the tree the YAML
|
|
# runs from. Never from the tested commit: a commit may predate the file (release cuts).
|
|
- name: ci/checkout-trusted-lanes
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
ref: ${{ github.sha }}
|
|
sparse-checkout: .github
|
|
path: .e2e-trusted
|
|
fetch-depth: 1
|
|
- name: ci/status-lanes
|
|
id: lanes
|
|
uses: ./.e2e-trusted/.github/actions/e2e-status-lanes
|
|
with:
|
|
context-name: ${{ inputs.context_name }}
|
|
- name: ci/claim-status
|
|
uses: mattermost/mattermost-test-automation-toolkit/actions/e2e-test-status@main # actions/e2e-test-status
|
|
with:
|
|
composite-identity: ${{ steps.composite-identity.outputs.composite-identity-json }}
|
|
status-context: ${{ inputs.context_name }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
operation: claim
|
|
state: pending
|
|
description: E2E tests queued or running
|
|
triage-status-context: ${{ steps.lanes.outputs.aggregate }}
|
|
triage-lanes: ${{ steps.lanes.outputs.lanes }}
|
|
|
|
# Decide how the workers get their deps, and bridge the gap when they can't use
|
|
# the cache. On a hit this job is a checkout and a metadata lookup, and the
|
|
# workers restore the warmed entry directly. On a miss — a PR that changes the
|
|
# cypress lockfile, or a master commit that landed before the warm workflow ran
|
|
# — it builds the deps once and ships them as a run-scoped artifact, until the
|
|
# next warm populates the key.
|
|
prep-deps:
|
|
name: prep-deps
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
cache-hit: ${{ steps.probe.outputs.cache-hit }}
|
|
steps:
|
|
- name: ci/checkout-repo
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
ref: ${{ inputs.commit_sha }}
|
|
fetch-depth: 1
|
|
# lookup-only: asking whether the key exists costs a metadata call rather
|
|
# than a ~275 MB download. The workers do the real restore.
|
|
- name: ci/probe-cypress-deps
|
|
id: probe
|
|
uses: ./.github/actions/restore-cypress-deps
|
|
with:
|
|
lookup-only: "true"
|
|
# Which transport a run took is otherwise only legible from which steps
|
|
# below were skipped. Reporting the key alongside the verdict is what makes
|
|
# a miss diagnosable: a lockfile or path drift reads as an unexpected key
|
|
# rather than an unexplained miss.
|
|
- name: ci/report-cypress-deps-transport
|
|
env:
|
|
CACHE_HIT: ${{ steps.probe.outputs.cache-hit }}
|
|
CACHE_KEY: ${{ steps.probe.outputs.key }}
|
|
run: |
|
|
if [ "${CACHE_HIT}" = "true" ]; then
|
|
echo "Cypress deps: cache **hit** on \`${CACHE_KEY}\` — workers restore it directly from the Actions cache." >> "${GITHUB_STEP_SUMMARY}"
|
|
else
|
|
echo "Cypress deps: cache **miss** on \`${CACHE_KEY}\` — installing once and shipping the run-scoped artifact." >> "${GITHUB_STEP_SUMMARY}"
|
|
fi
|
|
# Everything below is the cache-miss path.
|
|
#
|
|
# Provides the @mattermost/eslint-plugin target the cypress install
|
|
# symlinks to.
|
|
- name: ci/setup-webapp-node-modules
|
|
if: steps.probe.outputs.cache-hit != 'true'
|
|
uses: ./.github/actions/webapp-setup
|
|
# Read-only restore of the registry cache warmed daily from master, so the
|
|
# install below refetches as little as possible.
|
|
- name: ci/restore-npm-cache
|
|
if: steps.probe.outputs.cache-hit != 'true'
|
|
uses: ./.github/actions/restore-e2e-npm-cache
|
|
# The postinstall downloads the Cypress binary to ~/.cache/Cypress rather
|
|
# than into node_modules, which is why the artifact carries both.
|
|
- name: ci/install-cypress-deps
|
|
if: steps.probe.outputs.cache-hit != 'true'
|
|
working-directory: e2e-tests/cypress
|
|
run: npm ci
|
|
- name: ci/pack-cypress-deps
|
|
if: steps.probe.outputs.cache-hit != 'true'
|
|
uses: ./.github/actions/pack-cypress-deps
|
|
with:
|
|
fips: ${{ inputs.server_edition == 'fips' }}
|
|
|
|
# Register the Test System IO run AFTER prep-deps so workers reach
|
|
# dispatch-run within Test System IO's inactivity window.
|
|
dispatch-begin:
|
|
runs-on: ubuntu-24.04
|
|
needs: [prepare-run, prep-deps]
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
statuses: write
|
|
steps:
|
|
- name: ci/checkout-repo
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
ref: ${{ inputs.commit_sha }}
|
|
fetch-depth: 1
|
|
- name: ci/dispatch-begin
|
|
uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-dispatch-begin@18a0baa05c20060a4036d38ccdb030c5e53051be # 2026-10-30
|
|
with:
|
|
use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }}
|
|
framework: cypress
|
|
repo-dir: ${{ github.workspace }}
|
|
composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }}
|
|
total-reports-expected: ${{ inputs.workers }}
|
|
retest-on-fail: ${{ inputs.retest_on_fail }}
|
|
cypress-stage: ${{ inputs.cypress_stage }}
|
|
cypress-include-group: ${{ inputs.cypress_include_group }}
|
|
cypress-exclude-group: ${{ inputs.cypress_exclude_group }}
|
|
cypress-skip-on: ${{ inputs.cypress_skip_on }}
|
|
cypress-sort-first: ${{ inputs.cypress_sort_first }}
|
|
cypress-sort-last: ${{ inputs.cypress_sort_last }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
commit-status-context: ${{ inputs.context_name }}
|
|
post-pending-commit-status: "false"
|
|
image-tag: ${{ inputs.server_image_tag }}
|
|
image-aliases: ${{ inputs.server_image_aliases }}
|
|
|
|
workers:
|
|
name: dispatch-run-cypress-${{ inputs.server_edition }}-${{ matrix.worker_index }}
|
|
runs-on: ubuntu-24.04
|
|
# The step below times out first: a slow worker then fails rather than being cancelled, so lane-result
|
|
# still runs and triage reports the missing results instead of leaving its check waiting.
|
|
timeout-minutes: 35
|
|
# prep-deps is already an ancestor via dispatch-begin; naming it directly
|
|
# makes its cache-hit output readable here.
|
|
needs: [prepare-run, prep-deps, dispatch-begin]
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
worker_index: ${{ fromJSON(needs.prepare-run.outputs.workers-matrix) }}
|
|
env:
|
|
COMPOSITE_IDENTITY: ${{ needs.prepare-run.outputs.composite-identity-json }}
|
|
SERVER: "${{ inputs.server }}"
|
|
MM_LICENSE: "${{ secrets.MM_LICENSE }}"
|
|
ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}"
|
|
TEST: cypress
|
|
# The dispatch adapter invokes `npx cypress run` directly (no
|
|
# cross-env), so pin TZ here — several specs assume UTC.
|
|
TZ: Etc/UTC
|
|
BRANCH: "${{ inputs.branch }}"
|
|
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-actions
|
|
# Sparse-checkout just .github/actions from the triggering ref (master)
|
|
# so the composite action below is available before the full checkout
|
|
# overwrites the workspace with inputs.commit_sha.
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
sparse-checkout: .github/actions
|
|
sparse-checkout-cone-mode: true
|
|
- name: ci/runner-prep-for-openldap
|
|
uses: ./.github/actions/runner-prep-openldap
|
|
- name: ci/checkout-repo
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
ref: ${{ inputs.commit_sha }}
|
|
fetch-depth: 0
|
|
- name: ci/setup-webapp-node-modules
|
|
uses: ./.github/actions/webapp-setup
|
|
with:
|
|
read-only: "true"
|
|
# Exactly one of the next two steps runs, per prep-deps' probe. The warmed
|
|
# cache is the normal path; the artifact exists only when the probe missed.
|
|
- name: ci/restore-cypress-deps
|
|
if: needs.prep-deps.outputs.cache-hit == 'true'
|
|
uses: ./.github/actions/restore-cypress-deps
|
|
with:
|
|
fail-on-cache-miss: "true"
|
|
- name: ci/reuse-cypress-deps
|
|
if: needs.prep-deps.outputs.cache-hit != 'true'
|
|
uses: ./.github/actions/unpack-cypress-deps
|
|
with:
|
|
fips: ${{ inputs.server_edition == 'fips' }}
|
|
- name: ci/cloud-init
|
|
working-directory: e2e-tests
|
|
run: make cloud-init
|
|
- name: ci/start-server
|
|
working-directory: e2e-tests
|
|
run: make start-server
|
|
- name: ci/dispatch-run
|
|
timeout-minutes: 25
|
|
uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-dispatch-run@18a0baa05c20060a4036d38ccdb030c5e53051be # 2026-10-30
|
|
with:
|
|
use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }}
|
|
framework: cypress
|
|
composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }}
|
|
repo-dir: ${{ github.workspace }}
|
|
artifacts-root: ${{ github.workspace }}/worker-artifacts
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
gh-job-name: dispatch-run-cypress-${{ inputs.server_edition }}-${{ matrix.worker_index }}
|
|
- name: ci/cloud-teardown
|
|
if: always()
|
|
working-directory: e2e-tests
|
|
run: make cloud-teardown
|
|
- name: ci/upload-debug-artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
with:
|
|
name: cypress-${{ inputs.test_type }}-${{ inputs.server_edition }}-debug-${{ matrix.worker_index }}
|
|
path: |
|
|
e2e-tests/cypress/logs/
|
|
e2e-tests/cypress/results/
|
|
e2e-tests/cypress/tests/screenshots/
|
|
worker-artifacts/
|
|
retention-days: 5
|
|
if-no-files-found: ignore
|
|
|
|
report:
|
|
runs-on: ubuntu-24.04
|
|
needs: [prepare-run, dispatch-begin, workers]
|
|
if: always()
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
pull-requests: read
|
|
outputs:
|
|
commit_status_description: ${{ steps.summary.outputs.commit_status_description }}
|
|
webhook_payload: ${{ steps.summary.outputs.webhook_payload }}
|
|
report_url: ${{ steps.summary.outputs.report_url }}
|
|
summary_ok: ${{ steps.summary.outcome == 'success' }}
|
|
steps:
|
|
- name: ci/checkout-repo
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
- name: ci/run-summary
|
|
if: always()
|
|
id: summary
|
|
continue-on-error: true
|
|
uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-summary@18a0baa05c20060a4036d38ccdb030c5e53051be # 2026-10-30
|
|
with:
|
|
use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }}
|
|
composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }}
|
|
framework: cypress
|
|
report-type: ${{ inputs.report_type }}
|
|
image-tag: ${{ inputs.server_image_tag }}
|
|
image-aliases: ${{ inputs.server_image_aliases }}
|
|
server-image: ${{ env.SERVER_IMAGE }}
|
|
pr-number: ${{ inputs.pr_number }}
|
|
ref-branch: ${{ inputs.ref_branch }}
|
|
commit-status-context: ${{ inputs.context_name }}
|
|
update-commit-status: "false"
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Status, triage, webhook and the pass/fail gate: see e2e-tests-lane-result.yml.
|
|
lane-result:
|
|
needs: [prepare-run, dispatch-begin, workers, report]
|
|
if: ${{ !cancelled() && needs.workers.result != 'cancelled' && needs.dispatch-begin.result != 'cancelled' }}
|
|
permissions:
|
|
contents: read
|
|
statuses: write
|
|
pull-requests: read
|
|
uses: ./.github/workflows/e2e-tests-lane-result.yml
|
|
with:
|
|
composite_identity_json: ${{ needs.prepare-run.outputs.composite-identity-json }}
|
|
context_name: ${{ inputs.context_name }}
|
|
commit_sha: ${{ inputs.commit_sha }}
|
|
execution_success: ${{ needs.prepare-run.result == 'success' && needs.dispatch-begin.result == 'success' && needs.workers.result == 'success' }}
|
|
summary_ok: ${{ needs.report.outputs.summary_ok == 'true' }}
|
|
description: ${{ needs.report.outputs.commit_status_description }}
|
|
report_url: ${{ needs.report.outputs.report_url }}
|
|
test_root: e2e-tests/cypress
|
|
enable_reporting: ${{ inputs.enable_reporting }}
|
|
webhook_payload: ${{ needs.report.outputs.webhook_payload }}
|
|
secrets:
|
|
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
REPORT_WEBHOOK_URL: ${{ secrets.REPORT_WEBHOOK_URL }}
|