From 49e6fd2c69de19066c61bfa502297fbdb0e04b71 Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:09:58 +0300 Subject: [PATCH 1/5] CI: cache the system packages Playwright installs for WebKit `npx playwright install --with-deps` downloads 114MB of Debian packages from the Ubuntu mirror. The mirror throttles that fetch often enough to stall an end-to-end job: recent runs on trunk took 14, 15, 17 and 56 minutes on the step, against a median of 48 seconds. Every one of those 200 packages belongs to WebKit. The runner image already satisfies Chromium and Firefox, so those jobs download nothing. Cache the packages through a composite action, and engage the cache only when the browser list includes WebKit. Install Chromium alone in the two jobs that never launch another browser: the RTC WebSocket suite matches no spec tagged `@webkit` or `@firefox`, and the Storybook test runner only launches Chromium. --- .github/setup-playwright/action.yml | 55 +++++++++++++++++++++++++++ .github/workflows/end2end-test.yml | 11 ++++-- .github/workflows/storybook-check.yml | 9 ++++- 3 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 .github/setup-playwright/action.yml diff --git a/.github/setup-playwright/action.yml b/.github/setup-playwright/action.yml new file mode 100644 index 00000000000000..e7821eb48176cc --- /dev/null +++ b/.github/setup-playwright/action.yml @@ -0,0 +1,55 @@ +name: 'Install Playwright browsers' +description: 'Install Playwright browsers and their system dependencies, caching the Debian packages that WebKit needs.' + +inputs: + browsers: + description: 'Optional. Space separated list of browsers to install. When not specified, all browsers are installed.' + required: false + +runs: + using: 'composite' + steps: + # Only WebKit pulls system dependencies on the runner images, and the + # Ubuntu mirror throttles them often enough to stall a job for tens of + # minutes. + - name: Prepare the package cache + id: prepare + env: + BROWSERS: ${{ inputs.browsers }} + run: | + VERSION="$(node -p "require('playwright-core/package.json').version")" + echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" + echo "BROWSERS_KEY=$(echo "${BROWSERS:-all}" | tr ' ' '-')" >> "$GITHUB_OUTPUT" + echo "IMAGE=${ImageOS:-unknown}-${ImageVersion:-unknown}" >> "$GITHUB_OUTPUT" + + if [ -z "$BROWSERS" ] || [ "${BROWSERS#*webkit}" != "$BROWSERS" ]; then + echo "CACHE_DEPS=true" >> "$GITHUB_OUTPUT" + else + echo "CACHE_DEPS=false" >> "$GITHUB_OUTPUT" + fi + + echo 'APT::Keep-Downloaded-Packages "true";' | sudo tee /etc/apt/apt.conf.d/99keep-downloaded-packages > /dev/null + mkdir -p "$HOME/apt-cache" + shell: bash + + - name: Cache Playwright system dependencies + if: ${{ steps.prepare.outputs.CACHE_DEPS == 'true' }} + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/apt-cache + # The cached packages must match the versions in the runner image's apt lists. + key: playwright-apt-${{ runner.os }}-${{ runner.arch }}-${{ steps.prepare.outputs.IMAGE }}-${{ steps.prepare.outputs.VERSION }}-${{ steps.prepare.outputs.BROWSERS_KEY }} + + - name: Install Playwright browsers and system dependencies + env: + BROWSERS: ${{ inputs.browsers }} + run: | + # `--update=none` needs coreutils 9.3+, so ubuntu-24.04 or newer. + sudo cp -r --update=none "$HOME/apt-cache/." /var/cache/apt/archives/ + + # shellcheck disable=SC2086 # The browser list is intentionally split into separate arguments. + npx playwright install $BROWSERS --with-deps + + sudo find /var/cache/apt/archives -maxdepth 1 -name '*.deb' -exec cp --update=none -t "$HOME/apt-cache" {} + + sudo chown -R "$(id -u):$(id -g)" "$HOME/apt-cache" + shell: bash diff --git a/.github/workflows/end2end-test.yml b/.github/workflows/end2end-test.yml index b17bfd2d952708..9628a0a26a5f55 100644 --- a/.github/workflows/end2end-test.yml +++ b/.github/workflows/end2end-test.yml @@ -46,8 +46,9 @@ jobs: run: npm run build -- --skip-types - name: Install Playwright dependencies - run: | - npx playwright install chromium firefox webkit --with-deps + uses: ./.github/setup-playwright + with: + browsers: chromium firefox webkit - name: Install WordPress and start the server run: | @@ -95,9 +96,11 @@ jobs: - name: Npm build run: npm run build -- --skip-types + # No collaboration spec is tagged `@webkit` or `@firefox`. - name: Install Playwright dependencies - run: | - npx playwright install chromium firefox webkit --with-deps + uses: ./.github/setup-playwright + with: + browsers: chromium - name: Install WordPress and start the server run: | diff --git a/.github/workflows/storybook-check.yml b/.github/workflows/storybook-check.yml index 3f5b9604f3c1e6..1ea48b584be23f 100644 --- a/.github/workflows/storybook-check.yml +++ b/.github/workflows/storybook-check.yml @@ -49,10 +49,15 @@ jobs: NODE_ENV: test run: npm run storybook:e2e:build - - name: Install test-runner and Playwright dependencies + - name: Install test-runner run: | npm install --no-save @storybook/test-runner@0.24.2 - npx playwright install --with-deps + + # The Storybook test runner only launches Chromium. + - name: Install Playwright dependencies + uses: ./.github/setup-playwright + with: + browsers: chromium - name: Serve Storybook and run tests run: | From 3f0ca172a80cb42265e289b3edb3b304719246cd Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:30:29 +0300 Subject: [PATCH 2/5] CI: refresh the package cache when apt fetches a newer package Cache entries are immutable, so `actions/cache` does not save again on an exact key hit. With a fixed key, any package that Ubuntu updates would be re-downloaded on every run until the runner image rotated. Put a fingerprint of the cached packages in the key and split the restore and save steps. The save step runs only when the package set changed, so an unchanged run uploads nothing and an updated one writes a new entry. --- .github/setup-playwright/action.yml | 40 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/setup-playwright/action.yml b/.github/setup-playwright/action.yml index e7821eb48176cc..30a8cba612074c 100644 --- a/.github/setup-playwright/action.yml +++ b/.github/setup-playwright/action.yml @@ -18,9 +18,10 @@ runs: BROWSERS: ${{ inputs.browsers }} run: | VERSION="$(node -p "require('playwright-core/package.json').version")" - echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" - echo "BROWSERS_KEY=$(echo "${BROWSERS:-all}" | tr ' ' '-')" >> "$GITHUB_OUTPUT" - echo "IMAGE=${ImageOS:-unknown}-${ImageVersion:-unknown}" >> "$GITHUB_OUTPUT" + BROWSERS_KEY="$(echo "${BROWSERS:-all}" | tr ' ' '-')" + # The cached packages must match the versions in the runner image's apt lists. + IMAGE="${ImageOS:-unknown}-${ImageVersion:-unknown}" + echo "PREFIX=playwright-apt-${RUNNER_OS}-${RUNNER_ARCH}-${IMAGE}-${VERSION}-${BROWSERS_KEY}-" >> "$GITHUB_OUTPUT" if [ -z "$BROWSERS" ] || [ "${BROWSERS#*webkit}" != "$BROWSERS" ]; then echo "CACHE_DEPS=true" >> "$GITHUB_OUTPUT" @@ -32,18 +33,28 @@ runs: mkdir -p "$HOME/apt-cache" shell: bash - - name: Cache Playwright system dependencies + # The key carries a fingerprint of the packages, so a cache entry is + # never stale: when apt fetches a newer package the fingerprint changes + # and the save step writes a new entry. Cache entries are immutable, so + # refreshing one is not an option. + - name: Restore the cached packages if: ${{ steps.prepare.outputs.CACHE_DEPS == 'true' }} - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/apt-cache - # The cached packages must match the versions in the runner image's apt lists. - key: playwright-apt-${{ runner.os }}-${{ runner.arch }}-${{ steps.prepare.outputs.IMAGE }}-${{ steps.prepare.outputs.VERSION }}-${{ steps.prepare.outputs.BROWSERS_KEY }} + key: ${{ steps.prepare.outputs.PREFIX }} + restore-keys: ${{ steps.prepare.outputs.PREFIX }} - name: Install Playwright browsers and system dependencies + id: install env: BROWSERS: ${{ inputs.browsers }} run: | + fingerprint() { + find "$HOME/apt-cache" -maxdepth 1 -name '*.deb' -printf '%f\n' | sort | sha256sum | cut -c1-16 + } + BEFORE="$(fingerprint)" + # `--update=none` needs coreutils 9.3+, so ubuntu-24.04 or newer. sudo cp -r --update=none "$HOME/apt-cache/." /var/cache/apt/archives/ @@ -52,4 +63,19 @@ runs: sudo find /var/cache/apt/archives -maxdepth 1 -name '*.deb' -exec cp --update=none -t "$HOME/apt-cache" {} + sudo chown -R "$(id -u):$(id -g)" "$HOME/apt-cache" + + AFTER="$(fingerprint)" + echo "FINGERPRINT=$AFTER" >> "$GITHUB_OUTPUT" + if [ "$BEFORE" != "$AFTER" ]; then + echo "CHANGED=true" >> "$GITHUB_OUTPUT" + else + echo "CHANGED=false" >> "$GITHUB_OUTPUT" + fi shell: bash + + - name: Save the packages + if: ${{ steps.prepare.outputs.CACHE_DEPS == 'true' && steps.install.outputs.CHANGED == 'true' }} + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/apt-cache + key: ${{ steps.prepare.outputs.PREFIX }}${{ steps.install.outputs.FINGERPRINT }} From 7bb19b5380cbe717d8aae31357aaa8b88f086600 Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Wed, 29 Jul 2026 19:38:51 +0300 Subject: [PATCH 3/5] CI: correct a comment about the package cache key --- .github/setup-playwright/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/setup-playwright/action.yml b/.github/setup-playwright/action.yml index 30a8cba612074c..b875419b47fd50 100644 --- a/.github/setup-playwright/action.yml +++ b/.github/setup-playwright/action.yml @@ -19,7 +19,7 @@ runs: run: | VERSION="$(node -p "require('playwright-core/package.json').version")" BROWSERS_KEY="$(echo "${BROWSERS:-all}" | tr ' ' '-')" - # The cached packages must match the versions in the runner image's apt lists. + # Packages cached under another runner image mostly miss. IMAGE="${ImageOS:-unknown}-${ImageVersion:-unknown}" echo "PREFIX=playwright-apt-${RUNNER_OS}-${RUNNER_ARCH}-${IMAGE}-${VERSION}-${BROWSERS_KEY}-" >> "$GITHUB_OUTPUT" From 788e0952ec047afda4b2c2bf6dfc537be09d73dc Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:58:49 +0300 Subject: [PATCH 4/5] CI: lint every composite action, not just setup-node --- .github/workflows/workflow-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index b301d3618d9db8..34dbd131c83ed3 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -7,7 +7,7 @@ on: paths: # Only run when changes are made to workflow files. - '.github/workflows/**' - - '.github/setup-node/action.yml' + - '.github/*/action.yml' - 'packages/**/action.yml' pull_request: branches: @@ -15,7 +15,7 @@ on: paths: # Only run when changes are made to workflow files. - '.github/workflows/**' - - '.github/setup-node/action.yml' + - '.github/*/action.yml' - 'packages/**/action.yml' workflow_dispatch: From 389a30e5ebd3339e3393a13676fc8933dd160019 Mon Sep 17 00:00:00 2001 From: Adrian Moldovan <3854374+adimoldovan@users.noreply.github.com> Date: Fri, 31 Jul 2026 18:37:56 +0300 Subject: [PATCH 5/5] CI: stabilise the package fingerprint and narrow test:smoke:setup Sort the fingerprint under LC_ALL=C so collation cannot perturb the hash, and install only Chromium in the Storybook workspace's setup script, matching what CI installs and what the test runner launches. Document why the exact cache key never matches, and what apt does and does not verify about a cached package. --- .github/setup-playwright/action.yml | 8 ++++++-- storybook/package.json | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/setup-playwright/action.yml b/.github/setup-playwright/action.yml index b875419b47fd50..c906f80f2b64d1 100644 --- a/.github/setup-playwright/action.yml +++ b/.github/setup-playwright/action.yml @@ -36,7 +36,8 @@ runs: # The key carries a fingerprint of the packages, so a cache entry is # never stale: when apt fetches a newer package the fingerprint changes # and the save step writes a new entry. Cache entries are immutable, so - # refreshing one is not an option. + # refreshing one is not an option. `key` therefore never matches on its + # own; the prefix search restores the newest entry. - name: Restore the cached packages if: ${{ steps.prepare.outputs.CACHE_DEPS == 'true' }} uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -51,10 +52,13 @@ runs: BROWSERS: ${{ inputs.browsers }} run: | fingerprint() { - find "$HOME/apt-cache" -maxdepth 1 -name '*.deb' -printf '%f\n' | sort | sha256sum | cut -c1-16 + find "$HOME/apt-cache" -maxdepth 1 -name '*.deb' -printf '%f\n' | LC_ALL=C sort | sha256sum | cut -c1-16 } BEFORE="$(fingerprint)" + # apt looks packages up by name, version and arch, re-downloading + # any whose size does not match its own index, so a stale package + # here is never used. It does not re-checksum what it finds cached. # `--update=none` needs coreutils 9.3+, so ubuntu-24.04 or newer. sudo cp -r --update=none "$HOME/apt-cache/." /var/cache/apt/archives/ diff --git a/storybook/package.json b/storybook/package.json index 881a3902b8a620..133e4c5c571692 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -80,6 +80,6 @@ "storybook:build": "NODE_OPTIONS=--max-old-space-size=8192 storybook build -c . -o ./build && node ./scripts/inject-legacy-docgen.mjs ./build/manifests/components.json", "storybook:dev": "storybook dev -c . -p 50240", "test:smoke": "concurrently -k -s first -n \"SB,TEST\" -c \"magenta,blue\" \"http-server ./build --port 50240 --silent\" \"wait-on tcp:127.0.0.1:50240 && test-storybook --url http://localhost:50240 --config-dir .\"", - "test:smoke:setup": "playwright install --with-deps" + "test:smoke:setup": "playwright install chromium --with-deps" } }