From 91cab8d9fe9b7edbeb7d14a46649859aba5dce61 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Wed, 25 Mar 2026 18:04:11 +0100 Subject: [PATCH 1/4] Add Playwright visual regression testing Self-hosted full-page E2E visual testing alongside existing Chromatic/Storybook component tests. Uses Docker run-server pattern for consistent Linux rendering locally and in CI, with zero cost on public repos. - Add @playwright/test with Docker browser server config - Add homepage visual smoke test with baseline screenshot - Add visual-tests job to PR and push CI workflows - Add pw-test, pw-update, pw-report Makefile targets Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr.yaml | 42 +++++++++++ .github/workflows/push.yaml | 42 +++++++++++ .gitignore | 5 ++ Makefile | 14 +++- .../homepage-chromium.png | Bin 0 -> 76439 bytes app/e2e/visual/homepage.visual.spec.ts | 11 +++ app/e2e/visual/screenshot.css | 11 +++ app/package.json | 1 + app/playwright.config.ts | 69 ++++++++++++++++++ app/vite.config.mjs | 1 + bun.lock | 16 +++- 11 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 app/e2e/__screenshots__/visual/homepage.visual.spec.ts/homepage-chromium.png create mode 100644 app/e2e/visual/homepage.visual.spec.ts create mode 100644 app/e2e/visual/screenshot.css create mode 100644 app/playwright.config.ts diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 95979ac8d..2bd64bbc5 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -113,6 +113,48 @@ jobs: done if [ "$FAILED" = "1" ]; then exit 1; fi + visual-tests: + name: Visual regression + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.58.2-noble + options: --ipc=host + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build design system + run: bun run design-system:build + + - name: Run visual tests + working-directory: ./app + run: npx playwright test --grep @visual + env: + HOME: /root + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: app/playwright-report/ + retention-days: 14 + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: failure() + with: + name: visual-test-results + path: app/test-results/ + retention-days: 7 + build: name: Build runs-on: ubuntu-latest diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index d786e0d03..34a82ded0 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -104,6 +104,48 @@ jobs: done if [ "$FAILED" = "1" ]; then exit 1; fi + visual-tests: + name: Visual regression + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.58.2-noble + options: --ipc=host + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build design system + run: bun run design-system:build + + - name: Run visual tests + working-directory: ./app + run: npx playwright test --grep @visual + env: + HOME: /root + + - name: Upload HTML report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: app/playwright-report/ + retention-days: 14 + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: failure() + with: + name: visual-test-results + path: app/test-results/ + retention-days: 7 + build: name: Build runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 0938796c4..bd64c908b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,11 @@ coverage/ *.lcov .nyc_output +# Playwright +app/test-results/ +app/playwright-report/ +app/blob-report/ + # Production dist/ dist-*/ diff --git a/Makefile b/Makefile index e40b0203f..a551ba986 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install dev build test lint format clean deploy +.PHONY: help install dev build test lint format clean deploy pw-test pw-update pw-report help: @echo "Available commands:" @@ -11,6 +11,9 @@ help: @echo " make format - Format code" @echo " make clean - Clean build artifacts" @echo " make deploy - Build and deploy to GitHub Pages" + @echo " make pw-test - Run Playwright visual tests" + @echo " make pw-update - Update visual test baselines" + @echo " make pw-report - Open Playwright HTML report" install: bun install @@ -41,3 +44,12 @@ clean: deploy: build @echo "Build complete. GitHub Actions will handle deployment" + +pw-test: + cd app && npx playwright test --grep @visual + +pw-update: + cd app && npx playwright test --grep @visual --update-snapshots + +pw-report: + cd app && bunx playwright show-report diff --git a/app/e2e/__screenshots__/visual/homepage.visual.spec.ts/homepage-chromium.png b/app/e2e/__screenshots__/visual/homepage.visual.spec.ts/homepage-chromium.png new file mode 100644 index 0000000000000000000000000000000000000000..7ceb5edcccccf423f52d8a732373da761185ddb5 GIT binary patch literal 76439 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 literal 0 HcmV?d00001 diff --git a/app/e2e/visual/homepage.visual.spec.ts b/app/e2e/visual/homepage.visual.spec.ts new file mode 100644 index 000000000..bcaba9c9a --- /dev/null +++ b/app/e2e/visual/homepage.visual.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Homepage', () => { + test('renders correctly @visual', async ({ page }) => { + await page.goto('/'); + await page.waitForLoadState('networkidle'); + await expect(page).toHaveScreenshot('homepage.png', { + fullPage: true, + }); + }); +}); diff --git a/app/e2e/visual/screenshot.css b/app/e2e/visual/screenshot.css new file mode 100644 index 000000000..8beeb8a73 --- /dev/null +++ b/app/e2e/visual/screenshot.css @@ -0,0 +1,11 @@ +/* Applied during Playwright visual screenshots to eliminate flaky dynamic content. */ + +*, +*::before, +*::after { + animation-duration: 0s !important; + animation-delay: 0s !important; + transition-duration: 0s !important; + transition-delay: 0s !important; + scroll-behavior: auto !important; +} diff --git a/app/package.json b/app/package.json index c5859c8a7..a1d27eedc 100644 --- a/app/package.json +++ b/app/package.json @@ -71,6 +71,7 @@ "devDependencies": { "@eslint/js": "^9.29.0", "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@playwright/test": "^1.58.2", "@storybook/addon-essentials": "^8.6.14", "@storybook/react": "^8.6.12", "@storybook/react-vite": "^8.6.12", diff --git a/app/playwright.config.ts b/app/playwright.config.ts new file mode 100644 index 000000000..69cef5533 --- /dev/null +++ b/app/playwright.config.ts @@ -0,0 +1,69 @@ +import { defineConfig, devices } from '@playwright/test'; + +const isCI = !!process.env.CI; +const PW_VERSION = '1.58.2'; +const PW_IMAGE = `mcr.microsoft.com/playwright:v${PW_VERSION}-noble`; +const PW_SERVER_PORT = 3200; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: isCI, + retries: isCI ? 2 : 0, + workers: isCI ? 1 : undefined, + reporter: isCI ? [['html', { open: 'never' }], ['github']] : [['html', { open: 'on-failure' }]], + + snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}-{projectName}{ext}', + + expect: { + toHaveScreenshot: { + maxDiffPixelRatio: 0.005, + threshold: 0.2, + animations: 'disabled', + caret: 'hide', + scale: 'css', + stylePath: './e2e/visual/screenshot.css', + }, + }, + + use: { + baseURL: isCI ? 'http://localhost:3000' : 'http://host.docker.internal:3000', + trace: 'on-first-retry', + timezoneId: 'America/New_York', + ...(!isCI && { + connectOptions: { + wsEndpoint: `ws://127.0.0.1:${PW_SERVER_PORT}/`, + }, + }), + }, + + webServer: [ + { + command: 'VITE_APP_MODE=website npx vite', + url: 'http://localhost:3000', + reuseExistingServer: !isCI, + timeout: 120_000, + }, + ...(!isCI + ? [ + { + command: `docker run --rm --init -p ${PW_SERVER_PORT}:${PW_SERVER_PORT} ${PW_IMAGE} npx playwright run-server --port ${PW_SERVER_PORT} --host 0.0.0.0`, + url: `http://localhost:${PW_SERVER_PORT}`, + timeout: 120_000, + reuseExistingServer: !isCI, + gracefulShutdown: { signal: 'SIGTERM' as const, timeout: 10_000 }, + }, + ] + : []), + ], + + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1280, height: 720 }, + }, + }, + ], +}); diff --git a/app/vite.config.mjs b/app/vite.config.mjs index 643f6f995..f9c4b069c 100644 --- a/app/vite.config.mjs +++ b/app/vite.config.mjs @@ -123,5 +123,6 @@ export default defineConfig({ globals: true, environment: 'jsdom', setupFiles: './vitest.setup.mjs', + exclude: ['e2e/**', 'node_modules/**'], }, }); diff --git a/bun.lock b/bun.lock index 3c033b2df..716889da4 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "policyengine-monorepo", @@ -57,6 +58,7 @@ "devDependencies": { "@eslint/js": "^9.29.0", "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@playwright/test": "^1.58.2", "@storybook/addon-essentials": "^8.6.14", "@storybook/react": "^8.6.12", "@storybook/react-vite": "^8.6.12", @@ -125,7 +127,7 @@ "name": "@policyengine/website", "version": "0.1.0", "dependencies": { - "@policyengine/design-system": "workspace:*", + "@policyengine/design-system": "*", "@tabler/icons-react": "^3.31.0", "framer-motion": "^12.38.0", "fuse.js": "^7.1.0", @@ -462,6 +464,8 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="], + "@plotly/d3": ["@plotly/d3@3.8.2", "", {}, "sha512-wvsNmh1GYjyJfyEBPKJLTMzgf2c2bEbSIL50lmqVUi+o1NHaLPi1Lb4v7VxXXJn043BhNyrxUrWI85Q+zmjOVA=="], "@plotly/d3-sankey": ["@plotly/d3-sankey@0.7.2", "", { "dependencies": { "d3-array": "1", "d3-collection": "1", "d3-shape": "^1.2.0" } }, "sha512-2jdVos1N3mMp3QW0k2q1ph7Gd6j5PY1YihBrwpkFnKqO+cqtZq3AdEYUeSGXMeLsBDQYiqTVcihYfk8vr5tqhw=="], @@ -2052,7 +2056,11 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "plotly.js": ["plotly.js@3.4.0", "", { "dependencies": { "@plotly/d3": "3.8.2", "@plotly/d3-sankey": "0.7.2", "@plotly/d3-sankey-circular": "0.33.1", "@plotly/mapbox-gl": "1.13.4", "@plotly/regl": "^2.1.2", "@turf/area": "^7.1.0", "@turf/bbox": "^7.1.0", "@turf/centroid": "^7.1.0", "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "2.0.0", "color-rgba": "3.0.0", "country-regex": "^1.1.0", "d3-force": "^1.2.1", "d3-format": "^1.4.5", "d3-geo": "^1.12.1", "d3-geo-projection": "^2.9.0", "d3-hierarchy": "^1.1.9", "d3-interpolate": "^3.0.1", "d3-time": "^1.1.0", "d3-time-format": "^2.2.3", "fast-isnumeric": "^1.1.4", "gl-mat4": "^1.2.0", "gl-text": "^1.4.0", "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", "is-mobile": "^4.0.0", "maplibre-gl": "^4.7.1", "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", "mouse-wheel": "^1.2.0", "native-promise-only": "^0.8.1", "parse-svg-path": "^0.1.2", "point-in-polygon": "^1.1.0", "polybooljs": "^1.2.2", "probe-image-size": "^7.2.3", "regl-error2d": "^2.0.12", "regl-line2d": "^3.1.3", "regl-scatter2d": "^3.3.1", "regl-splom": "^1.0.14", "strongly-connected-components": "^1.0.1", "superscript-text": "^1.0.0", "svg-path-sdf": "^1.1.3", "tinycolor2": "^1.4.2", "to-px": "1.0.1", "topojson-client": "^3.1.0", "webgl-context": "^2.2.0", "world-calendars": "^1.0.4" } }, "sha512-jdWfHLB8AxlGUmVhqJTGEKdwjCKGn9Yi8yg16067/JyqseuSado7F6IOM1XPZspdZyO/cf8IPuy7ROlVhqZZNw=="], + "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], + + "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], + + "plotly.js": ["plotly.js@3.3.1", "", { "dependencies": { "@plotly/d3": "3.8.2", "@plotly/d3-sankey": "0.7.2", "@plotly/d3-sankey-circular": "0.33.1", "@plotly/mapbox-gl": "1.13.4", "@plotly/regl": "^2.1.2", "@turf/area": "^7.1.0", "@turf/bbox": "^7.1.0", "@turf/centroid": "^7.1.0", "base64-arraybuffer": "^1.0.2", "canvas-fit": "^1.5.0", "color-alpha": "1.0.4", "color-normalize": "1.5.0", "color-parse": "2.0.0", "color-rgba": "3.0.0", "country-regex": "^1.1.0", "d3-force": "^1.2.1", "d3-format": "^1.4.5", "d3-geo": "^1.12.1", "d3-geo-projection": "^2.9.0", "d3-hierarchy": "^1.1.9", "d3-interpolate": "^3.0.1", "d3-time": "^1.1.0", "d3-time-format": "^2.2.3", "fast-isnumeric": "^1.1.4", "gl-mat4": "^1.2.0", "gl-text": "^1.4.0", "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", "is-mobile": "^4.0.0", "maplibre-gl": "^4.7.1", "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", "mouse-wheel": "^1.2.0", "native-promise-only": "^0.8.1", "parse-svg-path": "^0.1.2", "point-in-polygon": "^1.1.0", "polybooljs": "^1.2.2", "probe-image-size": "^7.2.3", "regl-error2d": "^2.0.12", "regl-line2d": "^3.1.3", "regl-scatter2d": "^3.3.1", "regl-splom": "^1.0.14", "strongly-connected-components": "^1.0.1", "superscript-text": "^1.0.0", "svg-path-sdf": "^1.1.3", "tinycolor2": "^1.4.2", "to-px": "1.0.1", "topojson-client": "^3.1.0", "webgl-context": "^2.2.0", "world-calendars": "^1.0.4" } }, "sha512-SrGSZ02HvCWQIYsbQX4sgjgGo7k4T+Oz8a+RQwE5Caz+yu1vputBM1UThmiOPY51B5HzO9ajym8Rl4pmLj+i9Q=="], "point-in-polygon": ["point-in-polygon@1.1.0", "", {}, "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="], @@ -2804,14 +2812,14 @@ "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "plotly.js/d3-geo": ["d3-geo@1.12.1", "", { "dependencies": { "d3-array": "1" } }, "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg=="], "policyengine-app-v2/framer-motion": ["framer-motion@12.29.0", "", { "dependencies": { "motion-dom": "^12.29.0", "motion-utils": "^12.27.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-1gEFGXHYV2BD42ZPTFmSU9buehppU+bCuOnHU0AD18DKh9j4DuTx47MvqY5ax+NNWRtK32qIcJf1UxKo1WwjWg=="], "policyengine-app-v2/react-markdown": ["react-markdown@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw=="], - "policyengine-app-v2/tailwindcss": ["tailwindcss@4.2.0", "", {}, "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q=="], - "policyengine-app-v2/vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], From e2b4aa2c3a96bfda9270d306cdcf2748e4735e1d Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Wed, 25 Mar 2026 23:29:31 +0100 Subject: [PATCH 2/4] Use npm instead of bun in visual-tests CI job The Playwright Docker image lacks unzip, which setup-bun requires. Since the image already has Node/npm, use npm directly. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr.yaml | 7 ++----- .github/workflows/push.yaml | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2bd64bbc5..774edf777 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -124,14 +124,11 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Bun - uses: oven-sh/setup-bun@v2 - - name: Install dependencies - run: bun install --frozen-lockfile + run: npm install --frozen-lockfile - name: Build design system - run: bun run design-system:build + run: npm run design-system:build - name: Run visual tests working-directory: ./app diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 34a82ded0..c528093b3 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -115,14 +115,11 @@ jobs: - name: Check out repository uses: actions/checkout@v4 - - name: Set up Bun - uses: oven-sh/setup-bun@v2 - - name: Install dependencies - run: bun install --frozen-lockfile + run: npm install --frozen-lockfile - name: Build design system - run: bun run design-system:build + run: npm run design-system:build - name: Run visual tests working-directory: ./app From 92a58d2f778d16e7a0dbc458a9438dc9965b7fbd Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Thu, 26 Mar 2026 22:34:34 +0100 Subject: [PATCH 3/4] Install unzip and bun in visual-tests CI container The Playwright Docker image lacks unzip, which setup-bun needs. Install unzip first, then use bun consistently with the other CI jobs. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/pr.yaml | 10 ++++++++-- .github/workflows/push.yaml | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 774edf777..733e21a32 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -124,11 +124,17 @@ jobs: - name: Check out repository uses: actions/checkout@v4 + - name: Install unzip + run: apt-get update && apt-get install -y unzip + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + - name: Install dependencies - run: npm install --frozen-lockfile + run: bun install --frozen-lockfile - name: Build design system - run: npm run design-system:build + run: bun run design-system:build - name: Run visual tests working-directory: ./app diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index c528093b3..551f687fc 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -115,11 +115,17 @@ jobs: - name: Check out repository uses: actions/checkout@v4 + - name: Install unzip + run: apt-get update && apt-get install -y unzip + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + - name: Install dependencies - run: npm install --frozen-lockfile + run: bun install --frozen-lockfile - name: Build design system - run: npm run design-system:build + run: bun run design-system:build - name: Run visual tests working-directory: ./app From 2b0a7dcf6eef70bbc3570ee3c20d3c3e68b2ff14 Mon Sep 17 00:00:00 2001 From: Anthony Volk Date: Thu, 26 Mar 2026 23:08:46 +0100 Subject: [PATCH 4/4] Add update-visual-baselines workflow Supports two trigger mechanisms: - PR comment: type "/update-snapshots" on a PR - Manual dispatch: run from Actions tab on any branch Regenerates Playwright baselines in the same Docker container as CI, commits updated PNGs back to the branch. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/update-visual-baselines.yml | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/update-visual-baselines.yml diff --git a/.github/workflows/update-visual-baselines.yml b/.github/workflows/update-visual-baselines.yml new file mode 100644 index 000000000..9e1650716 --- /dev/null +++ b/.github/workflows/update-visual-baselines.yml @@ -0,0 +1,84 @@ +name: Update visual baselines + +on: + # Pattern 1: Comment "/update-snapshots" on a PR + issue_comment: + types: [created] + # Pattern 2: Manual trigger from Actions tab (pick a branch) + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + update-baselines: + name: Update visual baselines + # For issue_comment: only run on PRs when comment is "/update-snapshots" + # For workflow_dispatch: always run + if: > + github.event_name == 'workflow_dispatch' || + ( + github.event.issue.pull_request && + github.event.comment.body == '/update-snapshots' + ) + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.58.2-noble + options: --ipc=host + + steps: + # For issue_comment, we need to resolve the PR branch name + - name: Get PR branch + if: github.event_name == 'issue_comment' + id: pr + run: | + apt-get update && apt-get install -y jq curl + PR_DATA=$(curl -s \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}") + echo "branch=$(echo "$PR_DATA" | jq -r '.head.ref')" >> "$GITHUB_OUTPUT" + + - name: Check out repository + uses: actions/checkout@v4 + with: + ref: ${{ steps.pr.outputs.branch || github.ref }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install unzip + run: | + if ! command -v unzip &> /dev/null; then + apt-get update && apt-get install -y unzip + fi + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build design system + run: bun run design-system:build + + - name: Update visual baselines + working-directory: ./app + run: npx playwright test --grep @visual --update-snapshots --reporter=list + env: + HOME: /root + + - name: Commit updated baselines + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update visual baselines" + file_pattern: "app/e2e/__screenshots__/**/*.png" + + # Add a rocket reaction so the commenter knows it worked + - name: React to comment + if: github.event_name == 'issue_comment' + run: | + curl -s -X POST \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -d '{"content": "rocket"}'