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) <noreply@anthropic.com>
Three pre-existing infra issues surfaced by the Day 1→Day 3 push wave.
Each is independent — bundled here because the goal is "ci.yml + e2e.yml
green" before the v1.0.9 tag, and they're all small.
(1) gofmt — ci.yml golangci-lint v2 step
Five files were unformatted on main. Pre-existing (untouched by my
Item G work, but the formatter caught them now):
- internal/api/router.go
- internal/core/marketplace/reconcile_hyperswitch_test.go
- internal/models/user.go
- internal/monitoring/ledger_metrics.go
- internal/monitoring/ledger_metrics_test.go
Pure whitespace via `gofmt -w` — no behavior change.
(2) e2e silent-fail — playwright webServer port collision
The e2e workflow pre-starts the backend in step 9 ("Build + start
backend API") so it can fail-fast on a non-ok health check. But
playwright.config.ts had `reuseExistingServer: !process.env.CI` on
the backend webServer entry — meaning in CI Playwright tried to
spawn a SECOND backend on port 18080. The spawn collided with
EADDRINUSE and Playwright silently exited before printing any test
output. The artifact upload then warned "No files were found"
because tests/e2e/playwright-report/ never got written, and the job
ended in `Failure` for an unrelated reason (the artifact upload
step's GHESNotSupportedError).
Fix: backend `reuseExistingServer: true` always — workflow + dev
both pre-start backend on 18080. Vite stays `!CI` because the
workflow doesn't pre-start it. Comment in playwright.config.ts
documents the symptom so the next person debugging gets the
pointer immediately.
(3) orders.hyperswitch_payment_id missing in fresh DBs — migration 080
skip-branch + 099 ordering drift
Migration 080 (`add_payment_fields`) wraps its ALTERs in
"skip if orders doesn't exist". At authoring time orders existed
earlier in the migration sequence; that ordering has since shifted
(orders is now created at 099_z_create_orders.sql, AFTER 080).
Result: in any freshly-migrated DB (CI, fresh dev, future restore
drills) migration 080 takes the skip branch and the columns are
never added — even though the Order model and the marketplace code
rely on them.
Symptom: every CI run logs
pq: column "hyperswitch_payment_id" does not exist
from the periodic ledger_metrics worker. Order checkout would also
fail to persist payment_id at write time, breaking reconciliation.
Fix: append-only migration 987 with idempotent
`ADD COLUMN IF NOT EXISTS` + a partial index on the reconciliation
hot path. Production envs that did pick up 080 in the original
order are no-ops; fresh envs converge to the same end state.
Rollback in migrations/rollback/.
Verified locally:
$ cd veza-backend-api && go build ./... && VEZA_SKIP_INTEGRATION=1 \
go test -short -count=1 ./internal/...
(all green)
SKIP_TESTS=1: backend-only Go + Playwright config + SQL. Frontend
unit tests irrelevant to this commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prep for the upcoming E2E Playwright CI workflow (Batch C). When the
config flips reuseExistingServer to false in CI, each runner spawns a
dedicated backend + Vite dev server with the test-mode env vars
(APP_ENV=test, DISABLE_RATE_LIMIT_FOR_TESTS=true, etc.) instead of
piggy-backing on whatever happened to be listening on 18080/5173.
Local dev keeps reuseExistingServer=true so engineers retain the fast
turnaround when the dev stack is already up via `make dev`.
CI flag follows the standard convention (process.env.CI is set by
GitHub / Forgejo Actions automatically). No behaviour change for the
default `npm run e2e` invocation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Set RATE_LIMIT_LIMIT=10000 and RATE_LIMIT_WINDOW=60 so that the
backend started by Playwright doesn't throttle test traffic.
Must be combined with 'make dev-e2e' when running tests against
an already-running backend (reuseExistingServer=true means
Playwright won't restart the backend if one is already on :18080).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause analysis via Playwright MCP snapshots revealed that all
35 remaining E2E failures were timing issues, not real app bugs.
Every tested element (Notifications bell, Settings tabs, Search
combobox, Discover genres, Marketplace products, Social tabs) renders
correctly — but the 5s expect timeout was too short for React SPA
hydration.
Changes:
- Increase expect timeout from 5s to 10s in playwright.config.ts
- Fix avatar selector: add img[alt="username"] fallback (no "avatar" class)
- Fix profile edit test: /profile/edit doesn't exist, fields are on /settings
- Fix language selector: handle hidden input from custom Select component
- Fix GoLive regex: include "stream configuration" and "obs" alternatives
- Fix analytics period: match button text "7d" exactly
- Add 10s timeouts to critical assertions (discover, marketplace headings)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>