diff --git a/.github/scripts/verify-playwright-ffmpeg.sh b/.github/scripts/verify-playwright-ffmpeg.sh new file mode 100755 index 0000000000..6048bdd2ec --- /dev/null +++ b/.github/scripts/verify-playwright-ffmpeg.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Verifies that Playwright's ffmpeg download produced a usable binary. +# +# `playwright install` is not trustworthy on its own here: under the Node 24.16.0 +# yauzl/extract-zip regression (Playwright < 1.60.0) it would hang mid-extraction +# and leave a truncated `ffmpeg-linux` behind with no INSTALLATION_COMPLETE marker, +# so the exit code said nothing about whether ffmpeg actually worked. +# +# CI caches the download, so this runs before the cache is saved: checking both the +# marker and that the binary actually executes is what keeps a partial extraction +# from being promoted into a cache that every later job would restore. The install +# directory is read back from Playwright so this stays correct across version bumps +# and never lets a stale revision vouch for the one actually required. + +set -uo pipefail + +install_dir=$(npx playwright install --dry-run ffmpeg 2>/dev/null | + sed -n 's/^[[:space:]]*Install location:[[:space:]]*//p' | head -1) + +if [ -z "${install_dir}" ]; then + echo "::warning::Could not determine Playwright's ffmpeg install location; skipping cache save." + exit 1 +fi + +if [ ! -f "${install_dir}/INSTALLATION_COMPLETE" ]; then + echo "::warning::${install_dir} has no INSTALLATION_COMPLETE marker; the download did not finish." + exit 1 +fi + +binary="${install_dir}/ffmpeg-linux" + +if [ ! -x "${binary}" ]; then + echo "::warning::${binary} is missing or not executable." + exit 1 +fi + +if ! "${binary}" -version >/dev/null 2>&1; then + echo "::warning::${binary} is present but does not execute; treating it as a partial extraction." + exit 1 +fi + +echo "Verified Playwright ffmpeg at ${binary}" diff --git a/.github/workflows/codegraph-e2e-votes.yml b/.github/workflows/codegraph-e2e-votes.yml index 093b4e1d83..51c941ef88 100644 --- a/.github/workflows/codegraph-e2e-votes.yml +++ b/.github/workflows/codegraph-e2e-votes.yml @@ -172,11 +172,37 @@ jobs: run: google-chrome --version # ffmpeg for retry video — see the note in playwright-mock.yml. - - name: Install Playwright ffmpeg (best effort) + - name: Resolve Playwright version + id: playwright-version if: steps.tiers.outputs.count != '0' + run: | + version=$(node -p "require('./package-lock.json').packages['node_modules/playwright-core'].version") + echo "version=${version}" >> "$GITHUB_OUTPUT" + + - name: Restore Playwright ffmpeg cache + id: cache-ffmpeg + if: steps.tiers.outputs.count != '0' + uses: actions/cache/restore@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright ffmpeg (best effort) + id: install-ffmpeg + if: steps.tiers.outputs.count != '0' && steps.cache-ffmpeg.outputs.cache-hit != 'true' timeout-minutes: 3 continue-on-error: true - run: timeout -k 10 90 npx playwright install ffmpeg + run: | + timeout -k 10 60 npx playwright install ffmpeg + .github/scripts/verify-playwright-ffmpeg.sh + + - name: Save Playwright ffmpeg cache + if: steps.tiers.outputs.count != '0' && steps.install-ffmpeg.outcome == 'success' + continue-on-error: true + uses: actions/cache/save@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} # Optional fonts only — see the note in playwright-mock.yml. - name: Install optional Playwright font dependencies (best effort) diff --git a/.github/workflows/playwright-mock.yml b/.github/workflows/playwright-mock.yml index 5f984eb6bb..047f416920 100644 --- a/.github/workflows/playwright-mock.yml +++ b/.github/workflows/playwright-mock.yml @@ -146,13 +146,48 @@ jobs: # `video: 'on-first-retry'` needs ffmpeg; without it the first retry dies in # browserContext.newPage before the test body runs, so a flaky test loses the - # retry that would have recovered it. The CLI can hang after the download - # finishes on these runners, so bound it and keep it non-fatal — worst case is - # today's behaviour of retrying without video. + # retry that would have recovered it. + # + # This step used to burn its full 90s bound on every job. Playwright's bundled + # extractor hangs on Node 24.16.0 (a yauzl/extract-zip regression fixed in + # Playwright 1.60.0): the 2.3MB download finished in under a second, then + # extraction stalled and the timeout reaped it, leaving a truncated binary and + # no INSTALLATION_COMPLETE marker — so ffmpeg was never actually installed and + # retries never got video. With Playwright bumped past the fix the install + # takes about a second, and a restored cache skips it outright. + # + # The cache is only saved once the binary is verified to run, so a partial + # extraction can never be promoted into a cache every later job restores. + # Kept non-fatal: retry video is a debugging aid, not something CI asserts on. + - name: Resolve Playwright version + id: playwright-version + run: | + version=$(node -p "require('./package-lock.json').packages['node_modules/playwright-core'].version") + echo "version=${version}" >> "$GITHUB_OUTPUT" + + - name: Restore Playwright ffmpeg cache + id: cache-ffmpeg + uses: actions/cache/restore@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + - name: Install Playwright ffmpeg (best effort) + id: install-ffmpeg + if: steps.cache-ffmpeg.outputs.cache-hit != 'true' timeout-minutes: 3 continue-on-error: true - run: timeout -k 10 90 npx playwright install ffmpeg + run: | + timeout -k 10 60 npx playwright install ffmpeg + .github/scripts/verify-playwright-ffmpeg.sh + + - name: Save Playwright ffmpeg cache + if: steps.install-ffmpeg.outcome == 'success' + continue-on-error: true + uses: actions/cache/save@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} # The runner's Chrome is an apt package, so its real library dependencies are # already satisfied; all `install-deps` adds here are optional CJK/Thai/Cyrillic @@ -246,10 +281,35 @@ jobs: run: google-chrome --version # ffmpeg for retry video — see the note in the e2e_shards job. + - name: Resolve Playwright version + id: playwright-version + run: | + version=$(node -p "require('./package-lock.json').packages['node_modules/playwright-core'].version") + echo "version=${version}" >> "$GITHUB_OUTPUT" + + - name: Restore Playwright ffmpeg cache + id: cache-ffmpeg + uses: actions/cache/restore@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} + - name: Install Playwright ffmpeg (best effort) + id: install-ffmpeg + if: steps.cache-ffmpeg.outputs.cache-hit != 'true' timeout-minutes: 3 continue-on-error: true - run: timeout -k 10 90 npx playwright install ffmpeg + run: | + timeout -k 10 60 npx playwright install ffmpeg + .github/scripts/verify-playwright-ffmpeg.sh + + - name: Save Playwright ffmpeg cache + if: steps.install-ffmpeg.outcome == 'success' + continue-on-error: true + uses: actions/cache/save@v5 + with: + path: ~/.cache/ms-playwright + key: playwright-ffmpeg-${{ runner.os }}-${{ steps.playwright-version.outputs.version }} # This job deliberately skips the optional font install: its bounded # Playwright apt process can outlive the wrapper on a slow mirror and diff --git a/package-lock.json b/package-lock.json index bd0d902cae..1ef901012b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@eslint/compat": "^1.2.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.20.0", - "@playwright/test": "^1.56.1", + "@playwright/test": "^1.62.1", "@types/react-virtualized": "^9.22.0", "brace-expansion": "^2.1.2", "caniuse-lite": "^1.0.30001741", @@ -12892,19 +12892,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", - "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.1" + "playwright": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@preact/signals-core": { @@ -34992,35 +34992,35 @@ } }, "node_modules/playwright": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", - "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.1" + "playwright-core": "1.62.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" }, "optionalDependencies": { "fsevents": "2.3.2" } }, "node_modules/playwright-core": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", - "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/playwright/node_modules/fsevents": { @@ -35029,6 +35029,7 @@ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" diff --git a/package.json b/package.json index 4e0aaf6620..6f61835b0d 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "@eslint/compat": "^1.2.6", "@eslint/eslintrc": "^3.3.4", "@eslint/js": "^9.20.0", - "@playwright/test": "^1.56.1", + "@playwright/test": "^1.62.1", "@types/react-virtualized": "^9.22.0", "brace-expansion": "^2.1.2", "caniuse-lite": "^1.0.30001741",