From 9d318dc4cdabe6c9fd76a286ba7c07515a991fec Mon Sep 17 00:00:00 2001 From: sabril <5334504+saturninoabril@users.noreply.github.com> Date: Tue, 19 May 2026 11:26:39 +0800 Subject: [PATCH] refactor: speed up E2E test workflows and eliminate npm cache-restore failures (#36599) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Workers no longer run `npm ci` — `node_modules` and framework binaries are restored from actions/cache populated once by a new `prep-deps` job. This closes the intermittent EEXIST/ENOENT failure inside npm's own cacache writer that occasionally fails `npm ci` on a runner. Removing `npm ci` from workers also cuts ~5 min of duplicated install work per worker. dispatch-begin now runs as its own job after prep-deps so it fires once the per-worker test-server setup is the only remaining work before dispatch-run. --- .github/e2e-tests-workflows.md | 112 +++++++++++-- .github/workflows/e2e-tests-check.yml | 6 + .../e2e-tests-cypress-template-v2.yml | 84 +++++++--- .../e2e-tests-playwright-template-v2.yml | 151 +++++++++++++++--- .../e2e-tests-playwright-template.yml | 9 +- 5 files changed, 314 insertions(+), 48 deletions(-) diff --git a/.github/e2e-tests-workflows.md b/.github/e2e-tests-workflows.md index b6c6a6577fe..95660924dfd 100644 --- a/.github/e2e-tests-workflows.md +++ b/.github/e2e-tests-workflows.md @@ -16,25 +16,119 @@ All pipelines follow the **smoke-then-full** pattern: smoke tests run first, ful ``` .github/workflows/ -├── e2e-tests-ci.yml # PR orchestrator -├── e2e-tests-on-merge.yml # Merge orchestrator (master/release branches) -├── e2e-tests-on-release.yml # Release cut orchestrator -├── e2e-tests-cypress.yml # Shared wrapper: cypress smoke -> full -├── e2e-tests-playwright.yml # Shared wrapper: playwright smoke -> full -├── e2e-tests-cypress-template.yml # Template: actual cypress test execution -└── e2e-tests-playwright-template.yml # Template: actual playwright test execution +├── e2e-tests-ci.yml # PR orchestrator +├── e2e-tests-on-merge.yml # Merge orchestrator (master/release branches) +├── e2e-tests-on-release.yml # Release cut orchestrator +├── e2e-tests-cypress.yml # Shared wrapper: routes to v1 or v2 template +├── e2e-tests-playwright.yml # Shared wrapper: routes to v1 or v2 template +├── e2e-tests-cypress-template-v2.yml # Active: cypress + test-system-io dispatch +├── e2e-tests-playwright-template-v2.yml # Active: playwright + test-system-io dispatch +├── e2e-tests-cypress-template.yml # Deprecated v1 (legacy in-job execution) +└── e2e-tests-playwright-template.yml # Deprecated v1 (legacy in-job execution) ``` +> **v1 templates are deprecated.** They remain available behind a feature flag during cutover but receive no further changes. New work targets the v2 templates exclusively. The wrappers route by `vars.E2E_USE_TEST_IO_DISPATCH` — `'true'` selects v2, anything else falls back to v1. + ### Call hierarchy ``` e2e-tests-ci.yml ─────────────────┐ -e2e-tests-on-merge.yml ───────────┤──► e2e-tests-cypress.yml ──► e2e-tests-cypress-template.yml -e2e-tests-on-release.yml ─────────┘ e2e-tests-playwright.yml ──► e2e-tests-playwright-template.yml +e2e-tests-on-merge.yml ───────────┤──► e2e-tests-cypress.yml ─────┐ +e2e-tests-on-release.yml ─────────┘ e2e-tests-playwright.yml ──┤ + │ + ┌──────────────────────────┘ + │ routes on E2E_USE_TEST_IO_DISPATCH + ▼ + v2 (active) ──► e2e-tests-{cypress,playwright}-template-v2.yml + v1 (legacy) ──► e2e-tests-{cypress,playwright}-template.yml ``` --- +## Workflow Architecture (v2) + +v2 splits the template into five jobs — `prepare-run`, `prep-deps`, `dispatch-begin`, `workers` (matrix), and `report` — and pushes spec-level execution to [Test System IO](https://github.com/mattermost/mattermost-test-system-io) so workers stay thin and identical. + +``` +┌──────────────────────────────────────────────────────────────────────────┐ +│ Template v2: e2e-tests-{cypress,playwright}-template-v2.yml │ +│ │ +│ ┌───────────────────┐ ┌──────────────────────────────┐ │ +│ │ prepare-run │ │ prep-deps │ │ +│ │ (1 runner) │ parallel │ (1 runner) │ │ +│ │ │ ◄────────────► │ │ │ +│ │ • build workers │ │ Cypress: │ │ +│ │ matrix [1..N] │ │ • cypress/node_modules │ │ +│ │ • compute commit │ │ • ~/.cache/Cypress (binary)│ │ +│ │ status context │ │ │ │ +│ │ • emit composite │ │ Playwright: │ │ +│ │ identity │ │ • webapp/platform/{client, │ │ +│ │ │ │ types}/{lib,node_mod} │ │ +│ │ │ │ • playwright/node_modules │ │ +│ │ │ │ • playwright/lib/dist │ │ +│ │ │ │ • ~/.cache/ms-playwright │ │ +│ │ │ │ (chromium only) │ │ +│ │ │ │ │ │ +│ │ │ │ → saved to actions/cache │ │ +│ └─────────┬─────────┘ └───────────────┬──────────────┘ │ +│ │ │ │ +│ │ ▼ │ +│ │ ┌──────────────────────────────┐ │ +│ │ │ dispatch-begin │ │ +│ │ │ • register run with │ │ +│ │ │ Test System IO │ │ +│ │ │ • runs immediately before │ │ +│ │ │ workers to minimise the │ │ +│ │ │ inactivity-timeout window │ │ +│ │ └───────────────┬──────────────┘ │ +│ │ │ │ +│ └────────────────────┬─────────────────────┘ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────────┐ │ +│ │ workers (matrix, fail-fast: false) │ │ +│ │ Cypress full: N=40 | Playwright full: N=10 │ │ +│ │ │ │ +│ │ each worker, in parallel: │ │ +│ │ 1. sparse-checkout actions + full checkout-repo │ │ +│ │ 2. setup-node │ │ +│ │ 3. restore caches ◄─── actions/cache (from prep-deps) │ │ +│ │ (fail-on-cache-miss: true) │ │ +│ │ 4. cloud-init + start-server (docker compose stack) │ │ +│ │ 5. prepare-cypress | prepare-playwright (run setup project) │ │ +│ │ 6. dispatch-run ──────────────────────────────────┐ │ │ +│ │ (pulls specs from Test System IO, runs locally, │ │ │ +│ │ posts result, loops until queue is empty) │ │ │ +│ │ 7. cloud-teardown │ │ │ +│ └────────────────────┬────────────────────────────────────┼───────┘ │ +│ │ │ │ +│ ▼ │ │ +│ ┌─────────────────────────────────────────────────┐ │ │ +│ │ report │ │ │ +│ │ • pull aggregated results from Test System IO │ ◄────┘ │ +│ │ • post commit status │ │ +│ │ • send webhook notification │ │ +│ └─────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────────────────┬─┘ + │ + ┌───────────────────────────▼───┐ + │ Test System IO (external) │ + │ • spec-level dispatch │ + │ • result aggregation │ + │ • retry orchestration │ + └───────────────────────────────┘ +``` + +### Key properties + +- **Spec-level vs. job-level parallelism.** The matrix sizes the runner pool; Test System IO does the spec assignment. Slow specs don't block a worker — fast workers keep pulling the next spec from the queue. +- **Cache-only workers.** `prep-deps` installs once per workflow run and saves to `actions/cache`. Every worker restores with `fail-on-cache-miss: true` and runs zero `npm ci`. Eliminates the 40-way `EEXIST/ENOENT` race in npm's shared cacache writer. +- **dispatch-begin runs late.** It depends on `prep-deps` so the gap between Test System IO run registration and the first worker calling `dispatch-run` is just per-worker setup (~3–5 min). Registering earlier risks the run timing out before any worker checks in, bulk-failing every spec. +- **Playwright slim slice.** Playwright only consumes `@mattermost/client` and `@mattermost/types` from webapp, so prep-deps caches just those two packages' built `lib/` and `node_modules` (~10–30 MB) instead of the full `webapp/node_modules` tree (~1–2 GB). +- **Browser/binary caches.** Cypress caches `~/.cache/Cypress` (cypress binary lives outside node_modules); playwright caches `~/.cache/ms-playwright` (chromium only). Both keyed on the framework's lockfile so they invalidate on version bumps. +- **No retry plumbing in the template.** Test System IO handles per-spec retries; the workflow only sees aggregated results. + +--- + ## Pipeline 1: PR (`e2e-tests-ci.yml`) Runs E2E tests for every PR commit after the enterprise docker image is built. Fails if the commit is not associated with an open PR. diff --git a/.github/workflows/e2e-tests-check.yml b/.github/workflows/e2e-tests-check.yml index 53b59b726f7..13cd9b45fca 100644 --- a/.github/workflows/e2e-tests-check.yml +++ b/.github/workflows/e2e-tests-check.yml @@ -25,6 +25,12 @@ jobs: cache-dependency-path: | e2e-tests/cypress/package-lock.json e2e-tests/playwright/package-lock.json + webapp/package-lock.json + - name: ci/npm-cache-verify + # Heal any partial/dangling entries left in the restored ~/.npm cache + # before running `npm ci`. Avoids the intermittent EEXIST/ENOENT + # failures in npm's cacache writer. + run: npm cache verify # Cypress check - name: ci/cypress/npm-install diff --git a/.github/workflows/e2e-tests-cypress-template-v2.yml b/.github/workflows/e2e-tests-cypress-template-v2.yml index 16351715089..f08b7ec9f32 100644 --- a/.github/workflows/e2e-tests-cypress-template-v2.yml +++ b/.github/workflows/e2e-tests-cypress-template-v2.yml @@ -135,7 +135,7 @@ 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: - dispatch-begin: + prepare-run: runs-on: ubuntu-24.04 permissions: contents: read @@ -193,13 +193,62 @@ jobs: run: | echo "workers=$(jq -nc --argjson n ${{ inputs.workers }} '[range(1; $n+1)]')" >> $GITHUB_OUTPUT echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + + # Install cypress node_modules once, then workers restore from cache. + prep-deps: + name: prep-deps + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 1 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + - name: ci/cache-cypress-deps + # node_modules + the cypress binary (downloaded to ~/.cache/Cypress by + # cypress's postinstall, not into node_modules). Both must be cached; + # otherwise workers see "cypress npm package installed but binary missing". + id: cache-cypress + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: | + e2e-tests/cypress/node_modules + ~/.cache/Cypress + key: e2e-cypress-deps-${{ runner.os }}-${{ hashFiles('e2e-tests/cypress/package-lock.json') }} + - name: ci/install-cypress-deps + if: steps.cache-cypress.outputs.cache-hit != 'true' + working-directory: e2e-tests/cypress + run: npm ci + + # 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: + ref: ${{ inputs.commit_sha }} + fetch-depth: 1 - name: ci/dispatch-begin uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-dispatch-begin@a2ea7f005484c28fedf51e16645f6d3bd683fd63 # 2026-05-16 with: use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }} framework: cypress repo-dir: ${{ github.workspace }} - composite-identity: ${{ steps.composite-identity.outputs.composite-identity-json }} + 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 }} @@ -217,16 +266,16 @@ jobs: name: dispatch-run-${{ matrix.worker_index }} runs-on: ubuntu-24.04 timeout-minutes: 30 - needs: dispatch-begin + needs: [prepare-run, dispatch-begin] permissions: contents: read id-token: write strategy: fail-fast: false matrix: - worker_index: ${{ fromJSON(needs.dispatch-begin.outputs.workers-matrix) }} + worker_index: ${{ fromJSON(needs.prepare-run.outputs.workers-matrix) }} env: - COMPOSITE_IDENTITY: ${{ needs.dispatch-begin.outputs.composite-identity-json }} + COMPOSITE_IDENTITY: ${{ needs.prepare-run.outputs.composite-identity-json }} SERVER: "${{ inputs.server }}" MM_LICENSE: "${{ secrets.MM_LICENSE }}" ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}" @@ -260,29 +309,26 @@ jobs: 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/get-webapp-node-modules - working-directory: webapp - run: make node_modules + - name: ci/restore-cypress-deps + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: | + e2e-tests/cypress/node_modules + ~/.cache/Cypress + key: e2e-cypress-deps-${{ runner.os }}-${{ hashFiles('e2e-tests/cypress/package-lock.json') }} + fail-on-cache-miss: true - name: ci/cloud-init working-directory: e2e-tests run: make cloud-init - name: ci/start-server working-directory: e2e-tests run: make start-server - # `npm ci` in the host context replaces the container-built native - # binaries with host-built ones, since the dispatch adapter spawns - # `npx cypress run` on the host. - - name: ci/prepare-cypress - working-directory: e2e-tests/cypress - run: npm ci - name: ci/dispatch-run uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-dispatch-run@a2ea7f005484c28fedf51e16645f6d3bd683fd63 # 2026-05-16 with: use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }} framework: cypress - composite-identity: ${{ needs.dispatch-begin.outputs.composite-identity-json }} + composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }} repo-dir: ${{ github.workspace }} artifacts-root: ${{ github.workspace }}/worker-artifacts github-token: ${{ secrets.GITHUB_TOKEN }} @@ -306,7 +352,7 @@ jobs: report: runs-on: ubuntu-24.04 - needs: [dispatch-begin, workers] + needs: [prepare-run, dispatch-begin, workers] if: always() permissions: contents: read @@ -324,7 +370,7 @@ jobs: uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-summary@a2ea7f005484c28fedf51e16645f6d3bd683fd63 # 2026-05-16 with: use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }} - composite-identity: ${{ needs.dispatch-begin.outputs.composite-identity-json }} + composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }} framework: cypress report-type: ${{ inputs.report_type }} image-tag: ${{ inputs.server_image_tag }} diff --git a/.github/workflows/e2e-tests-playwright-template-v2.yml b/.github/workflows/e2e-tests-playwright-template-v2.yml index 1573e1d4616..d2d1966209b 100644 --- a/.github/workflows/e2e-tests-playwright-template-v2.yml +++ b/.github/workflows/e2e-tests-playwright-template-v2.yml @@ -101,7 +101,7 @@ 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: - dispatch-begin: + prepare-run: runs-on: ubuntu-24.04 permissions: contents: read @@ -158,13 +158,104 @@ jobs: run: | echo "workers=$(jq -nc --argjson n ${{ inputs.workers }} '[range(1; $n+1)]')" >> $GITHUB_OUTPUT echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT + + # Build @mattermost/client + @mattermost/types and install playwright deps once, + # then workers restore from cache. Playwright only consumes those two packages + # from webapp, so we cache just their built lib/ instead of all of webapp/node_modules. + prep-deps: + name: prep-deps + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + steps: + - name: ci/checkout-repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.commit_sha }} + fetch-depth: 1 + - name: ci/setup-node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: ".nvmrc" + - name: ci/cache-platform-pkgs + # `webapp/node_modules/@mattermost/{client,types}` are the workspace + # symlinks Node walks up to find when platform/client requires + # @mattermost/types. Without them, module resolution fails inside + # the slim slice. + id: cache-platform-pkgs + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: | + webapp/node_modules/@mattermost/client + webapp/node_modules/@mattermost/types + webapp/platform/client/lib + webapp/platform/client/node_modules + webapp/platform/types/lib + webapp/platform/types/node_modules + key: e2e-platform-pkgs-${{ runner.os }}-${{ hashFiles('webapp/package-lock.json', 'webapp/platform/client/src/**', 'webapp/platform/client/tsconfig*.json', 'webapp/platform/types/src/**', 'webapp/platform/types/tsconfig*.json') }} + - name: ci/build-platform-pkgs + # Full webapp install is needed for tsc + workspace linking; the + # postinstall builds platform/{client,types}/lib. We only cache those. + if: steps.cache-platform-pkgs.outputs.cache-hit != 'true' + working-directory: webapp + run: make node_modules + - name: ci/cache-playwright-deps + # Caches node_modules + the rolled-up @mattermost/playwright-lib dist + # so workers don't re-run rollup on every job. + id: cache-playwright + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: | + e2e-tests/playwright/node_modules + e2e-tests/playwright/lib/dist + e2e-tests/playwright/lib/node_modules + key: e2e-playwright-deps-${{ runner.os }}-${{ hashFiles('e2e-tests/playwright/package-lock.json', 'e2e-tests/playwright/lib/src/**', 'e2e-tests/playwright/lib/package.json', 'e2e-tests/playwright/lib/rollup.config.js', 'e2e-tests/playwright/lib/tsconfig.json') }} + - name: ci/install-playwright-deps + # `npm ci` creates symlinks at node_modules/@mattermost/{client,types} + # → webapp/platform/{client,types}; targets must already be built. + # The postinstall then builds lib/dist via rollup. Skip browser + # download here — chromium is cached separately below. + if: steps.cache-playwright.outputs.cache-hit != 'true' + working-directory: e2e-tests/playwright + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" + run: npm ci + - name: ci/cache-playwright-browsers + # Cache chromium binary (~150MB) keyed on the playwright lockfile so a + # version bump invalidates. Restored by workers; no docker image needed. + id: cache-pw-browsers + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.cache/ms-playwright + key: playwright-browsers-${{ runner.os }}-${{ hashFiles('e2e-tests/playwright/package-lock.json') }} + - name: ci/install-playwright-chromium + if: steps.cache-pw-browsers.outputs.cache-hit != 'true' + working-directory: e2e-tests/playwright + run: npx playwright install chromium + + # 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: + ref: ${{ inputs.commit_sha }} + fetch-depth: 1 - name: ci/dispatch-begin uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-dispatch-begin@a2ea7f005484c28fedf51e16645f6d3bd683fd63 # 2026-05-16 with: use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }} framework: playwright repo-dir: ${{ github.workspace }} - composite-identity: ${{ steps.composite-identity.outputs.composite-identity-json }} + composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }} total-reports-expected: ${{ inputs.workers }} retest-on-fail: ${{ inputs.retest_on_fail }} playwright-project: ${{ inputs.playwright_project }} @@ -177,16 +268,16 @@ jobs: name: dispatch-run-${{ matrix.worker_index }} runs-on: ubuntu-24.04 timeout-minutes: 30 - needs: dispatch-begin + needs: [prepare-run, dispatch-begin] permissions: contents: read id-token: write strategy: fail-fast: false matrix: - worker_index: ${{ fromJSON(needs.dispatch-begin.outputs.workers-matrix) }} + worker_index: ${{ fromJSON(needs.prepare-run.outputs.workers-matrix) }} env: - COMPOSITE_IDENTITY: ${{ needs.dispatch-begin.outputs.composite-identity-json }} + COMPOSITE_IDENTITY: ${{ needs.prepare-run.outputs.composite-identity-json }} SERVER: "${{ inputs.server }}" MM_LICENSE: "${{ secrets.MM_LICENSE }}" ENABLED_DOCKER_SERVICES: "${{ inputs.enabled_docker_services }}" @@ -214,31 +305,53 @@ jobs: 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/restore-platform-pkgs + # Built lib/ for @mattermost/client and @mattermost/types, plus the + # webapp workspace symlinks under webapp/node_modules/@mattermost/. + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: | + webapp/node_modules/@mattermost/client + webapp/node_modules/@mattermost/types + webapp/platform/client/lib + webapp/platform/client/node_modules + webapp/platform/types/lib + webapp/platform/types/node_modules + key: e2e-platform-pkgs-${{ runner.os }}-${{ hashFiles('webapp/package-lock.json', 'webapp/platform/client/src/**', 'webapp/platform/client/tsconfig*.json', 'webapp/platform/types/src/**', 'webapp/platform/types/tsconfig*.json') }} + fail-on-cache-miss: true + - name: ci/restore-playwright-deps + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: | + e2e-tests/playwright/node_modules + e2e-tests/playwright/lib/dist + e2e-tests/playwright/lib/node_modules + key: e2e-playwright-deps-${{ runner.os }}-${{ hashFiles('e2e-tests/playwright/package-lock.json', 'e2e-tests/playwright/lib/src/**', 'e2e-tests/playwright/lib/package.json', 'e2e-tests/playwright/lib/rollup.config.js', 'e2e-tests/playwright/lib/tsconfig.json') }} + fail-on-cache-miss: true + - name: ci/restore-playwright-browsers + uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 + with: + path: ~/.cache/ms-playwright + key: playwright-browsers-${{ runner.os }}-${{ hashFiles('e2e-tests/playwright/package-lock.json') }} + fail-on-cache-miss: true - name: ci/cloud-init working-directory: e2e-tests run: make cloud-init - name: ci/start-server working-directory: e2e-tests run: make start-server - # Build once + run the `setup` project so per-spec dispatches can - # pass --no-deps and skip plugin-load + server-deployment checks. + # Run the `setup` project so per-spec dispatches can pass --no-deps + # and skip plugin-load + server-deployment checks. node_modules, + # lib/dist, and chromium are all restored from cache. - name: ci/prepare-playwright working-directory: e2e-tests/playwright - run: | - npm ci - npm run build - npx playwright test --project=setup + run: npx playwright test --project=setup - name: ci/dispatch-run uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-dispatch-run@a2ea7f005484c28fedf51e16645f6d3bd683fd63 # 2026-05-16 with: use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }} framework: playwright - composite-identity: ${{ needs.dispatch-begin.outputs.composite-identity-json }} + composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }} repo-dir: ${{ github.workspace }} artifacts-root: ${{ github.workspace }}/worker-artifacts github-token: ${{ secrets.GITHUB_TOKEN }} @@ -263,7 +376,7 @@ jobs: report: runs-on: ubuntu-24.04 - needs: [dispatch-begin, workers] + needs: [prepare-run, dispatch-begin, workers] if: always() permissions: contents: read @@ -281,7 +394,7 @@ jobs: uses: mattermost/mattermost-test-system-io/.github/actions/test-system-io-summary@a2ea7f005484c28fedf51e16645f6d3bd683fd63 # 2026-05-16 with: use-staging: ${{ vars.E2E_USE_STAGING_TEST_IO_URL != 'false' }} - composite-identity: ${{ needs.dispatch-begin.outputs.composite-identity-json }} + composite-identity: ${{ needs.prepare-run.outputs.composite-identity-json }} framework: playwright report-type: ${{ inputs.report_type }} image-tag: ${{ inputs.server_image_tag }} diff --git a/.github/workflows/e2e-tests-playwright-template.yml b/.github/workflows/e2e-tests-playwright-template.yml index af976a3110b..80d92d71794 100644 --- a/.github/workflows/e2e-tests-playwright-template.yml +++ b/.github/workflows/e2e-tests-playwright-template.yml @@ -179,7 +179,14 @@ jobs: with: node-version-file: ".nvmrc" cache: npm - cache-dependency-path: "e2e-tests/playwright/package-lock.json" + cache-dependency-path: | + e2e-tests/playwright/package-lock.json + webapp/package-lock.json + - name: ci/npm-cache-verify + # Heal any partial/dangling entries left in the restored ~/.npm cache + # before running `npm ci`. Avoids the intermittent EEXIST/ENOENT + # failures in npm's cacache writer. + run: npm cache verify - name: ci/get-webapp-node-modules working-directory: webapp run: make node_modules