diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..f5d8d00a --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "chrome-devtools": { + "command": "npx", + "args": ["chrome-devtools-mcp@latest"] + } + } +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15fc2768..dc987658 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,4 +34,30 @@ jobs: - run: npm ci - run: npx playwright install --with-deps chromium - run: npm run build - - run: npm run test:e2e + - name: Run E2E tests (functional only) + run: npx playwright test --grep-invert "Visual Regression" + + visual-regression: + runs-on: ubuntu-latest + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} + NEXT_PUBLIC_CHAIN_ID: "8453" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npx playwright install --with-deps chromium + - run: npm run build + - name: Run visual regression + run: npx playwright test e2e/visual-regression.spec.ts --update-snapshots + - name: Upload snapshots + if: always() + uses: actions/upload-artifact@v4 + with: + name: visual-regression-snapshots + path: e2e/__snapshots__/ + retention-days: 30 diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml new file mode 100644 index 00000000..fd9dc5c6 --- /dev/null +++ b/.github/workflows/update-snapshots.yml @@ -0,0 +1,29 @@ +name: Update Visual Snapshots + +on: + workflow_dispatch: + +jobs: + update-snapshots: + runs-on: ubuntu-latest + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} + NEXT_PUBLIC_CHAIN_ID: "8453" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - run: npm ci + - run: npx playwright install --with-deps chromium + - run: npm run build + - name: Generate snapshots + run: npx playwright test e2e/visual-regression.spec.ts --update-snapshots + - name: Upload snapshots + uses: actions/upload-artifact@v4 + with: + name: visual-snapshots-linux + path: e2e/__snapshots__/ + retention-days: 30 diff --git a/e2e/visual-regression.spec.ts b/e2e/visual-regression.spec.ts new file mode 100644 index 00000000..594a4143 --- /dev/null +++ b/e2e/visual-regression.spec.ts @@ -0,0 +1,103 @@ +import { test, expect } from "@playwright/test"; + +/** + * Visual regression tests — screenshot key pages at desktop and mobile viewports. + * Baselines stored in e2e/__snapshots__/. + * Update with: npx playwright test e2e/visual-regression.spec.ts --update-snapshots + */ + +const DESKTOP = { width: 1280, height: 720 }; +const MOBILE = { width: 375, height: 812 }; + +test.describe("Visual Regression — Desktop", () => { + test.beforeEach(async ({ page }) => { + await page.setViewportSize(DESKTOP); + }); + + test("Home page", async ({ page }) => { + await page.goto("/"); + // Wait for content to settle + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("home-desktop.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); + + test("Create page", async ({ page }) => { + await page.goto("/create"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("create-desktop.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); + + test("Token page", async ({ page }) => { + await page.goto("/token"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("token-desktop.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); + + test("Agents page", async ({ page }) => { + await page.goto("/agents"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("agents-desktop.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); +}); + +test.describe("Visual Regression — Mobile", () => { + test.beforeEach(async ({ page }) => { + await page.setViewportSize(MOBILE); + }); + + test("Home page", async ({ page }) => { + await page.goto("/"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("home-mobile.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); + + test("Create page", async ({ page }) => { + await page.goto("/create"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("create-mobile.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); + + test("Token page", async ({ page }) => { + await page.goto("/token"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("token-mobile.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); + + test("Agents page", async ({ page }) => { + await page.goto("/agents"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + await expect(page).toHaveScreenshot("agents-mobile.png", { + maxDiffPixelRatio: 0.01, + fullPage: true, + }); + }); +}); diff --git a/playwright.config.ts b/playwright.config.ts index 9b2f8114..bb3d9485 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "@playwright/test"; export default defineConfig({ testDir: "./e2e", + snapshotPathTemplate: "{testDir}/__snapshots__/{testFileName}/{arg}{ext}", fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0,