diff --git a/.github/workflows/compatibility-matrix-testing.yml b/.github/workflows/compatibility-matrix-testing.yml index d2bb1258c2e..d2a797a18fd 100644 --- a/.github/workflows/compatibility-matrix-testing.yml +++ b/.github/workflows/compatibility-matrix-testing.yml @@ -203,9 +203,8 @@ jobs: TSIO_POLL_DELAY_MS: 5000 COMMIT_STATUS_CONTEXT: e2e/compatibility-matrix-testing # Incoming webhook for the Desktop CMT/RC results channel (separate from - # PR/master MM_DESKTOP_E2E_WEBHOOK_URL). Optional — when unset, TSIO - # status still flips and channel notify is skipped. - # RC / CMT channel (same destination as master E2E via resolveWebhookUrl). + # PR MM_DESKTOP_E2E_WEBHOOK_URL and master MM_E2E_MASTER_HEALTH_WEBHOOK_URL). + # Optional — when unset, TSIO status still flips and channel notify is skipped. MATTERMOST_CMT_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }} # TSIO only sees test-case-level results — a job-level failure with no # matching failed test (hung worker teardown, crashed runner, npm ci diff --git a/.github/workflows/e2e-functional-template.yml b/.github/workflows/e2e-functional-template.yml index 1256f917c5a..4b8ece4fffb 100644 --- a/.github/workflows/e2e-functional-template.yml +++ b/.github/workflows/e2e-functional-template.yml @@ -303,7 +303,7 @@ jobs: - name: e2e/upload-report-to-tsio if: ${{ always() && inputs.tsio-config != '' && hashFiles('e2e/test-results/results.json') != '' }} continue-on-error: true - uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-report-upload@606431efb597481b0857066be8130e86c22d2a03 # release-0.11.0 / 2026-07-21 + uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-report-upload@45fb048848edc392f398144f45e2f3e30f43aeaa # release-0.12.0 / main@45fb048 (#81 Maestro FilePath + Detox paths, #82 background finalize) with: composite-identity: ${{ toJSON(fromJSON(inputs.tsio-config).composite_identity) }} total-reports-expected: ${{ fromJSON(inputs.tsio-config).total_reports_expected }} diff --git a/.github/workflows/e2e-functional.yml b/.github/workflows/e2e-functional.yml index 37944879f1a..2aabcd2d327 100644 --- a/.github/workflows/e2e-functional.yml +++ b/.github/workflows/e2e-functional.yml @@ -186,10 +186,11 @@ jobs: TSIO_POLL_DELAY_MS: 5000 COMMIT_STATUS_CONTEXT: e2e-test/desktop-playwright UPSTREAM_JOBS_SUCCEEDED: ${{ needs.e2e-tests.result == 'success' && needs.e2e-policy-tests.result == 'success' }} - # Master shares the CMT/RC channel; PR posts to the E2E QA channel. + # PR → MM_DESKTOP_E2E_WEBHOOK_URL; master → MM_E2E_MASTER_HEALTH_WEBHOOK_URL; + # CMT/RC uses MM_E2E_RELEASE_WEBHOOK_URL (compatibility-matrix-testing.yml). # Routing is by compositeIdentity.name in cmt-channel-notify.js. - MATTERMOST_CMT_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }} MATTERMOST_E2E_WEBHOOK_URL: ${{ secrets.MM_DESKTOP_E2E_WEBHOOK_URL }} + MATTERMOST_MASTER_HEALTH_WEBHOOK_URL: ${{ secrets.MM_E2E_MASTER_HEALTH_WEBHOOK_URL }} with: script: | const {reportUrl, status, stats} = await require('./e2e/utils/tsio-report-status.js')({ @@ -434,7 +435,7 @@ jobs: - name: e2e/upload-report-to-tsio if: ${{ always() && needs.prepare-matrix.outputs.tsio-composite-identity != '' && hashFiles('e2e/test-results/results.json') != '' }} continue-on-error: true - uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-report-upload@606431efb597481b0857066be8130e86c22d2a03 # release-0.11.0 / 2026-07-21 + uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-report-upload@45fb048848edc392f398144f45e2f3e30f43aeaa # release-0.12.0 / main@45fb048 (#81 Maestro FilePath + Detox paths, #82 background finalize) with: composite-identity: ${{ needs.prepare-matrix.outputs.tsio-composite-identity }} total-reports-expected: ${{ needs.prepare-matrix.outputs.tsio-total-reports-expected }} diff --git a/e2e/utils/cmt-channel-notify.js b/e2e/utils/cmt-channel-notify.js index e8619e0496d..12e10265209 100644 --- a/e2e/utils/cmt-channel-notify.js +++ b/e2e/utils/cmt-channel-notify.js @@ -87,22 +87,28 @@ function parseCmtJobName(jobName) { } /** - * Webhook routing: - * cmt-desktop + desktop-master → MM_E2E_RELEASE_WEBHOOK_URL (RC / master channel) - * desktop-pr → MM_DESKTOP_E2E_WEBHOOK_URL (PR channel) + * Webhook routing (fail closed for named report groups): + * cmt-desktop → MATTERMOST_CMT_WEBHOOK_URL only (MM_E2E_RELEASE_WEBHOOK_URL) + * desktop-master → MATTERMOST_MASTER_HEALTH_WEBHOOK_URL only (MM_E2E_MASTER_HEALTH_WEBHOOK_URL) + * desktop-pr → MATTERMOST_E2E_WEBHOOK_URL only (MM_DESKTOP_E2E_WEBHOOK_URL) * - * MATTERMOST_WEBHOOK_URL remains a fallback for workflows that set only one URL. + * Named groups never fall back to MATTERMOST_WEBHOOK_URL (avoids posting + * CMT/PR/master to the wrong channel when a dedicated secret is missing). + * Unknown names still use MATTERMOST_WEBHOOK_URL as a generic fallback. * * @param {string} reportName - compositeIdentity.name * @param {NodeJS.ProcessEnv} [env] * @returns {string} */ function resolveWebhookUrl(reportName, env = process.env) { - if (reportName === 'cmt-desktop' || reportName === 'desktop-master') { - return env.MATTERMOST_CMT_WEBHOOK_URL || env.MATTERMOST_WEBHOOK_URL || ''; + if (reportName === 'cmt-desktop') { + return env.MATTERMOST_CMT_WEBHOOK_URL || ''; + } + if (reportName === 'desktop-master') { + return env.MATTERMOST_MASTER_HEALTH_WEBHOOK_URL || ''; } if (reportName === 'desktop-pr') { - return env.MATTERMOST_E2E_WEBHOOK_URL || env.MATTERMOST_WEBHOOK_URL || ''; + return env.MATTERMOST_E2E_WEBHOOK_URL || ''; } return env.MATTERMOST_WEBHOOK_URL || ''; } @@ -451,7 +457,7 @@ async function fetchPerJobCountsFromConsolidated(baseUrl, compositeIdentity, gro */ async function postMattermostWebhook({core, webhookUrl, text, username = 'Desktop E2E'}) { if (!webhookUrl) { - core.info('MATTERMOST_WEBHOOK_URL not set — skipping E2E channel notify'); + core.info('Mattermost webhook URL not set — skipping E2E channel notify'); return; } diff --git a/e2e/utils/cmt-channel-notify.test.js b/e2e/utils/cmt-channel-notify.test.js index d419dca8f48..82d33c251e0 100644 --- a/e2e/utils/cmt-channel-notify.test.js +++ b/e2e/utils/cmt-channel-notify.test.js @@ -78,24 +78,56 @@ describe('cmt-channel-notify', () => { const env = { MATTERMOST_CMT_WEBHOOK_URL: 'https://mm.example/hooks/cmt', MATTERMOST_E2E_WEBHOOK_URL: 'https://mm.example/hooks/e2e', + MATTERMOST_MASTER_HEALTH_WEBHOOK_URL: 'https://mm.example/hooks/master-health', MATTERMOST_WEBHOOK_URL: 'https://mm.example/hooks/fallback', }; - it('sends CMT and master to the CMT webhook', () => { + it('sends CMT to the release webhook', () => { assert.equal(resolveWebhookUrl('cmt-desktop', env), env.MATTERMOST_CMT_WEBHOOK_URL); - assert.equal(resolveWebhookUrl('desktop-master', env), env.MATTERMOST_CMT_WEBHOOK_URL); + }); + + it('sends master runs to the master-health webhook', () => { + assert.equal(resolveWebhookUrl('desktop-master', env), env.MATTERMOST_MASTER_HEALTH_WEBHOOK_URL); }); it('sends PR runs to the E2E webhook', () => { assert.equal(resolveWebhookUrl('desktop-pr', env), env.MATTERMOST_E2E_WEBHOOK_URL); }); - it('does not fall back master to the PR webhook when CMT secret is missing', () => { + it('does not fall back CMT to the E2E webhook when release secret is missing', () => { + assert.equal( + resolveWebhookUrl('cmt-desktop', {MATTERMOST_E2E_WEBHOOK_URL: env.MATTERMOST_E2E_WEBHOOK_URL}), + '', + ); + }); + + it('does not use the shared webhook when a dedicated CMT secret is missing', () => { assert.equal( - resolveWebhookUrl('desktop-master', {MATTERMOST_E2E_WEBHOOK_URL: env.MATTERMOST_E2E_WEBHOOK_URL}), + resolveWebhookUrl('cmt-desktop', {MATTERMOST_WEBHOOK_URL: env.MATTERMOST_WEBHOOK_URL}), '', ); }); + + it('does not use the shared or PR webhook when master-health secret is missing', () => { + assert.equal( + resolveWebhookUrl('desktop-master', { + MATTERMOST_WEBHOOK_URL: env.MATTERMOST_WEBHOOK_URL, + MATTERMOST_E2E_WEBHOOK_URL: env.MATTERMOST_E2E_WEBHOOK_URL, + }), + '', + ); + }); + + it('does not use the shared webhook when a dedicated E2E secret is missing for PR', () => { + assert.equal( + resolveWebhookUrl('desktop-pr', {MATTERMOST_WEBHOOK_URL: env.MATTERMOST_WEBHOOK_URL}), + '', + ); + }); + + it('still uses the shared webhook for unknown report names', () => { + assert.equal(resolveWebhookUrl('unknown-report', env), env.MATTERMOST_WEBHOOK_URL); + }); }); describe('formatCmtChannelMessage', () => { diff --git a/e2e/utils/tsio-report-status.js b/e2e/utils/tsio-report-status.js index e722b06771e..48e6b73e378 100644 --- a/e2e/utils/tsio-report-status.js +++ b/e2e/utils/tsio-report-status.js @@ -267,8 +267,9 @@ async function reportTsioStatus({ }); // Channel notify (best-effort). Routing (see resolveWebhookUrl): - // cmt-desktop / desktop-master → MM_E2E_RELEASE_WEBHOOK_URL - // desktop-pr → MM_DESKTOP_E2E_WEBHOOK_URL + // cmt-desktop → MM_E2E_RELEASE_WEBHOOK_URL + // desktop-master → MM_E2E_MASTER_HEALTH_WEBHOOK_URL + // desktop-pr → MM_DESKTOP_E2E_WEBHOOK_URL // Failures here must not undo a successfully written commit status. try { const notifyNames = new Set(['cmt-desktop', 'desktop-pr', 'desktop-master']);