From f700171d67c800ec007b821973bb880e1cceb291 Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 07:50:22 +0200 Subject: [PATCH 01/10] ci: cache Puppeteer's Chrome instead of re-installing it every run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build_lint_and_test job cached only the pnpm store. Puppeteer downloads Chrome to ~/.cache/puppeteer (outside node_modules / the store), and pnpm's side-effects cache records the postinstall as already-run on a store cache hit, so the browser binary was never preserved — the explicit `puppeteer browsers install chrome` step re-fetched it every run. When the runner image shipped a stale/partial ~/.cache/puppeteer for the pinned Chrome build, @puppeteer/browsers refused to overwrite it and failed with "the browser folder exists but the executable is missing", blocking the job. Relocate the browser to a workspace-local PUPPETEER_CACHE_DIR and cache that directory (keyed on the lockfile). The install step is now a no-op on a cache hit and a clean download into an empty dir on a miss, so it can't collide with a partially-populated system cache. No `rm -rf`, and the binary is actually preserved across runs. --- .github/workflows/main.yml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9090380f15..9944ebe1d9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,6 +18,14 @@ jobs: build_lint_and_test: runs-on: ubuntu-24.04 + # Install Puppeteer's Chrome into a workspace-local, cacheable directory + # instead of the default ~/.cache/puppeteer. This keeps the browser binary + # out of any stale/partial system cache the runner image may ship (which + # made `puppeteer browsers install` fail with "the browser folder exists but + # the executable is missing") and lets us cache the binary ourselves. + env: + PUPPETEER_CACHE_DIR: ${{ github.workspace }}/.cache/puppeteer + steps: - name: Checkout uses: actions/checkout@v6 @@ -67,12 +75,20 @@ jobs: - name: Building Visual Regression Tests application for UI components run: pnpm visual-testing-app:build - # Puppeteer's postinstall hook downloads Chrome to ~/.cache/puppeteer. - # Under pnpm, the postinstall script's side-effects are cached in the pnpm - # store (which is restored from the GitHub Actions cache), so pnpm skips - # re-running it. But Chrome lives outside node_modules / the pnpm store, - # so the binary is missing on the runner. Install it explicitly here so - # the visual regression tests can find it. + # Puppeteer downloads Chrome outside node_modules / the pnpm store, so the + # pnpm-store cache (which records that the postinstall "ran") leaves the + # binary missing on a cache hit. Cache the browser directory itself, keyed + # on the lockfile, so it's preserved across runs: the explicit install + # below is a no-op on a hit, and a clean download into an empty + # workspace-local dir on a miss (never a partially-populated one). + - name: Cache Puppeteer browser + uses: actions/cache@v5 + with: + path: ${{ github.workspace }}/.cache/puppeteer + key: ${{ runner.os }}-puppeteer-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-puppeteer- + - name: Install Puppeteer's Chrome run: pnpm exec puppeteer browsers install chrome From de8c6d0908e588bc9bd20860678f90f80d6322fd Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 07:53:24 +0200 Subject: [PATCH 02/10] ci: key Puppeteer browser cache on the resolved puppeteer version Resolve the installed puppeteer version and use it as the cache key, instead of the lockfile hash + restore-keys. The Chrome build is pinned transitively by the puppeteer dependency, so a Puppeteer bump now changes the key and forces a clean re-download, while same-version runs reuse the cached binary. Dropping restore-keys avoids ever falling back to a stale-version browser, and avoids the cache churning on unrelated lockfile changes. --- .github/workflows/main.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9944ebe1d9..d5ccb9f5d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,17 +77,24 @@ jobs: # Puppeteer downloads Chrome outside node_modules / the pnpm store, so the # pnpm-store cache (which records that the postinstall "ran") leaves the - # binary missing on a cache hit. Cache the browser directory itself, keyed - # on the lockfile, so it's preserved across runs: the explicit install - # below is a no-op on a hit, and a clean download into an empty - # workspace-local dir on a miss (never a partially-populated one). + # binary missing on a cache hit. Cache the browser directory itself so it's + # preserved across runs: the explicit install below is a no-op on a hit, + # and a clean download into an empty workspace-local dir on a miss (never a + # partially-populated one). + # + # The Chrome build is pinned transitively by the `puppeteer` dependency, so + # key the cache on that version: a Puppeteer bump changes the key and forces + # a clean re-download; same-version runs reuse the cached binary. (No + # restore-keys — a stale-version fallback is exactly what we want to avoid.) + - name: Resolve Puppeteer version (for cache key) + id: puppeteer + run: echo "version=$(node -p "require('./node_modules/puppeteer/package.json').version")" >> "$GITHUB_OUTPUT" + - name: Cache Puppeteer browser uses: actions/cache@v5 with: path: ${{ github.workspace }}/.cache/puppeteer - key: ${{ runner.os }}-puppeteer-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-puppeteer- + key: ${{ runner.os }}-puppeteer-${{ steps.puppeteer.outputs.version }} - name: Install Puppeteer's Chrome run: pnpm exec puppeteer browsers install chrome From a66c87feb37facc940639adc27d6f5774427ea62 Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 08:08:24 +0200 Subject: [PATCH 03/10] ci: skip Puppeteer's postinstall download; install Chrome explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of the install failure: with PUPPETEER_CACHE_DIR set, Puppeteer's postinstall (run during `pnpm install` under the restored pnpm store) leaves a partial Chrome — the version folder without the executable. @puppeteer/browsers 2.3.0 then refuses to repair it and fails the explicit install with "the browser folder exists but the executable is missing". Set PUPPETEER_SKIP_DOWNLOAD=true for the install step so downloadBrowser() returns before touching the cache dir (verified against puppeteer 22.15.0's node/install.js). The explicit `puppeteer browsers install chrome` step — which does not consult that flag — remains the sole, authoritative installer into the version-keyed cache dir. --- .github/workflows/main.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d5ccb9f5d4..fae0ee7485 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,7 +49,14 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm- + # Skip Puppeteer's postinstall Chrome download. Under the restored pnpm + # store it leaves a partial browser folder (directory without the + # executable) in PUPPETEER_CACHE_DIR, which @puppeteer/browsers then + # refuses to repair ("the browser folder exists but the executable is + # missing"). Chrome is installed explicitly, into a cached dir, further down. - name: Install dependencies + env: + PUPPETEER_SKIP_DOWNLOAD: 'true' run: pnpm install --frozen-lockfile - name: Check for unmet constraints From 1e3d330e09eeb95b9bd484c54626042d629dee42 Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 09:01:59 +0200 Subject: [PATCH 04/10] ci: re-trigger CI (Percy build did not finalize on prior run) From da66a6f32958177ccc6ddd55fe93e99f01fa706e Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 09:34:19 +0200 Subject: [PATCH 05/10] ci: clear stale Puppeteer cache before install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the cache/relocation/skip-download experiments back to main's working flow. Adds a single step to remove a partial ~/.cache/puppeteer (browser folder without the executable) that the runner image can ship — @puppeteer/browsers refuses to repair an incomplete install (same check in 2.3.0 through 2.13.2), so a version bump can't fix it. Clearing it lets the postinstall download a clean Chrome. --- .github/workflows/main.yml | 49 ++++++++++---------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fae0ee7485..db7b553d43 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,14 +18,6 @@ jobs: build_lint_and_test: runs-on: ubuntu-24.04 - # Install Puppeteer's Chrome into a workspace-local, cacheable directory - # instead of the default ~/.cache/puppeteer. This keeps the browser binary - # out of any stale/partial system cache the runner image may ship (which - # made `puppeteer browsers install` fail with "the browser folder exists but - # the executable is missing") and lets us cache the binary ourselves. - env: - PUPPETEER_CACHE_DIR: ${{ github.workspace }}/.cache/puppeteer - steps: - name: Checkout uses: actions/checkout@v6 @@ -49,14 +41,14 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm- - # Skip Puppeteer's postinstall Chrome download. Under the restored pnpm - # store it leaves a partial browser folder (directory without the - # executable) in PUPPETEER_CACHE_DIR, which @puppeteer/browsers then - # refuses to repair ("the browser folder exists but the executable is - # missing"). Chrome is installed explicitly, into a cached dir, further down. + # GitHub's runner image can ship a partial ~/.cache/puppeteer (a browser + # folder without the executable). @puppeteer/browsers refuses to repair an + # incomplete install and fails the "Install Puppeteer's Chrome" step below. + # Clear it so Puppeteer's postinstall downloads a clean Chrome. + - name: Clear stale Puppeteer browser cache + run: rm -rf ~/.cache/puppeteer + - name: Install dependencies - env: - PUPPETEER_SKIP_DOWNLOAD: 'true' run: pnpm install --frozen-lockfile - name: Check for unmet constraints @@ -82,27 +74,12 @@ jobs: - name: Building Visual Regression Tests application for UI components run: pnpm visual-testing-app:build - # Puppeteer downloads Chrome outside node_modules / the pnpm store, so the - # pnpm-store cache (which records that the postinstall "ran") leaves the - # binary missing on a cache hit. Cache the browser directory itself so it's - # preserved across runs: the explicit install below is a no-op on a hit, - # and a clean download into an empty workspace-local dir on a miss (never a - # partially-populated one). - # - # The Chrome build is pinned transitively by the `puppeteer` dependency, so - # key the cache on that version: a Puppeteer bump changes the key and forces - # a clean re-download; same-version runs reuse the cached binary. (No - # restore-keys — a stale-version fallback is exactly what we want to avoid.) - - name: Resolve Puppeteer version (for cache key) - id: puppeteer - run: echo "version=$(node -p "require('./node_modules/puppeteer/package.json').version")" >> "$GITHUB_OUTPUT" - - - name: Cache Puppeteer browser - uses: actions/cache@v5 - with: - path: ${{ github.workspace }}/.cache/puppeteer - key: ${{ runner.os }}-puppeteer-${{ steps.puppeteer.outputs.version }} - + # Puppeteer's postinstall hook downloads Chrome to ~/.cache/puppeteer. + # Under pnpm, the postinstall script's side-effects are cached in the pnpm + # store (which is restored from the GitHub Actions cache), so pnpm skips + # re-running it. But Chrome lives outside node_modules / the pnpm store, + # so the binary is missing on the runner. Install it explicitly here so + # the visual regression tests can find it. - name: Install Puppeteer's Chrome run: pnpm exec puppeteer browsers install chrome From 03d82a7c9866b44b092261b0d4b17d9a31798e19 Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 10:16:59 +0200 Subject: [PATCH 06/10] ci: pin build_lint_and_test to ubuntu-22.04 to fix VRT snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ubuntu-24.04 runner image (~20260525) tightened AppArmor unprivileged-userns restrictions, preventing Percy/Chromium from launching — VRT ran green but captured 0 snapshots (Percy builds 'manually failed'). Last green VRT (81 snapshots, May 27) was on image 20260518. Pin the job to ubuntu-22.04, which has no such restriction, and drop the now- obsolete 24.04-specific workarounds: the aa-exec --profile=chrome wrapper (added for 24.04 AppArmor) and the ~/.cache/puppeteer clear (the husk was a 24.04 image artifact). This restores the pre-24.04 VRT configuration on a compatible OS. --- .github/workflows/main.yml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db7b553d43..e75a477f9a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,10 @@ permissions: jobs: build_lint_and_test: - runs-on: ubuntu-24.04 + # Pinned to 22.04: the 24.04 runner image (since ~20260525) tightened + # AppArmor unprivileged-userns restrictions that prevent Percy/Chromium from + # launching, so VRT produced 0 snapshots. 22.04 has no such restriction. + runs-on: ubuntu-22.04 steps: - name: Checkout @@ -41,13 +44,6 @@ jobs: restore-keys: | ${{ runner.os }}-pnpm- - # GitHub's runner image can ship a partial ~/.cache/puppeteer (a browser - # folder without the executable). @puppeteer/browsers refuses to repair an - # incomplete install and fails the "Install Puppeteer's Chrome" step below. - # Clear it so Puppeteer's postinstall downloads a clean Chrome. - - name: Clear stale Puppeteer browser cache - run: rm -rf ~/.cache/puppeteer - - name: Install dependencies run: pnpm install --frozen-lockfile @@ -84,13 +80,7 @@ jobs: run: pnpm exec puppeteer browsers install chrome - name: Running Visual Regression Tests for UI components - # aa-exec: - # ubuntu-24.04, the image used for `ubuntu-latest` as of Oct 14 2024(https://github.com/actions/runner-images/issues/10636), - # introduced security measures in its app-armor security (https://github.com/puppeteer/puppeteer/issues/12818#issuecomment-2247844464) - # that made it so that puppeteers chromium installation is not whitelisted, - # which made it so that the chromium sandbox is not available and puppeteer errors out, - # so we need to specify that we are using app armor's chrome profile when running puppeteer (https://github.com/mermaid-js/mermaid-cli/issues/730#issuecomment-2408615110) - run: aa-exec --profile=chrome -- pnpm vrt:components + run: pnpm vrt:components timeout-minutes: 20 env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} From a0443a9cfd5b3ec261d2732869080da7b83f0c3a Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 10:35:28 +0200 Subject: [PATCH 07/10] ci: point Percy at runner's Chrome to skip pruned Chromium download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause of the 0-snapshot VRT runs: @percy/core 1.31.13 pins Chromium snapshot revision 1300309 and auto-downloads it for asset discovery. That revision has been pruned from the public Chromium snapshots bucket, so the download (which took 4s on May 27) now hangs indefinitely and Percy captures no snapshots — independent of the runner OS. Set PERCY_BROWSER_EXECUTABLE to the runner's pre-installed Chrome so @percy/core uses it and skips the download (verified in @percy/core dist/browser.js: executable || install.chromium()). --- .github/workflows/main.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e75a477f9a..0bd75f77dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,10 @@ permissions: jobs: build_lint_and_test: - # Pinned to 22.04: the 24.04 runner image (since ~20260525) tightened - # AppArmor unprivileged-userns restrictions that prevent Percy/Chromium from - # launching, so VRT produced 0 snapshots. 22.04 has no such restriction. + # Pinned to 22.04: the current 24.04 runner image ships a partial + # ~/.cache/puppeteer that breaks `puppeteer browsers install`, and needs an + # AppArmor `aa-exec` workaround for Chrome's sandbox. 22.04 has neither, so + # the job runs without those workarounds. runs-on: ubuntu-22.04 steps: @@ -79,8 +80,16 @@ jobs: - name: Install Puppeteer's Chrome run: pnpm exec puppeteer browsers install chrome + # @percy/core pins a Chromium snapshot revision (1300309) for asset + # discovery and auto-downloads it; that revision was pruned from the public + # bucket, so the download now hangs and VRT captures 0 snapshots. Point + # Percy at the runner's pre-installed Chrome so it skips the download + # entirely (it falls back to the broken download if this path is empty). - name: Running Visual Regression Tests for UI components - run: pnpm vrt:components + run: | + export PERCY_BROWSER_EXECUTABLE="$(command -v google-chrome google-chrome-stable chromium-browser 2>/dev/null | head -1)" + echo "Percy discovery browser: ${PERCY_BROWSER_EXECUTABLE:-}" + pnpm vrt:components timeout-minutes: 20 env: PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} From 2f14bbb8cbd3bf1a63f8b247a59a3410b37459bb Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 10:48:30 +0200 Subject: [PATCH 08/10] ci: use runner's Chrome for both visual tests and Percy Both pinned browser downloads now fail in CI, yielding 0 snapshots: - puppeteer's Chrome-for-Testing 127.0.6533.88 no longer installs (jest-puppeteer then can't find Chrome -> globalSetup error) - @percy/core's Chromium snapshot 1300309 was pruned from the public bucket, so its auto-download hangs Point both at the runner's pre-installed Chrome via PUPPETEER_EXECUTABLE_PATH and PERCY_BROWSER_EXECUTABLE, and drop the now-redundant 'Install Puppeteer's Chrome' step. jest-puppeteer.config.js already reads PUPPETEER_EXECUTABLE_PATH and runs with --no-sandbox. Fails fast if no Chrome is found rather than silently falling back to a broken download. --- .github/workflows/main.yml | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0bd75f77dc..553de88992 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,24 +71,19 @@ jobs: - name: Building Visual Regression Tests application for UI components run: pnpm visual-testing-app:build - # Puppeteer's postinstall hook downloads Chrome to ~/.cache/puppeteer. - # Under pnpm, the postinstall script's side-effects are cached in the pnpm - # store (which is restored from the GitHub Actions cache), so pnpm skips - # re-running it. But Chrome lives outside node_modules / the pnpm store, - # so the binary is missing on the runner. Install it explicitly here so - # the visual regression tests can find it. - - name: Install Puppeteer's Chrome - run: pnpm exec puppeteer browsers install chrome - - # @percy/core pins a Chromium snapshot revision (1300309) for asset - # discovery and auto-downloads it; that revision was pruned from the public - # bucket, so the download now hangs and VRT captures 0 snapshots. Point - # Percy at the runner's pre-installed Chrome so it skips the download - # entirely (it falls back to the broken download if this path is empty). + # Both the visual-test browser (jest-puppeteer) and Percy's asset-discovery + # browser need a Chrome. Their pinned runtime downloads both fail in CI now + # — Puppeteer's Chrome-for-Testing 127.0.6533.88 and Percy's Chromium + # snapshot 1300309 (pruned from the public bucket) — which yielded 0 + # snapshots. Use the runner's pre-installed Chrome for both instead of + # downloading. jest-puppeteer.config.js already reads + # PUPPETEER_EXECUTABLE_PATH and launches with --no-sandbox. - name: Running Visual Regression Tests for UI components run: | - export PERCY_BROWSER_EXECUTABLE="$(command -v google-chrome google-chrome-stable chromium-browser 2>/dev/null | head -1)" - echo "Percy discovery browser: ${PERCY_BROWSER_EXECUTABLE:-}" + CHROME="$(command -v google-chrome google-chrome-stable chromium-browser 2>/dev/null | head -1)" + echo "Using Chrome for VRT + Percy: ${CHROME:?no Chrome found on runner}" + export PUPPETEER_EXECUTABLE_PATH="$CHROME" + export PERCY_BROWSER_EXECUTABLE="$CHROME" pnpm vrt:components timeout-minutes: 20 env: From 5d9018fd95d643bf7e14d193e09efc5dfa2a4141 Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 11:43:27 +0200 Subject: [PATCH 09/10] ci: provision Chrome via setup-chrome on ubuntu-24.04 for VRT + Percy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert the ubuntu-22.04 pin (unnecessary: deleting the puppeteer install step removes the ~/.cache/puppeteer husk issue, and --no-sandbox in both launchers makes the 24.04 AppArmor workaround moot — so 24.04 works and avoids the 22.04 deprecation track). Provision Chrome explicitly with browser-actions/setup-chrome (SHA-pinned) and point PUPPETEER_EXECUTABLE_PATH + PERCY_BROWSER_EXECUTABLE at it, instead of relying on the runner's rolling-latest Chrome. Using chrome-version: stable for now; will pin a specific build for reproducible Percy snapshots once green. --- .github/workflows/main.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 553de88992..0085abfb84 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,11 +16,7 @@ permissions: jobs: build_lint_and_test: - # Pinned to 22.04: the current 24.04 runner image ships a partial - # ~/.cache/puppeteer that breaks `puppeteer browsers install`, and needs an - # AppArmor `aa-exec` workaround for Chrome's sandbox. 22.04 has neither, so - # the job runs without those workarounds. - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout @@ -71,22 +67,27 @@ jobs: - name: Building Visual Regression Tests application for UI components run: pnpm visual-testing-app:build - # Both the visual-test browser (jest-puppeteer) and Percy's asset-discovery - # browser need a Chrome. Their pinned runtime downloads both fail in CI now - # — Puppeteer's Chrome-for-Testing 127.0.6533.88 and Percy's Chromium - # snapshot 1300309 (pruned from the public bucket) — which yielded 0 - # snapshots. Use the runner's pre-installed Chrome for both instead of - # downloading. jest-puppeteer.config.js already reads + # The visual-test browser (jest-puppeteer) and Percy's asset-discovery + # browser both need a Chrome. Puppeteer's and Percy's own pinned downloads + # (Chrome-for-Testing 127.0.6533.88 / Percy Chromium snapshot 1300309) stopped + # working in CI and yielded 0 snapshots, so we provision Chrome explicitly + # here and point both tools at it. jest-puppeteer.config.js reads # PUPPETEER_EXECUTABLE_PATH and launches with --no-sandbox. + # TODO: pin `chrome-version` to a specific build for reproducible snapshots. + - name: Set up Chrome + id: setup-chrome + uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2 + with: + chrome-version: stable + - name: Running Visual Regression Tests for UI components run: | - CHROME="$(command -v google-chrome google-chrome-stable chromium-browser 2>/dev/null | head -1)" - echo "Using Chrome for VRT + Percy: ${CHROME:?no Chrome found on runner}" - export PUPPETEER_EXECUTABLE_PATH="$CHROME" - export PERCY_BROWSER_EXECUTABLE="$CHROME" + echo "Using Chrome ${{ steps.setup-chrome.outputs.chrome-version }} at $PUPPETEER_EXECUTABLE_PATH" pnpm vrt:components timeout-minutes: 20 env: + PUPPETEER_EXECUTABLE_PATH: ${{ steps.setup-chrome.outputs.chrome-path }} + PERCY_BROWSER_EXECUTABLE: ${{ steps.setup-chrome.outputs.chrome-path }} PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} From ea2055cbe9f324d7ba97d132de2816b8ea40890f Mon Sep 17 00:00:00 2001 From: Michael Salzmann Date: Thu, 11 Jun 2026 12:02:40 +0200 Subject: [PATCH 10/10] fix(vrt): add --no-sandbox to self-launching rich-text specs; pin Chrome 127 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rich-text-input and localized-rich-text-input visualspecs launch their OWN puppeteer browser (not the jest-puppeteer global one) and did not pass --no-sandbox. On ubuntu-24.04 (AppArmor restricts unprivileged user namespaces) that crashes with 'No usable sandbox', failing those 2 suites and dropping a Percy snapshot. They previously only passed because the removed aa-exec wrapper covered them. Add --no-sandbox/--disable-setuid-sandbox to both launches, matching jest-puppeteer.config.js used by the other 68 specs. Also pin setup-chrome to 127.0.6533.88 (puppeteer 22.15.0's target build and the Percy baseline's browser) instead of 'stable' — 'stable' pulled Chrome 149. --- .github/workflows/main.yml | 8 ++++++-- .../src/localized-rich-text-input.visualspec.js | 5 +++++ .../rich-text-input/src/rich-text-input.visualspec.js | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0085abfb84..6815a045cc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -73,12 +73,16 @@ jobs: # working in CI and yielded 0 snapshots, so we provision Chrome explicitly # here and point both tools at it. jest-puppeteer.config.js reads # PUPPETEER_EXECUTABLE_PATH and launches with --no-sandbox. - # TODO: pin `chrome-version` to a specific build for reproducible snapshots. + # + # Pinned to 127.0.6533.88 — the Chrome build puppeteer 22.15.0 targets and + # the one the Percy baseline was captured with. `chrome-version: stable` + # pulled Chrome 149 (~22 majors newer), which broke 2 visual test suites + # via CDP/runtime drift. Bump this in lockstep with puppeteer. - name: Set up Chrome id: setup-chrome uses: browser-actions/setup-chrome@2e1d749697dd1612b833dba4a722266286fbefcd # v2.1.2 with: - chrome-version: stable + chrome-version: 127.0.6533.88 - name: Running Visual Regression Tests for UI components run: | diff --git a/packages/components/inputs/localized-rich-text-input/src/localized-rich-text-input.visualspec.js b/packages/components/inputs/localized-rich-text-input/src/localized-rich-text-input.visualspec.js index aed3ef536a..7adf319cbc 100644 --- a/packages/components/inputs/localized-rich-text-input/src/localized-rich-text-input.visualspec.js +++ b/packages/components/inputs/localized-rich-text-input/src/localized-rich-text-input.visualspec.js @@ -9,6 +9,11 @@ jest.setTimeout(20000); beforeEach(async () => { browser = await puppeteer.launch({ headless: 'new', + // This spec launches its own browser (not the jest-puppeteer global one), so + // it must opt out of the Chrome sandbox itself. CI runners (Ubuntu 24.04+) + // restrict unprivileged user namespaces via AppArmor, so the sandbox can't + // start otherwise. Matches jest-puppeteer.config.js used by other specs. + args: ['--no-sandbox', '--disable-setuid-sandbox'], slowMo: 10, // Launching the browser in slow motion is necessary due to race conditions. Otherwise browser closes prematurely and tests fail. }); page = await browser.newPage(); diff --git a/packages/components/inputs/rich-text-input/src/rich-text-input.visualspec.js b/packages/components/inputs/rich-text-input/src/rich-text-input.visualspec.js index 0ffb71b343..0dbf767352 100644 --- a/packages/components/inputs/rich-text-input/src/rich-text-input.visualspec.js +++ b/packages/components/inputs/rich-text-input/src/rich-text-input.visualspec.js @@ -9,6 +9,11 @@ jest.setTimeout(20000); beforeEach(async () => { browser = await puppeteer.launch({ headless: 'new', + // This spec launches its own browser (not the jest-puppeteer global one), so + // it must opt out of the Chrome sandbox itself. CI runners (Ubuntu 24.04+) + // restrict unprivileged user namespaces via AppArmor, so the sandbox can't + // start otherwise. Matches jest-puppeteer.config.js used by other specs. + args: ['--no-sandbox', '--disable-setuid-sandbox'], slowMo: 10, // Launching the browser in slow motion is necessary due to race conditions. Otherwise browser closes prematurely and tests fail. }); page = await browser.newPage();