mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-12 14:10:14 +00:00
* MM-71189 Preserve feature flags in Detox config patches Co-authored-by: mattermost-code <matty-code@mattermost.com> * Guard getFeatureFlags against empty config responses Return an error instead of treating a missing config body as an empty flag set, so a dropped GET does not patch only the requested flags. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Retrigger CI after Detox iOS and iPad E2E flakes Co-authored-by: mattermost-code <matty-code@mattermost.com> * test(e2e): apply one declared server baseline at provisioning Provisioning drives each test server to one declared baseline: the config keys E2E needs and the full FeatureFlags map, written read-modify-write so no flag is reset. Env-pinned keys are reported, not patched, and leftover E2E policies, remote clusters and property fields are removed. Provisioning also disables stray plugins once. * test(e2e): assert the server baseline instead of patching config in specs These specs patched server-wide settings (CRT, archived channels, auto-replies, ...) and restored them while other shards depended on them. They now assert the provisioned baseline instead. * test(e2e): split CI servers into pool and mutation roles; remove the locks Each platform's server A becomes a read-only pool for 27 shards (and iPad and Maestro); server B (and on iOS part of SITE_3) runs one serial mutation shard with the specs in detox/e2e/server_mutation_specs.json. The classification, server and SITE_3 locks go. - split-tests.js places mutation specs by the manifest and packs the pool by duration; a spec that needs B never falls back to the shared SITE_3. - A pool request guard, an ESLint rule and a manifest checker keep server-wide writes off the pool. Mutation shards re-apply the baseline before each spec file. - Mutating tests move to their own spec files; ABAC specs use channel-scoped permission policies; MFA and Terms of Service run on the mutation server. - Pool shards no longer disable provisioned plugins, so the Agents specs run. * fix(e2e): fail a spec when the baseline cannot be applied locally * fix(e2e): keep transport retries for feature-flag and baseline reads Restore the retry in getFeatureFlags, which apiEnableFeatureFlags calls directly, and retry the baseline assertion's GET requests on transport failures. * test(e2e): cover retries of dropped config and baseline reads * test(e2e): apply one declared server baseline at provisioning Provisioning drives each test server to one declared baseline: the config keys E2E needs and the full FeatureFlags map, written read-modify-write so no flag is reset. Env-pinned keys are reported, not patched, and leftover E2E policies, remote clusters and property fields are removed. Provisioning also disables stray plugins once. * test(e2e): assert the server baseline instead of patching config in specs These specs patched server-wide settings (CRT, archived channels, auto-replies, ...) and restored them while other shards depended on them. They now assert the provisioned baseline instead. * fix(e2e): fail a spec when the baseline cannot be applied locally * fix(e2e): keep transport retries for feature-flag and baseline reads Restore the retry in getFeatureFlags, which apiEnableFeatureFlags calls directly, and retry the baseline assertion's GET requests on transport failures. * test(e2e): cover retries of dropped config and baseline reads * E2E: stop burn-on-read spec turning other feature flags off Its partial FeatureFlags patch reset every other flag on the shared server, which turned ChannelAttributes off under channel_attributes on another shard. Enable only the missing flags, as redacted_files and upload_permission do, and do the same for the dialog spec. Also tap the attach button the app actually renders in MM-T339_1. * E2E: route flag-off spec to B on iOS; tap the real attach button in the max-size spec * test(e2e): apply one declared server baseline at provisioning Provisioning drives each test server to one declared baseline: the config keys E2E needs and the full FeatureFlags map, written read-modify-write so no flag is reset. Env-pinned keys are reported, not patched, and leftover E2E policies, remote clusters and property fields are removed. Provisioning also disables stray plugins once. * test(e2e): assert the server baseline instead of patching config in specs These specs patched server-wide settings (CRT, archived channels, auto-replies, ...) and restored them while other shards depended on them. They now assert the provisioned baseline instead. * fix(e2e): fail a spec when the baseline cannot be applied locally * fix(e2e): keep transport retries for feature-flag and baseline reads Restore the retry in getFeatureFlags, which apiEnableFeatureFlags calls directly, and retry the baseline assertion's GET requests on transport failures. * test(e2e): cover retries of dropped config and baseline reads * E2E: stop burn-on-read spec turning other feature flags off Its partial FeatureFlags patch reset every other flag on the shared server, which turned ChannelAttributes off under channel_attributes on another shard. Enable only the missing flags, as redacted_files and upload_permission do, and do the same for the dialog spec. Also tap the attach button the app actually renders in MM-T339_1. * E2E: wait for the results service before retrying a shard upload upload-artifact does not retry network errors (ENOTFOUND, ETIMEDOUT), and the retry fired ~30 ms after the first failure, inside the same DNS outage. A shard whose specs all passed was then reported as "spec did not run". Each retry now waits (bounded, 120 s) for the results host to answer, with a second retry. * E2E: run the specs a killed iOS Jest never started; record why it died When Jest is killed between spec files (exit code = null) it writes no report, and the specs after that point never run. Run only those once in a fresh Jest and merge the results; specs that ran keep their own result, so a spec that crashes Jest stays red. Also sample memory and collect jetsam/crash reports so the next kill names its cause. * E2E: keep the demo plugin off the shared servers; 32 shards The demo plugin posts into every team on every server event and inside every config save, and on our servers each of those posts fails. Only the two dialog specs use it, so install it only on mutation server B, disable it everywhere else (failing provisioning if it stays on), and route both dialog specs to B. Drop the DialogOnlyMode setting, which v0.12.1 does not have. Raise Detox shards 28 -> 32. * E2E: give the demo plugin a settings entry on the servers that install it Demo plugin v0.12.1's ConfigurationWillBeSaved hands the whole config back when it has no settings entry, and that round trip resets settings it does not know, or that are false, to their defaults. With DialogOnlyMode gone from the baseline, fresh B servers had no entry, so ChannelAttributesRequired=true and EnableMobileUpload=false were reverted inside the save. An empty entry keeps the hook out of the save. --------- Co-authored-by: mattermost-code <matty-code@mattermost.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: yasserfaraazkhan <yasser.khan@mattermost.com>
667 lines
29 KiB
YAML
667 lines
29 KiB
YAML
name: Compatibility Matrix Testing
|
||
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
CMT_MATRIX:
|
||
description: "A JSON object representing the testing matrix"
|
||
required: true
|
||
type: string
|
||
MOBILE_VERSION:
|
||
description: "The mattermost mobile version to test"
|
||
required: true
|
||
|
||
# Cancel overlapping CMT runs for the same ref; matterwick cleans up cancelled servers via workflow_run completed.
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
# CMT runs against a freshly-cut build-release-NNN branch, so the app content
|
||
# never matches main's fingerprint — the S3 build cache would always miss.
|
||
# CMT's optimization is structural instead: each app (iOS, Android Detox,
|
||
# Android Maestro) is built ONCE below and reused across every server-version
|
||
# leg of the matrix via github.run_id artifacts.
|
||
jobs:
|
||
calculate-commit-hash:
|
||
runs-on: ubuntu-24.04
|
||
outputs:
|
||
MOBILE_SHA: ${{ steps.repo.outputs.MOBILE_SHA }}
|
||
steps:
|
||
- name: cmt/checkout-mobile
|
||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
with:
|
||
persist-credentials: false
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
|
||
- name: cmt/calculate-mattermost-sha
|
||
id: repo
|
||
run: echo "MOBILE_SHA=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
||
|
||
# Phase 5 — base TSIO identity for this CMT run. Each matrix leg builds its
|
||
# own per-job report group (total_reports_expected=1) from this base + tsio-shard-name.
|
||
cmt-identity:
|
||
name: Calculate CMT TSIO base identity
|
||
runs-on: ubuntu-24.04
|
||
permissions:
|
||
contents: read
|
||
needs:
|
||
- calculate-commit-hash
|
||
- prepare-maestro-matrix
|
||
outputs:
|
||
tsio-base-config: ${{ steps.id.outputs.tsio-base-config }}
|
||
tsio-job-configs: ${{ steps.id.outputs.tsio-job-configs }}
|
||
steps:
|
||
- name: Sparse checkout TSIO job-config helper
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||
with:
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
persist-credentials: false
|
||
sparse-checkout: |
|
||
detox/utils/build-tsio-job-config.js
|
||
|
||
- id: id
|
||
env:
|
||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||
MM_SHA: ${{ needs.calculate-commit-hash.outputs.MOBILE_SHA }}
|
||
MOBILE_VERSION: ${{ inputs.MOBILE_VERSION }}
|
||
CMT_MATRIX: ${{ inputs.CMT_MATRIX }}
|
||
MAESTRO_MATRIX: ${{ needs.prepare-maestro-matrix.outputs.matrix }}
|
||
run: |
|
||
BRANCH=$(echo "$MOBILE_VERSION" | sed 's#^refs/heads/##; s#^refs/tags/##')
|
||
BASE=$(jq -nc \
|
||
--arg repo "$GITHUB_REPOSITORY" --arg sha "$MM_SHA" --arg run_id "$GITHUB_RUN_ID" \
|
||
--arg attempt "$GITHUB_RUN_ATTEMPT" --arg branch "$BRANCH" \
|
||
'{repository:$repo, commit_sha:$sha, gh_run_id:$run_id, gh_run_attempt:$attempt, branch:$branch, name:"mobile-release", run_group:"mobile-release", framework:"detox"}')
|
||
# No status_context — templates expand per tsio-shard-name via buildTsioJobConfig.
|
||
TSIO_BASE=$(jq -nc --argjson identity "$BASE" \
|
||
'{composite_identity: $identity, total_reports_expected: 1}')
|
||
echo "tsio-base-config=$TSIO_BASE" >> "$GITHUB_OUTPUT"
|
||
|
||
# Per-shard configs so the final rollup can poll every leg's TSIO group.
|
||
export BASE CMT_MATRIX MAESTRO_MATRIX
|
||
JOB_CONFIGS=$(node -e '
|
||
const {buildTsioJobConfigMap, cmtJobKeys} = require("./detox/utils/build-tsio-job-config.js");
|
||
const base = JSON.parse(process.env.BASE);
|
||
const keys = cmtJobKeys(JSON.parse(process.env.CMT_MATRIX), JSON.parse(process.env.MAESTRO_MATRIX));
|
||
process.stdout.write(JSON.stringify(buildTsioJobConfigMap(base, keys)));
|
||
')
|
||
echo "tsio-job-configs=$JOB_CONFIGS" >> "$GITHUB_OUTPUT"
|
||
|
||
update-initial-status:
|
||
runs-on: ubuntu-24.04
|
||
permissions:
|
||
contents: read
|
||
statuses: write
|
||
needs:
|
||
- calculate-commit-hash
|
||
- cmt-identity
|
||
steps:
|
||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||
with:
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
persist-credentials: false
|
||
|
||
# Pending statuses are posted per matrix leg when each job starts; keep a
|
||
# single umbrella pending only when MOBILE_SHA is known so the PR/commit
|
||
# UI shows CMT is running before the first shard uploads.
|
||
- name: Build TSIO report URL (release group prefix)
|
||
id: tsio-url
|
||
env:
|
||
TSIO_BASE: ${{ needs.cmt-identity.outputs.tsio-base-config }}
|
||
run: |
|
||
URL=$(jq -nr --argjson cfg "$TSIO_BASE" --arg base "https://test-io.test.mattermost.com" '
|
||
($cfg.composite_identity) as $id
|
||
| ($id.repository | split("/") | .[-1]) as $repo
|
||
| ($id.branch // "main"
|
||
| sub("^refs/heads/"; "")
|
||
| sub("^refs/tags/"; "")
|
||
| gsub("/"; "~")) as $branch
|
||
| ($id.commit_sha[0:7]) as $sha
|
||
| "\($base)/reports/\($repo|@uri)/\($branch|@uri)/\($sha)/mobile-release?gh_run_id=\($id.gh_run_id)"
|
||
')
|
||
echo "target_url=$URL" >> "$GITHUB_OUTPUT"
|
||
|
||
- uses: ./.github/actions/update-commit-status
|
||
if: ${{ needs.calculate-commit-hash.outputs.MOBILE_SHA != '' }}
|
||
env:
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
with:
|
||
repository_full_name: ${{ github.repository }}
|
||
commit_sha: ${{ needs.calculate-commit-hash.outputs.MOBILE_SHA }}
|
||
context: e2e-test/compatibility-matrix-testing
|
||
description: "Compatibility Matrix Testing for ${{ inputs.MOBILE_VERSION }} version — per-job checks also posted"
|
||
status: pending
|
||
target_url: ${{ steps.tsio-url.outputs.target_url }}
|
||
|
||
# Maestro only runs against the latest server version; matrix filtering happens here since job-level if: can't access matrix.
|
||
prepare-maestro-matrix:
|
||
runs-on: ubuntu-24.04
|
||
outputs:
|
||
matrix: ${{ steps.filter.outputs.matrix }}
|
||
steps:
|
||
- name: Filter CMT matrix to latest server only
|
||
id: filter
|
||
env:
|
||
CMT_MATRIX: ${{ inputs.CMT_MATRIX }}
|
||
run: |
|
||
matrix=$(echo "$CMT_MATRIX" | jq -c '{server: [.server[] | select(.latest == true)]}')
|
||
if [ "$(echo "$matrix" | jq '.server | length')" -eq 0 ]; then
|
||
matrix=$(echo "$CMT_MATRIX" | jq -c '{server: [.server[-1]]}')
|
||
fi
|
||
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
|
||
|
||
# Provisions license, config and plugins (demo plugin on site 2 only) before tests.
|
||
# CMT previously skipped that and only used Matterwick-booted servers, so
|
||
# interactive_dialog_plugin failed 24× with "Demo plugin is not active".
|
||
provision-cmt-servers:
|
||
name: Provision CMT test servers
|
||
runs-on: ubuntu-24.04
|
||
needs:
|
||
- calculate-commit-hash
|
||
steps:
|
||
- name: Checkout Repository
|
||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||
with:
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
persist-credentials: false
|
||
|
||
- name: Collect unique matrix server URLs
|
||
id: urls
|
||
env:
|
||
CMT_MATRIX: ${{ inputs.CMT_MATRIX }}
|
||
run: |
|
||
# Full-suite rows run the server-mutation specs, which need their own site 2; fail here, not after the builds.
|
||
BAD_ROWS=$(echo "$CMT_MATRIX" | jq -r '
|
||
.server[]
|
||
| select(.latest == true)
|
||
| . as $row
|
||
| ["android", "ios"][]
|
||
| select(($row[. + "_site_2_url"] // "") == "" or $row[. + "_site_2_url"] == $row[. + "_site_1_url"])
|
||
| "\($row.version // "unknown"): \(.)_site_2_url is missing or equals \(.)_site_1_url"
|
||
')
|
||
if [ -n "$BAD_ROWS" ]; then
|
||
echo "::error::Full-suite CMT rows need a distinct site 2 URL per platform:"
|
||
printf '%s\n' "$BAD_ROWS"
|
||
exit 1
|
||
fi
|
||
|
||
# The demo plugin goes only to full-suite site 2 URLs; a pool, shared or smoke server must not be one of them.
|
||
DEMO_CONFLICTS=$(echo "$CMT_MATRIX" | jq -r '
|
||
. as $m
|
||
| ([ $m.server[] | select(.latest != true) | .android_site_1_url, .android_site_2_url, .ios_site_1_url, .ios_site_2_url, .site_3_url ]
|
||
+ [ $m.server[] | select(.latest == true) | .android_site_1_url, .ios_site_1_url, .site_3_url ]
|
||
| map(select(. != null and . != ""))) as $pool
|
||
| $m.server[] | select(.latest == true) | . as $row
|
||
| ["android", "ios"][] as $p
|
||
| $row[$p + "_site_2_url"] as $url
|
||
| select($url != null and $url != "" and ($pool | any(. == $url)))
|
||
| "\($row.version // "unknown"): \($p)_site_2_url \($url) is also a pool, shared or smoke server"
|
||
')
|
||
if [ -n "$DEMO_CONFLICTS" ]; then
|
||
echo "::error::The demo plugin would land on a pool server; these site 2 URLs are also pool, shared or smoke servers:"
|
||
printf '%s\n' "$DEMO_CONFLICTS"
|
||
exit 1
|
||
fi
|
||
|
||
URLS=$(echo "$CMT_MATRIX" | jq -r '
|
||
.server[]
|
||
| .android_site_1_url, .android_site_2_url, .ios_site_1_url, .ios_site_2_url, .site_3_url
|
||
| select(. != null and . != "")
|
||
' | awk 'NF && !seen[$0]++')
|
||
if [ -z "$URLS" ]; then
|
||
echo "::error::CMT_MATRIX contained no server URLs to provision"
|
||
exit 1
|
||
fi
|
||
# Only full-suite rows run the mutation shard on site 2 (smoke rows use smoke_test, which has no mutation specs).
|
||
DEMO_URLS=$(echo "$CMT_MATRIX" | jq -r '
|
||
.server[]
|
||
| select(.latest == true)
|
||
| .android_site_2_url, .ios_site_2_url
|
||
| select(. != null and . != "")
|
||
' | awk 'NF && !seen[$0]++')
|
||
echo "Provisioning $(echo "$URLS" | wc -l | tr -d ' ') unique server(s)"
|
||
{
|
||
echo "list<<EOF"
|
||
printf '%s\n' "$URLS"
|
||
echo "EOF"
|
||
echo "demo<<EOF"
|
||
if [ -n "$DEMO_URLS" ]; then printf '%s\n' "$DEMO_URLS"; fi
|
||
echo "EOF"
|
||
} >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Provision test servers
|
||
uses: ./.github/actions/provision-e2e-servers
|
||
env:
|
||
ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }}
|
||
ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }}
|
||
with:
|
||
server-urls: ${{ steps.urls.outputs.list }}
|
||
demo-plugin-server-urls: ${{ steps.urls.outputs.demo }}
|
||
|
||
# Input follows the below schema
|
||
# {
|
||
# "server": [
|
||
# {
|
||
# "version": "9.6.1",
|
||
# "android_site_1_url": "https://android-1.example.test",
|
||
# "android_site_2_url": "https://android-2.example.test",
|
||
# "ios_site_1_url": "https://ios-1.example.test",
|
||
# "ios_site_2_url": "https://ios-2.example.test",
|
||
# "site_3_url": "https://shared-3.example.test"
|
||
# },
|
||
# {
|
||
# "version": "9.5.2",
|
||
# "android_site_1_url": "https://android-1-older.example.test",
|
||
# "android_site_2_url": "https://android-2-older.example.test",
|
||
# "ios_site_1_url": "https://ios-1-older.example.test",
|
||
# "ios_site_2_url": "https://ios-2-older.example.test",
|
||
# "site_3_url": "https://shared-3-older.example.test"
|
||
# }
|
||
# ]
|
||
# }
|
||
build-ios-simulator:
|
||
# macos-14 predates Xcode 26 and has none pre-installed, so prepare-ios-build's
|
||
# Xcode 26 requirement always fails here. e2e-detox-pr.yml and
|
||
# e2e-maestro-template.yml already build on macos-26 successfully — match that.
|
||
runs-on: macos-26
|
||
needs:
|
||
- update-initial-status
|
||
permissions:
|
||
contents: read
|
||
actions: write
|
||
steps:
|
||
- name: Checkout Repository
|
||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
with:
|
||
persist-credentials: false
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
|
||
- name: Prepare iOS Build
|
||
uses: ./.github/actions/prepare-ios-build
|
||
|
||
- name: Build iOS Simulator
|
||
env:
|
||
TAG: "${{ inputs.MOBILE_VERSION }}"
|
||
GITHUB_TOKEN: "${{ secrets.MM_MOBILE_GITHUB_TOKEN }}"
|
||
run: bundle exec fastlane ios simulator --env ios.simulator
|
||
working-directory: ./fastlane
|
||
|
||
- name: Upload iOS Simulator Build
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: ios-build-simulator-${{ github.run_id }}
|
||
path: Mattermost-simulator-*.app.zip
|
||
|
||
build-android-apk:
|
||
runs-on: ubuntu-latest-8-cores
|
||
needs:
|
||
- update-initial-status
|
||
permissions:
|
||
contents: read
|
||
actions: write
|
||
env:
|
||
ORG_GRADLE_PROJECT_jvmargs: -Xmx8g
|
||
steps:
|
||
- name: Prune Docker to free up space
|
||
run: docker system prune -af
|
||
|
||
- name: Checkout Repository
|
||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
with:
|
||
persist-credentials: false
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
|
||
- name: Prepare Android Build
|
||
uses: ./.github/actions/prepare-android-build
|
||
env:
|
||
STORE_FILE: ${{ secrets.MM_MOBILE_STORE_FILE }}
|
||
STORE_ALIAS: ${{ secrets.MM_MOBILE_STORE_ALIAS }}
|
||
STORE_PASSWORD: ${{ secrets.MM_MOBILE_STORE_PASSWORD }}
|
||
MATTERMOST_BUILD_GH_TOKEN: ${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}
|
||
|
||
- name: Detox build
|
||
run: |
|
||
cd detox
|
||
npm ci --prefer-offline --no-audit --no-fund
|
||
npm run e2e:android-build
|
||
|
||
- name: Upload Android Build
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: android-build-files-${{ github.run_id }}
|
||
# Match e2e-detox-pr.yml's glob shape exactly: the wildcard must start
|
||
# right after "build/" so upload-artifact only strips "android/app/build/"
|
||
# and preserves the outputs/apk/... subtree. e2e-android-template.yml's
|
||
# Download APK artifact step (shared by PR + CMT) expects that shape.
|
||
path: "android/app/build/**/*"
|
||
|
||
build-android-apk-maestro:
|
||
runs-on: ubuntu-latest-8-cores
|
||
needs:
|
||
- update-initial-status
|
||
permissions:
|
||
contents: read
|
||
actions: write
|
||
env:
|
||
ORG_GRADLE_PROJECT_jvmargs: -Xmx8g
|
||
steps:
|
||
- name: Prune Docker to free up space
|
||
run: docker system prune -af
|
||
|
||
- name: Checkout Repository
|
||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||
with:
|
||
persist-credentials: false
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
|
||
- name: Prepare Android Build
|
||
uses: ./.github/actions/prepare-android-build
|
||
env:
|
||
STORE_FILE: ${{ secrets.MM_MOBILE_STORE_FILE }}
|
||
STORE_ALIAS: ${{ secrets.MM_MOBILE_STORE_ALIAS }}
|
||
STORE_PASSWORD: ${{ secrets.MM_MOBILE_STORE_PASSWORD }}
|
||
MATTERMOST_BUILD_GH_TOKEN: ${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}
|
||
|
||
- name: Build Maestro Android APK
|
||
uses: ./.github/actions/build-android-maestro-apk
|
||
|
||
- name: Upload Maestro Android Build
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: android-maestro-build-files-${{ github.run_id }}
|
||
path: android/app/build/outputs/apk/release/
|
||
|
||
# latest entry runs full suite, older entries run smoke only. Job-level if: can't conditionally skip matrix legs, so inputs vary instead.
|
||
detox-e2e:
|
||
name: mobile-cmt-ios${{ matrix.server.latest && '-full' || '-smoke' }}-${{ matrix.server.version }}
|
||
permissions:
|
||
contents: read
|
||
# nested template mints an OIDC token for the TSIO upload.
|
||
id-token: write
|
||
actions: read
|
||
statuses: write
|
||
# Nested template finalize reads PR labels to honour E2E/Override; a
|
||
# reusable workflow cannot escalate past the caller, so grant it here too.
|
||
issues: read
|
||
uses: ./.github/workflows/e2e-ios-template.yml
|
||
needs:
|
||
- build-ios-simulator
|
||
- cmt-identity
|
||
- provision-cmt-servers
|
||
strategy:
|
||
fail-fast: false
|
||
matrix: ${{ fromJson(inputs.CMT_MATRIX) }}
|
||
secrets:
|
||
MM_MOBILE_E2E_ADMIN_EMAIL: ${{ secrets.MM_MOBILE_E2E_ADMIN_EMAIL }}
|
||
MM_MOBILE_E2E_ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }}
|
||
MM_MOBILE_E2E_ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }}
|
||
MM_MOBILE_E2E_ZEPHYR_API_KEY: ${{ secrets.MM_MOBILE_E2E_ZEPHYR_API_KEY }}
|
||
MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX: ${{ secrets.MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX }}
|
||
MM_MOBILE_E2E_WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }}
|
||
MM_E2E_MASTER_HEALTH_WEBHOOK_URL: ${{ secrets.MM_E2E_MASTER_HEALTH_WEBHOOK_URL }}
|
||
MM_E2E_RELEASE_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }}
|
||
MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }}
|
||
MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }}
|
||
MM_MOBILE_E2E_WEBHOOK_PUBLIC_BASE_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_PUBLIC_BASE_URL }}
|
||
MM_MOBILE_E2E_CLOUDFLARED_TUNNEL_TOKEN: ${{ secrets.MM_MOBILE_E2E_CLOUDFLARED_TUNNEL_TOKEN }}
|
||
with:
|
||
tsio-config: ${{ needs.cmt-identity.outputs.tsio-base-config }}
|
||
tsio-shard-name: detox-ios-Server_${{ matrix.server.version }}
|
||
run-type: "RELEASE"
|
||
MM_OTHER_PLATFORM_POOL_URL: ${{ matrix.server.android_site_1_url }}
|
||
MM_TEST_SERVER_URL: ${{ matrix.server.ios_site_1_url }}
|
||
MM_TEST_SERVER_URL_2: ${{ matrix.server.ios_site_2_url }}
|
||
MM_TEST_SERVER_URL_3: ${{ matrix.server.site_3_url }}
|
||
MOBILE_VERSION: ${{ inputs.MOBILE_VERSION }}
|
||
search_path: ${{ matrix.server.latest && 'detox/e2e/test' || 'detox/e2e/test/products/channels/smoke_test' }}
|
||
# Full suite matches the PR/main shard count (template default 32); smoke stays single-shard.
|
||
parallelism: ${{ matrix.server.latest && '32' || '1' }}
|
||
artifact_run_id: ${{ github.run_id }}
|
||
|
||
detox-ipad-e2e:
|
||
name: mobile-cmt-ipad-${{ matrix.server.version }}
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
actions: read
|
||
statuses: write
|
||
# Nested template finalize reads PR labels to honour E2E/Override; a
|
||
# reusable workflow cannot escalate past the caller, so grant it here too.
|
||
issues: read
|
||
uses: ./.github/workflows/e2e-ios-template.yml
|
||
needs:
|
||
- build-ios-simulator
|
||
- cmt-identity
|
||
- provision-cmt-servers
|
||
strategy:
|
||
fail-fast: false
|
||
matrix: ${{ fromJson(inputs.CMT_MATRIX) }}
|
||
secrets:
|
||
MM_MOBILE_E2E_ADMIN_EMAIL: ${{ secrets.MM_MOBILE_E2E_ADMIN_EMAIL }}
|
||
MM_MOBILE_E2E_ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }}
|
||
MM_MOBILE_E2E_ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }}
|
||
MM_MOBILE_E2E_ZEPHYR_API_KEY: ${{ secrets.MM_MOBILE_E2E_ZEPHYR_API_KEY }}
|
||
MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX: ${{ secrets.MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX }}
|
||
MM_MOBILE_E2E_WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }}
|
||
MM_E2E_MASTER_HEALTH_WEBHOOK_URL: ${{ secrets.MM_E2E_MASTER_HEALTH_WEBHOOK_URL }}
|
||
MM_E2E_RELEASE_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }}
|
||
MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }}
|
||
MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }}
|
||
MM_MOBILE_E2E_WEBHOOK_PUBLIC_BASE_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_PUBLIC_BASE_URL }}
|
||
MM_MOBILE_E2E_CLOUDFLARED_TUNNEL_TOKEN: ${{ secrets.MM_MOBILE_E2E_CLOUDFLARED_TUNNEL_TOKEN }}
|
||
with:
|
||
tsio-config: ${{ needs.cmt-identity.outputs.tsio-base-config }}
|
||
tsio-shard-name: detox-ipad-Server_${{ matrix.server.version }}
|
||
run-type: "RELEASE"
|
||
MM_OTHER_PLATFORM_POOL_URL: ${{ matrix.server.android_site_1_url }}
|
||
MM_TEST_SERVER_URL: ${{ matrix.server.ios_site_1_url }}
|
||
MM_TEST_SERVER_URL_2: ${{ matrix.server.ios_site_2_url }}
|
||
MM_TEST_SERVER_URL_3: ${{ matrix.server.site_3_url }}
|
||
MOBILE_VERSION: ${{ inputs.MOBILE_VERSION }}
|
||
ios_device_name: "iPad Pro 13-inch (M5)"
|
||
ios_device_os_name: "iOS 26.2"
|
||
search_path: "detox/e2e/test/products/channels/ipad"
|
||
parallelism: "1"
|
||
detox_config: "ios.ipad.debug"
|
||
artifact_run_id: ${{ github.run_id }}
|
||
|
||
# Maestro runs only against the latest server version (no smoke-on-older variant).
|
||
maestro-ios-e2e:
|
||
name: mobile-cmt-maestro-ios-${{ matrix.server.version }}
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
actions: read
|
||
statuses: write
|
||
# Nested template finalize reads PR labels to honour E2E/Override; a
|
||
# reusable workflow cannot escalate past the caller, so grant it here too.
|
||
issues: read
|
||
uses: ./.github/workflows/e2e-maestro-template.yml
|
||
needs:
|
||
- build-ios-simulator
|
||
- prepare-maestro-matrix
|
||
- cmt-identity
|
||
- provision-cmt-servers
|
||
strategy:
|
||
fail-fast: false
|
||
matrix: ${{ fromJson(needs.prepare-maestro-matrix.outputs.matrix) }}
|
||
secrets:
|
||
MM_MOBILE_E2E_ADMIN_EMAIL: ${{ secrets.MM_MOBILE_E2E_ADMIN_EMAIL }}
|
||
MM_MOBILE_E2E_ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }}
|
||
MM_MOBILE_E2E_ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }}
|
||
MM_MOBILE_E2E_ZEPHYR_API_KEY: ${{ secrets.MM_MOBILE_E2E_ZEPHYR_API_KEY }}
|
||
MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }}
|
||
MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }}
|
||
MM_MOBILE_E2E_WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }}
|
||
MM_E2E_MASTER_HEALTH_WEBHOOK_URL: ${{ secrets.MM_E2E_MASTER_HEALTH_WEBHOOK_URL }}
|
||
MM_E2E_RELEASE_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }}
|
||
with:
|
||
tsio-config: ${{ needs.cmt-identity.outputs.tsio-base-config }}
|
||
tsio-shard-name: maestro-ios-Server_${{ matrix.server.version }}
|
||
platform: ios
|
||
run-type: "RELEASE"
|
||
MM_TEST_SERVER_URL: ${{ matrix.server.ios_site_1_url }}
|
||
MOBILE_VERSION: ${{ inputs.MOBILE_VERSION }}
|
||
record_tests_in_zephyr: true
|
||
artifact_run_id: ${{ github.run_id }}
|
||
|
||
detox-android-e2e:
|
||
name: mobile-cmt-android${{ matrix.server.latest && '-full' || '-smoke' }}-${{ matrix.server.version }}
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
actions: read
|
||
statuses: write
|
||
# Nested template finalize reads PR labels to honour E2E/Override; a
|
||
# reusable workflow cannot escalate past the caller, so grant it here too.
|
||
issues: read
|
||
uses: ./.github/workflows/e2e-android-template.yml
|
||
needs:
|
||
- build-android-apk
|
||
- cmt-identity
|
||
- provision-cmt-servers
|
||
strategy:
|
||
fail-fast: false
|
||
matrix: ${{ fromJson(inputs.CMT_MATRIX) }}
|
||
secrets:
|
||
MM_MOBILE_E2E_ADMIN_EMAIL: ${{ secrets.MM_MOBILE_E2E_ADMIN_EMAIL }}
|
||
MM_MOBILE_E2E_ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }}
|
||
MM_MOBILE_E2E_ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }}
|
||
MM_MOBILE_E2E_ZEPHYR_API_KEY: ${{ secrets.MM_MOBILE_E2E_ZEPHYR_API_KEY }}
|
||
MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX: ${{ secrets.MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX }}
|
||
MM_MOBILE_E2E_WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }}
|
||
MM_E2E_MASTER_HEALTH_WEBHOOK_URL: ${{ secrets.MM_E2E_MASTER_HEALTH_WEBHOOK_URL }}
|
||
MM_E2E_RELEASE_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }}
|
||
MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }}
|
||
MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }}
|
||
MM_MOBILE_E2E_WEBHOOK_PUBLIC_BASE_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_PUBLIC_BASE_URL }}
|
||
MM_MOBILE_E2E_CLOUDFLARED_TUNNEL_TOKEN: ${{ secrets.MM_MOBILE_E2E_CLOUDFLARED_TUNNEL_TOKEN }}
|
||
with:
|
||
tsio-config: ${{ needs.cmt-identity.outputs.tsio-base-config }}
|
||
tsio-shard-name: detox-android-Server_${{ matrix.server.version }}
|
||
run-android-tests: true
|
||
run-type: "RELEASE"
|
||
MM_OTHER_PLATFORM_POOL_URL: ${{ matrix.server.ios_site_1_url }}
|
||
MM_TEST_SERVER_URL: ${{ matrix.server.android_site_1_url }}
|
||
MM_TEST_SERVER_URL_2: ${{ matrix.server.android_site_2_url }}
|
||
MM_TEST_SERVER_URL_3: ${{ matrix.server.site_3_url }}
|
||
MOBILE_VERSION: ${{ inputs.MOBILE_VERSION }}
|
||
search_path: ${{ matrix.server.latest && 'detox/e2e/test' || 'detox/e2e/test/products/channels/smoke_test' }}
|
||
# Full suite matches the PR/main shard count (template default 32); smoke stays single-shard.
|
||
parallelism: ${{ matrix.server.latest && '32' || '1' }}
|
||
artifact_run_id: ${{ github.run_id }}
|
||
|
||
# Maestro runs only against the latest server version (no smoke-on-older variant).
|
||
maestro-android-e2e:
|
||
name: mobile-cmt-maestro-android-${{ matrix.server.version }}
|
||
permissions:
|
||
contents: read
|
||
id-token: write
|
||
actions: read
|
||
statuses: write
|
||
# Nested template finalize reads PR labels to honour E2E/Override; a
|
||
# reusable workflow cannot escalate past the caller, so grant it here too.
|
||
issues: read
|
||
uses: ./.github/workflows/e2e-maestro-template.yml
|
||
needs:
|
||
- build-android-apk-maestro
|
||
- prepare-maestro-matrix
|
||
- cmt-identity
|
||
- provision-cmt-servers
|
||
strategy:
|
||
fail-fast: false
|
||
matrix: ${{ fromJson(needs.prepare-maestro-matrix.outputs.matrix) }}
|
||
secrets:
|
||
MM_MOBILE_E2E_ADMIN_EMAIL: ${{ secrets.MM_MOBILE_E2E_ADMIN_EMAIL }}
|
||
MM_MOBILE_E2E_ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }}
|
||
MM_MOBILE_E2E_ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }}
|
||
MM_MOBILE_E2E_ZEPHYR_API_KEY: ${{ secrets.MM_MOBILE_E2E_ZEPHYR_API_KEY }}
|
||
MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }}
|
||
MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }}
|
||
MM_MOBILE_E2E_WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }}
|
||
MM_E2E_MASTER_HEALTH_WEBHOOK_URL: ${{ secrets.MM_E2E_MASTER_HEALTH_WEBHOOK_URL }}
|
||
MM_E2E_RELEASE_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }}
|
||
with:
|
||
tsio-config: ${{ needs.cmt-identity.outputs.tsio-base-config }}
|
||
tsio-shard-name: maestro-android-Server_${{ matrix.server.version }}
|
||
platform: android
|
||
run-type: "RELEASE"
|
||
MM_TEST_SERVER_URL: ${{ matrix.server.android_site_1_url }}
|
||
MOBILE_VERSION: ${{ inputs.MOBILE_VERSION }}
|
||
artifact_run_id: ${{ github.run_id }}
|
||
record_tests_in_zephyr: true
|
||
|
||
update-final-status:
|
||
runs-on: ubuntu-24.04
|
||
if: always()
|
||
permissions:
|
||
contents: read
|
||
statuses: write
|
||
# the channel rollup mints an OIDC token to read each shard's TSIO group.
|
||
id-token: write
|
||
needs:
|
||
- cmt-identity
|
||
- calculate-commit-hash
|
||
- provision-cmt-servers
|
||
- detox-e2e
|
||
- detox-android-e2e
|
||
- detox-ipad-e2e
|
||
- maestro-ios-e2e
|
||
- maestro-android-e2e
|
||
steps:
|
||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||
with:
|
||
ref: ${{ inputs.MOBILE_VERSION }}
|
||
persist-credentials: false
|
||
|
||
- name: Resolve upstream matrix outcome
|
||
id: upstream
|
||
run: |
|
||
UPSTREAM="true"
|
||
for r in "${{ needs.provision-cmt-servers.result }}" "${{ needs.detox-e2e.result }}" "${{ needs.detox-android-e2e.result }}" "${{ needs.detox-ipad-e2e.result }}" "${{ needs.maestro-ios-e2e.result }}" "${{ needs.maestro-android-e2e.result }}"; do
|
||
case "$r" in
|
||
success|skipped) ;;
|
||
*) UPSTREAM="false" ;;
|
||
esac
|
||
done
|
||
echo "succeeded=$UPSTREAM" >> "$GITHUB_OUTPUT"
|
||
|
||
# Umbrella context only — each matrix leg already posted <shard> from TSIO.
|
||
- name: Finalize umbrella CMT commit status from CI job results
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
COMMIT_SHA: ${{ needs.calculate-commit-hash.outputs.MOBILE_SHA }}
|
||
REPO: ${{ github.repository }}
|
||
TARGET_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
UPSTREAM: ${{ steps.upstream.outputs.succeeded }}
|
||
run: |
|
||
if [ "$UPSTREAM" = "true" ]; then
|
||
STATE=success
|
||
DESC="Compatibility Matrix Testing completed — see per-job e2e-test/* checks"
|
||
else
|
||
STATE=failure
|
||
DESC="Compatibility Matrix Testing failed — see per-job e2e-test/* checks"
|
||
fi
|
||
gh api --method POST "repos/${REPO}/statuses/${COMMIT_SHA}" \
|
||
-f state="$STATE" \
|
||
-f context="e2e-test/compatibility-matrix-testing" \
|
||
-f description="$DESC" \
|
||
-f target_url="$TARGET_URL"
|
||
|
||
# Per-shard TSIO groups are finalized in each matrix leg, which leaves channel
|
||
# notify off so N legs do not spam. This umbrella polls every shard group and
|
||
# posts one aggregated summary (no second commit status) — same path as PR/main.
|
||
- name: Post Mattermost CMT channel rollup
|
||
if: always()
|
||
env:
|
||
JOB_CONFIGS: ${{ needs.cmt-identity.outputs.tsio-job-configs }}
|
||
MATTERMOST_CMT_WEBHOOK_URL: ${{ secrets.MM_E2E_RELEASE_WEBHOOK_URL }}
|
||
UPSTREAM_SUCCEEDED: ${{ steps.upstream.outputs.succeeded }}
|
||
run: |
|
||
node detox/utils/tsio-channel-notify-rollup.js \
|
||
--job-configs="$JOB_CONFIGS" \
|
||
--upstream-succeeded="$UPSTREAM_SUCCEEDED" \
|
||
--poll-attempts=6 || true
|
||
|
||
# Instance cleanup is handled by matterwick on workflow_run completed.
|