From 4778f797810bbaf4bc3008b3da0476701474f92d Mon Sep 17 00:00:00 2001 From: eren23 Date: Sun, 19 Jul 2026 11:12:34 +0300 Subject: [PATCH] ci(e2e): Playwright runs against the mocked stack on every PR (soak phase) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The e2e-mock job was curl-only; the real browser suite sat behind an e2e:run label nobody applies. Now the SAME job — one boot, one build — runs the Playwright suite after its curl smoke, on every PR: - WORLD_MODE=true on the boot (backend flag + the compose build-arg fallthrough bakes NEXT_PUBLIC_WORLD_MODE into the web bundle) so the world/interior specs can exist; classic paths stay exercisable. - pnpm + node + ms-playwright caches; browsers installed per run (~30-40s warm); report + test-results uploaded always; compose logs dumped on failure. - continue-on-error=true is the SOAK: the suite must prove itself green across ~10 unrelated PRs before the one-line flip PR makes browser tests REQUIRED. The curl gate never loosens meanwhile. - playwright.config: E2E_MOCK tightens timeouts (120s/30s vs 240s/60s) so a hang fails in minutes; the label-gated real-key e2e job is untouched. --- .github/workflows/ci.yml | 43 +++++++++++++++++++++++++++++++++++ apps/web/playwright.config.ts | 15 ++++++++---- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc095ef..cd02c05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,11 @@ jobs: - name: Bring up the mocked stack env: MOCK_PROVIDERS: "1" + # World mode ON: the backend flag AND (via the compose build-arg + # fallthrough) the web bundle's NEXT_PUBLIC_WORLD_MODE — the + # world/interior Playwright specs need it. Classic paths stay + # exercisable (per-request world_mode:false + the UI mode toggle). + WORLD_MODE: "true" run: docker compose up -d --build - name: Wait for the web app run: | @@ -154,6 +159,44 @@ jobs: -d '{"query":"a town","aspect_ratio":"16:9","web_search":false,"session_id":"ci_idem","current_node_id":""}') echo "replay status: $code" test "$code" = "409" + # ── The interactive gate (soak phase): the real Playwright suite runs + # against the mocked stack on EVERY PR. continue-on-error while the + # suite proves itself green across ~10 unrelated PRs; then the flip PR + # deletes that line and browser tests become REQUIRED. Deterministic by + # construction (mock providers, serial workers, signal-driven waits). + - uses: pnpm/action-setup@v4 + with: { version: 9.12.0 } + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + - name: Install dependencies + run: pnpm install --frozen-lockfile + - uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }} + - name: Install Playwright browsers + working-directory: apps/web + run: pnpm exec playwright install --with-deps chromium + - name: Playwright against the mocked stack (soak — flip to required after ~10 greens) + continue-on-error: true + working-directory: apps/web + env: + E2E_BASE_URL: http://localhost:3000 + E2E_MOCK: "1" + run: pnpm e2e + - name: Backend/web logs on failure + if: failure() + run: docker compose logs --tail 100 backend web + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-mock-report + path: | + apps/web/playwright-report + apps/web/test-results e2e: # Gated on `e2e:run` label. Like perfbudget, this needs the full stack + diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts index 12ce09c..e6a8c77 100644 --- a/apps/web/playwright.config.ts +++ b/apps/web/playwright.config.ts @@ -1,14 +1,19 @@ import { defineConfig, devices } from "@playwright/test"; const BASE_URL = process.env.E2E_BASE_URL ?? "http://localhost:3000"; +// E2E_MOCK=1: the suite runs against the MOCK_PROVIDERS stack (the every-PR +// CI gate). Generations return in seconds, so a hang should fail in minutes, +// not the quarter-hour a real-model run legitimately needs. +const MOCK = !!process.env.E2E_MOCK; export default defineConfig({ testDir: "./e2e", - // VLM click-resolve (40s) + planner (30s) + image gen (30s) eats most of - // a tap-to-final cycle; a click-to-next test runs that twice. Generous - // timeout reflects what the backend actually costs end-to-end. - timeout: 240_000, - expect: { timeout: 60_000 }, + // Real models: VLM click-resolve (40s) + planner (30s) + image gen (30s) + // eats most of a tap-to-final cycle; a click-to-next test runs that twice. + // Mock: everything is deterministic and near-instant — tight timeouts so + // a real hang surfaces fast. + timeout: MOCK ? 120_000 : 240_000, + expect: { timeout: MOCK ? 30_000 : 60_000 }, fullyParallel: false, workers: 1, forbidOnly: !!process.env.CI,