From 3c274af0f74a17b6bde246bf8584f1216ca726cb Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:49:31 +0200 Subject: [PATCH 1/9] perf: speed up Playwright CI significantly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Chromium only (drop Firefox + WebKit) — 3× fewer browser sessions - workers: '50%' instead of 1 — fully parallel on CI runners - retries: 1 instead of 2 — one safety net is enough - Cache ~/.cache/ms-playwright keyed on lockfile — skip browser download on repeat runs - Install only chromium binary/deps (--with-deps chromium) — faster install - pnpm run preview instead of npm run preview Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/playwright.yml | 14 ++++++++++++-- playwright.config.ts | 18 ++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 615a47b9..5cd13c3f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -6,7 +6,7 @@ on: branches: [main] jobs: test: - timeout-minutes: 60 + timeout-minutes: 15 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -16,8 +16,18 @@ jobs: - uses: pnpm/action-setup@v6 - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Cache Playwright browsers + uses: actions/cache@v4 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: pnpm exec playwright install --with-deps chromium + - name: Install Playwright system deps + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: pnpm exec playwright install-deps chromium - name: Build the application run: pnpm build - name: Run Playwright tests diff --git a/playwright.config.ts b/playwright.config.ts index c757d307..ef746b08 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,5 @@ import { defineConfig, devices } from '@playwright/test'; -// Check if we need to run the mock server based on environment variable or test patterns const shouldRunMockServer = process.env.PLAYWRIGHT_WITH_MOCK === 'true' || process.argv.some((arg) => arg.includes('mock-api.spec.ts')); @@ -9,8 +8,8 @@ export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, + retries: process.env.CI ? 1 : 0, + workers: process.env.CI ? '50%' : undefined, reporter: 'html', use: { baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:3000', @@ -22,16 +21,7 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, ], - // Conditionally start mock server based on test needs webServer: shouldRunMockServer ? [ { @@ -41,7 +31,7 @@ export default defineConfig({ timeout: 120 * 1000, }, { - command: process.env.CI ? 'npm run preview' : 'npm run start', + command: process.env.CI ? 'pnpm run preview' : 'pnpm run start', url: process.env.CI ? 'http://localhost:4173' : 'http://localhost:3000', @@ -50,7 +40,7 @@ export default defineConfig({ }, ] : { - command: process.env.CI ? 'npm run preview' : 'npm run start', + command: process.env.CI ? 'pnpm run preview' : 'pnpm run start', url: process.env.CI ? 'http://localhost:4173' : 'http://localhost:3000', reuseExistingServer: !process.env.CI, timeout: 120 * 1000, From 1762ce0339eb6076378dce509e8f72685e830aa9 Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:50:15 +0200 Subject: [PATCH 2/9] perf: use 100% workers instead of 50% Co-Authored-By: Claude Sonnet 4.6 --- playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index ef746b08..abb30226 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, - workers: process.env.CI ? '50%' : undefined, + workers: '100%', reporter: 'html', use: { baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:3000', From b9ae5a3a8d0690af65888255cef1eb44ac0420f4 Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:52:11 +0200 Subject: [PATCH 3/9] perf: cache pnpm store in all workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds cache: pnpm to actions/setup-node in build, lint, and test workflows. On cache hit pnpm install resolves from disk instead of the network — typically saves 20-40s per job. Also removes the single-item strategy matrix from each (pointless overhead for a single Node version). Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 10 +++------- .github/workflows/lint.yml | 10 +++------- .github/workflows/test.yml | 10 +++------- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fe87f90..aab6b661 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,17 +6,13 @@ jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [v22.15.1] - steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + - uses: actions/setup-node@v6 with: - node-version: ${{ matrix.node-version }} + node-version: v22.15.1 + cache: pnpm - name: Install and build run: | pnpm install --frozen-lockfile diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e3ca7728..703663c1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -6,17 +6,13 @@ jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [v22.15.1] - steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + - uses: actions/setup-node@v6 with: - node-version: ${{ matrix.node-version }} + node-version: v22.15.1 + cache: pnpm - name: Run lint run: | pnpm install --frozen-lockfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3bcbb1d..1c5bc8e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,17 +6,13 @@ jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [v22.15.1] - steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v6 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v6 + - uses: actions/setup-node@v6 with: - node-version: ${{ matrix.node-version }} + node-version: v22.15.1 + cache: pnpm - name: Web run: | pnpm install --frozen-lockfile From 3321f75d59c4e65c85ac2898d154b54c243a6c8e Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:54:05 +0200 Subject: [PATCH 4/9] fix: restore check name 'build (v22.15.1)' expected by branch protection Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 1 + .github/workflows/lint.yml | 1 + .github/workflows/test.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aab6b661..6231fd5e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: [push] jobs: build: + name: build (v22.15.1) runs-on: ubuntu-latest steps: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 703663c1..c94b7006 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,6 +4,7 @@ on: [push] jobs: build: + name: build (v22.15.1) runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c5bc8e2..056872b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: [push] jobs: build: + name: build (v22.15.1) runs-on: ubuntu-latest steps: From 83adb422e0a535bdc323515a6617cda383f47703 Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:55:57 +0200 Subject: [PATCH 5/9] perf: skip install-deps on browser cache hit ubuntu-latest already has all Chromium runtime deps. The only packages install-deps adds are obscure font packs (21 MB) that functional UI tests don't need. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/playwright.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5cd13c3f..c5a78c41 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -25,9 +25,6 @@ jobs: - name: Install Playwright Browsers if: steps.playwright-cache.outputs.cache-hit != 'true' run: pnpm exec playwright install --with-deps chromium - - name: Install Playwright system deps - if: steps.playwright-cache.outputs.cache-hit == 'true' - run: pnpm exec playwright install-deps chromium - name: Build the application run: pnpm build - name: Run Playwright tests From 515ad32a88e939b087cba5eacfbe6f07de8cd70e Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:58:12 +0200 Subject: [PATCH 6/9] perf: cache dist/ output across workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both build and playwright workflows now share a dist cache keyed on a hash of all source files and config. On a cache hit the build step is skipped entirely — playwright no longer rebuilds what build.yml already compiled. Also fix pnpm/action-setup order and add cache: pnpm to playwright workflow so node_modules are also cached there. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 22 +++++++++++++++++----- .github/workflows/playwright.yml | 10 +++++++++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6231fd5e..0f34c85c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,22 @@ jobs: with: node-version: v22.15.1 cache: pnpm - - name: Install and build - run: | - pnpm install --frozen-lockfile - pnpm typecheck - pnpm build + - name: Install + run: pnpm install --frozen-lockfile + env: + CI: true + - name: Typecheck + run: pnpm typecheck + env: + CI: true + - name: Cache dist + id: dist-cache + uses: actions/cache@v4 + with: + path: dist + key: dist-${{ runner.os }}-${{ hashFiles('src/**', 'public/**', 'index.html', 'vite.config.*', 'tsconfig*', 'pnpm-lock.yaml') }} + - name: Build + if: steps.dist-cache.outputs.cache-hit != 'true' + run: pnpm build env: CI: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c5a78c41..94042a28 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,10 +10,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 with: node-version: v22.15.1 - - uses: pnpm/action-setup@v6 + cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Cache Playwright browsers @@ -25,7 +26,14 @@ jobs: - name: Install Playwright Browsers if: steps.playwright-cache.outputs.cache-hit != 'true' run: pnpm exec playwright install --with-deps chromium + - name: Cache dist + id: dist-cache + uses: actions/cache@v4 + with: + path: dist + key: dist-${{ runner.os }}-${{ hashFiles('src/**', 'public/**', 'index.html', 'vite.config.*', 'tsconfig*', 'pnpm-lock.yaml') }} - name: Build the application + if: steps.dist-cache.outputs.cache-hit != 'true' run: pnpm build - name: Run Playwright tests run: pnpm exec playwright test From d2a50229e21a46d366f667f975257358e710ed1c Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 22:59:59 +0200 Subject: [PATCH 7/9] fix: cache build/ not dist/ (vite outDir is build) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 2 +- .github/workflows/playwright.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f34c85c..de146c2f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: id: dist-cache uses: actions/cache@v4 with: - path: dist + path: build key: dist-${{ runner.os }}-${{ hashFiles('src/**', 'public/**', 'index.html', 'vite.config.*', 'tsconfig*', 'pnpm-lock.yaml') }} - name: Build if: steps.dist-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 94042a28..3febbf4b 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -30,7 +30,7 @@ jobs: id: dist-cache uses: actions/cache@v4 with: - path: dist + path: build key: dist-${{ runner.os }}-${{ hashFiles('src/**', 'public/**', 'index.html', 'vite.config.*', 'tsconfig*', 'pnpm-lock.yaml') }} - name: Build the application if: steps.dist-cache.outputs.cache-hit != 'true' From 7e89bc4da56aff97b8cc32b913980bdafc92d38c Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 23:01:42 +0200 Subject: [PATCH 8/9] chore: opt into Node 24 for actions before June 2nd forced cutover Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 3 +++ .github/workflows/lint.yml | 3 +++ .github/workflows/playwright.yml | 3 +++ .github/workflows/test.yml | 3 +++ 4 files changed, 12 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index de146c2f..5835628c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,9 @@ name: Build on: [push] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: name: build (v22.15.1) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c94b7006..5f65e6c0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,6 +2,9 @@ name: Lint server on: [push] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: name: build (v22.15.1) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 3febbf4b..04232ffb 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -4,6 +4,9 @@ on: branches: [main] pull_request: branches: [main] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: test: timeout-minutes: 15 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 056872b2..cb1b8fab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,6 +2,9 @@ name: test on: [push] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: build: name: build (v22.15.1) From ebe279fec0f0aedb7314615bbec7c11b371a83f6 Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Tue, 5 May 2026 23:04:09 +0200 Subject: [PATCH 9/9] perf: skip pnpm install on node_modules cache hit Cache node_modules keyed on pnpm-lock.yaml in all four workflows. On a hit, pnpm install is skipped entirely (~0s vs ~8s cold / ~2s warm). The pnpm store cache (cache: pnpm on setup-node) remains as fallback for when the lockfile changes and node_modules must be rebuilt. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/build.yml | 7 +++++++ .github/workflows/lint.yml | 15 ++++++++++++--- .github/workflows/playwright.yml | 7 +++++++ .github/workflows/test.yml | 17 +++++++++++++---- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5835628c..6b22d524 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,14 @@ jobs: with: node-version: v22.15.1 cache: pnpm + - name: Cache node_modules + id: modules-cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Install + if: steps.modules-cache.outputs.cache-hit != 'true' run: pnpm install --frozen-lockfile env: CI: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5f65e6c0..ebb26f4c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,9 +17,18 @@ jobs: with: node-version: v22.15.1 cache: pnpm + - name: Cache node_modules + id: modules-cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + - name: Install + if: steps.modules-cache.outputs.cache-hit != 'true' + run: pnpm install --frozen-lockfile + env: + CI: true - name: Run lint - run: | - pnpm install --frozen-lockfile - pnpm lint + run: pnpm lint env: CI: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 04232ffb..0ebc246d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -18,7 +18,14 @@ jobs: with: node-version: v22.15.1 cache: pnpm + - name: Cache node_modules + id: modules-cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Install dependencies + if: steps.modules-cache.outputs.cache-hit != 'true' run: pnpm install --frozen-lockfile - name: Cache Playwright browsers uses: actions/cache@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb1b8fab..dee4fd71 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,9 +17,18 @@ jobs: with: node-version: v22.15.1 cache: pnpm - - name: Web - run: | - pnpm install --frozen-lockfile - pnpm test + - name: Cache node_modules + id: modules-cache + uses: actions/cache@v4 + with: + path: node_modules + key: node-modules-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + - name: Install + if: steps.modules-cache.outputs.cache-hit != 'true' + run: pnpm install --frozen-lockfile + env: + CI: true + - name: Test + run: pnpm test env: CI: true