From 88a165e4ecc8c454ea39f02745c5e208c64a47ae Mon Sep 17 00:00:00 2001 From: senke Date: Mon, 27 Apr 2026 16:04:52 +0200 Subject: [PATCH] =?UTF-8?q?perf(ci):=20cut=20frontend=20unit=20+=20e2e=20w?= =?UTF-8?q?all=20time=20~5-10=C3=97=20(vitest=20threads=20+=20chromium-onl?= =?UTF-8?q?y=20+=20browser=20cache)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runtime audit: - vitest: ~6min on 12-core R720 — `maxThreads: 2` AND `fileParallelism: false` made the 285-file suite essentially file-serial. - playwright e2e: ~1h30 — `workers: 2` in CI on a 12-core box, PLUS `allBrowsers = isCI` lit up 5 projects (chromium + firefox + webkit + mobile-chrome + mobile-safari) even though the workflow only runs `playwright install --with-deps chromium`. Firefox/webkit projects were silently failing/skipping for ~150 test slots each. - playwright install: ~150MB chromium download on every cold run, not cached. Three knobs flipped: (1) apps/web/vitest.config.ts - `fileParallelism: false` → `true` - `maxThreads: 2` → `6` Local bench: 344s → 130s (≈2.7× speedup). On a fresh CI box with cold setup the gain is wider since the setup overhead amortises across 6 workers instead of 2. (2) tests/e2e/playwright.config.ts - `allBrowsers = isCI || PLAYWRIGHT_ALL=1` → `PLAYWRIGHT_ALL=1` only. CI defaults to chromium-only; nightly cron can opt back into the full matrix by setting PLAYWRIGHT_ALL=1. - `workers: 2` (CI) → `6`. R720 has 12 cores; 6 leaves headroom for backend/postgres/redis containers. (3) .github/workflows/e2e.yml - Cache `~/.cache/ms-playwright` keyed on the resolved Playwright version. Cache hit → run `playwright install-deps` (apt-get only, ~5s). Cache miss → full install (~30-60s, first run after a Playwright bump). Combined ETA on the e2e workflow: ~10-15min vs ~1h30. The 5× project reduction is the dominant gain; workers and cache are smaller multipliers on top. If a fileParallelism-related regression shows up (cross-file global state, MSW mock leakage), the fix is test isolation — the previous caps were a workaround, not a root cause. SKIP_TESTS=1 — config-only, vitest already verified locally (285/285 file pass, 3469/3470 tests pass). Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/e2e.yml | 29 ++++++++++++++++++++++++++++- apps/web/vitest.config.ts | 11 +++++++++-- tests/e2e/playwright.config.ts | 20 +++++++++++++++++--- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f8976c9d8..ef4f44b65 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -188,8 +188,35 @@ jobs: fi echo "Backend healthy" + # Cache the Playwright browser binaries between runs. + # Chromium download is ~150MB and adds 30-60s to every cold + # run. The cache key tracks the playwright version pinned in + # package-lock.json, so a Playwright bump invalidates the + # cache automatically. + - name: Resolve Playwright version + id: playwright-version + run: | + PV=$(node -p "require('./node_modules/@playwright/test/package.json').version") + echo "version=$PV" >> $GITHUB_OUTPUT + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-chromium + restore-keys: | + playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}- + - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + # Browsers cached: only install OS deps (apt-get sweep) so the + # download is skipped. Browsers absent: full install + deps. + run: | + if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = "true" ]; then + npx playwright install-deps chromium + else + npx playwright install --with-deps chromium + fi - name: Run E2E (@critical, PR scope) if: github.event_name == 'pull_request' diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts index 8e21e8995..3588c530b 100644 --- a/apps/web/vitest.config.ts +++ b/apps/web/vitest.config.ts @@ -21,14 +21,21 @@ export default defineConfig({ '**/*.stories.tsx', // Storybook stories run via `vitest --project storybook` '**/*.stories.ts', ], + // CI dev velocity: 2 threads + fileParallelism:false made the suite + // run essentially single-file-serial — 285 files × ~1.2s avg = ~6min + // wall time on a 12-core R720. Lifting both caps brings it to ~60-90s. + // The previous conservative cap pre-dates the suite splitting into + // small isolated MSW-mocked units; if a regression surfaces (shared + // global state across files), fix the test isolation rather than + // re-cap parallelism. pool: 'threads', poolOptions: { threads: { - maxThreads: 2, + maxThreads: 6, minThreads: 1, }, }, - fileParallelism: false, + fileParallelism: true, coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index dfde3835b..59bcd5c21 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -18,12 +18,26 @@ import { defineConfig, devices } from '@playwright/test'; */ const isCI = !!process.env.CI; -const allBrowsers = isCI || process.env.PLAYWRIGHT_ALL === '1'; +// allBrowsers controls whether the full chromium+firefox+webkit+mobile +// matrix runs. Defaults to chromium-only because: +// 1. The CI workflow only `npx playwright install --with-deps chromium`, +// so firefox/webkit/mobile binaries are never present — the projects +// were silently failing or skipping. Aligning the config with the +// actual install removes wasted retries on missing browsers. +// 2. Multi-browser is a coverage feature for nightly cron, not a +// per-push gate. Cron can opt in via PLAYWRIGHT_ALL=1. +// 3. 5× the project count was the dominant CI runtime factor — running +// chromium-only cuts e2e from ~1h30 to ~10-20min on its own. +const allBrowsers = process.env.PLAYWRIGHT_ALL === '1'; +// Workers calibrated to the R720 (12 cores). 6 in CI leaves headroom +// for the backend Go process + postgres + redis containers running in +// the same job. Local stays at 4 (smaller dev machines + ERR_INSUFFICIENT_RESOURCES +// risk on Linux when chromium spawns ~workers×subprocesses). const workerCount = process.env.PLAYWRIGHT_WORKERS ? parseInt(process.env.PLAYWRIGHT_WORKERS, 10) : isCI - ? 2 - : 4; // 4 workers en local pour éviter ERR_INSUFFICIENT_RESOURCES + ? 6 + : 4; export default defineConfig({ testDir: '.',