From 4ccf7cd16d1bb3d59348a56e12d3a033e7fc3359 Mon Sep 17 00:00:00 2001 From: Abdu Alim Arlikhozhaev Date: Sat, 11 Jul 2026 21:12:52 -0700 Subject: [PATCH 1/3] ci: add Playwright e2e job with trace upload on failure (RL-008) --- .github/workflows/ci.yml | 65 ++++++++++++++++++++++++++++++++++++---- README.md | 8 ++--- playwright.config.ts | 21 +++++++++---- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 371cfe3..5b0c133 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,14 +10,15 @@ concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + DATABASE_URL: postgresql://ci:ci@localhost:5432/reviewlens_ci + OPENAI_API_KEY: sk-ci-placeholder-key-for-build-only + AUTH_SECRET: ci-only-auth-secret-min-32-characters-long + NEXT_PUBLIC_APP_URL: http://localhost:3000 + jobs: verify: runs-on: ubuntu-latest - env: - DATABASE_URL: postgresql://ci:ci@localhost:5432/reviewlens_ci - OPENAI_API_KEY: sk-ci-placeholder-key-for-build-only - AUTH_SECRET: ci-only-auth-secret-min-32-characters-long - NEXT_PUBLIC_APP_URL: http://localhost:3000 steps: - uses: actions/checkout@v4 @@ -44,3 +45,57 @@ jobs: - name: Production build run: npm run build + + e2e: + runs-on: ubuntu-latest + needs: verify + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: ci + POSTGRES_PASSWORD: ci + POSTGRES_DB: reviewlens_ci + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Generate Prisma client + run: npx prisma generate + + - name: Apply database migrations + run: npx prisma migrate deploy + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Run E2E tests + run: npm run test:e2e + env: + CI: true + + - name: Upload Playwright trace on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: playwright-trace-${{ github.run_id }} + path: | + test-results/ + playwright-report/ + retention-days: 7 diff --git a/README.md b/README.md index a7ba6bd..1e742af 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ | Reviews per upload | **500+** supported | | Automated unit tests | **93** (Vitest — no DB/network in CI) | | E2E specs | **4** (Playwright — auth gating + golden path) | -| CI gates on every merge | **Lint · type-check · test · production build** | +| CI gates on every merge | **Lint · type-check · test · build · Playwright e2e** | | AI pipeline stages | **4** (embed → cluster → summarize → executive summary) | | Share protection modes | **Password + expiry** (scrypt + HMAC cookie) | | Export formats | **PDF · summary CSV · raw reviews CSV** | @@ -209,10 +209,8 @@ npm run test:e2e # 4 Playwright specs (golden path + auth gating) **CI** (`.github/workflows/ci.yml`) on every push/PR to `main`: -1. ESLint -2. `tsc --noEmit` -3. Vitest (no live DB or network) -4. Production build +1. **verify** — ESLint, `tsc --noEmit`, Vitest (no live DB/network), production build +2. **e2e** — Postgres service + `prisma migrate deploy`, Playwright (Chromium) with placeholder env; API routes mocked in specs. Trace + HTML report uploaded on failure. **E2E setup:** diff --git a/playwright.config.ts b/playwright.config.ts index 35c23c4..be5f784 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,6 +2,7 @@ import { defineConfig, devices } from "@playwright/test"; const PORT = 3000; const baseURL = `http://localhost:${PORT}`; +const isCI = Boolean(process.env.CI); // Fixed secret so the generated auth cookie (auth.setup.ts) matches what the // dev server verifies. Overrides .env.local because real process.env takes @@ -12,12 +13,17 @@ const AUTH_SECRET = export default defineConfig({ testDir: "./e2e", fullyParallel: true, - forbidOnly: Boolean(process.env.CI), - retries: process.env.CI ? 2 : 0, - reporter: "list", + forbidOnly: isCI, + retries: isCI ? 2 : 0, + reporter: isCI + ? [ + ["list"], + ["html", { open: "never", outputFolder: "playwright-report" }], + ] + : "list", use: { baseURL, - trace: "on-first-retry", + trace: isCI ? "retain-on-failure" : "on-first-retry", }, projects: [ { name: "setup", testMatch: /auth\.setup\.ts/ }, @@ -39,12 +45,17 @@ export default defineConfig({ webServer: { command: "npm run dev", url: baseURL, - reuseExistingServer: !process.env.CI, + reuseExistingServer: !isCI, timeout: 120_000, env: { AUTH_SECRET, AUTH_URL: baseURL, NEXT_PUBLIC_APP_URL: baseURL, + DATABASE_URL: + process.env.DATABASE_URL ?? + "postgresql://ci:ci@localhost:5432/reviewlens_ci", + OPENAI_API_KEY: + process.env.OPENAI_API_KEY ?? "sk-ci-placeholder-key-for-build-only", }, }, }); From 40dd1b5d0f4d588e445460004d607e0f3ae01ace Mon Sep 17 00:00:00 2001 From: Abdu Alim Arlikhozhaev Date: Sat, 11 Jul 2026 21:18:52 -0700 Subject: [PATCH 2/3] fix(ci): set DIRECT_URL for prisma migrate deploy in e2e job --- .github/workflows/ci.yml | 1 + playwright.config.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b0c133..45e0fae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,7 @@ concurrency: env: DATABASE_URL: postgresql://ci:ci@localhost:5432/reviewlens_ci + DIRECT_URL: postgresql://ci:ci@localhost:5432/reviewlens_ci OPENAI_API_KEY: sk-ci-placeholder-key-for-build-only AUTH_SECRET: ci-only-auth-secret-min-32-characters-long NEXT_PUBLIC_APP_URL: http://localhost:3000 diff --git a/playwright.config.ts b/playwright.config.ts index be5f784..b89aaf1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -54,6 +54,9 @@ export default defineConfig({ DATABASE_URL: process.env.DATABASE_URL ?? "postgresql://ci:ci@localhost:5432/reviewlens_ci", + DIRECT_URL: + process.env.DIRECT_URL ?? + "postgresql://ci:ci@localhost:5432/reviewlens_ci", OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "sk-ci-placeholder-key-for-build-only", }, From 8fbcdff3e90a709cdf89f8189227a81115b8cad7 Mon Sep 17 00:00:00 2001 From: Abdu Alim Arlikhozhaev Date: Sat, 11 Jul 2026 21:30:40 -0700 Subject: [PATCH 3/3] fix(e2e): assert reviews ready text and mock status in golden path --- README.md | 2 +- e2e/golden-path.spec.ts | 25 +++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e742af..b28b020 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ npm run test:e2e ``` - `auth.setup.ts` — signed session cookie for authed specs -- `golden-path.spec.ts` — upload → preview → submit +- `golden-path.spec.ts` — upload → preview → submit (mocks create/process/status APIs) - `auth-redirect.spec.ts` — middleware gating without DB --- diff --git a/e2e/golden-path.spec.ts b/e2e/golden-path.spec.ts index 3930556..8d97663 100644 --- a/e2e/golden-path.spec.ts +++ b/e2e/golden-path.spec.ts @@ -47,6 +47,25 @@ test.describe("golden path: upload → preview → submit", () => { }); }); + await page.route("**/api/analysis/*/status", async (route) => { + if (route.request().method() !== "GET") { + await route.fallback(); + return; + } + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + success: true, + data: { + status: "PROCESSING", + totalReviews: 6, + isStale: false, + }, + }), + }); + }); + await page.goto("/analyze"); // Upload the CSV via the (possibly hidden) file input. @@ -58,8 +77,10 @@ test.describe("golden path: upload → preview → submit", () => { buffer: Buffer.from(CSV), }); - // Client-side parse advances to the preview step. - await expect(page.getByText(/parsed successfully/i)).toBeVisible(); + // Client-side parse advances to the preview step (Step 2 · Verify). + await expect(page.getByText(/reviews ready/i)).toBeVisible({ + timeout: 15_000, + }); // Submit kicks off analysis and routes to the dashboard. await page.getByRole("button", { name: /start ai analysis/i }).click();