diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index f3348bf..2a29c40 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -66,7 +66,7 @@ jobs: - name: Server log on failure if: failure() run: cat /tmp/server.log || true - - run: npx playwright test --ignore-snapshots + - run: npx playwright test - uses: actions/upload-artifact@v3 if: failure() with: @@ -88,59 +88,3 @@ jobs: -H 'Content-type: application/json' \ --data "{\"text\":\":x: *Ghost Guild CI failed* on \`${{ github.ref_name }}\`\nCommit: ${{ github.sha }}\n${{ github.server_url }}/${{ github.repository }}/actions\"}" - visual: - runs-on: ubuntu-latest - needs: vitest - continue-on-error: true - env: - MONGODB_URI: mongodb://mongo-ci:27017/ghostguild-test - JWT_SECRET: ci-test-jwt-secret - RESEND_API_KEY: re_ci_dummy_not_used - HELCIM_API_TOKEN: helcim_ci_dummy_not_used - OIDC_COOKIE_SECRET: ci-oidc-cookie-secret-not-secret - NUXT_PUBLIC_COMING_SOON: 'false' - NODE_ENV: development - ALLOW_DEV_TEST_ENDPOINTS: 'true' - BASE_URL: http://localhost:3000 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: npm - - run: npm ci - - run: npx playwright install --with-deps chromium - - name: Start MongoDB - run: | - docker rm -f mongo-ci 2>/dev/null || true - docker run -d --name mongo-ci mongo:7 - # Forgejo runs each job inside its own container; attach Mongo to - # that container's network so MONGODB_URI=mongodb://mongo-ci:27017 - # resolves from inside the runner. - RUNNER_NET=$(docker inspect "$HOSTNAME" --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' | awk '{print $1}') - docker network connect "$RUNNER_NET" mongo-ci - docker ps - - name: Wait for MongoDB - run: timeout 30 sh -c 'until docker exec mongo-ci mongosh --quiet --eval "1" >/dev/null 2>&1; do sleep 1; done' - - name: MongoDB log on failure - if: failure() - run: docker logs mongo-ci || true - - name: Seed test data - run: node scripts/seed-all.js && node scripts/seed-tags.js - - run: npm run build - - name: Start server - run: node .output/server/index.mjs > /tmp/server.log 2>&1 & - env: - PORT: 3000 - - name: Wait for server - run: timeout 30 sh -c 'until curl -sf http://localhost:3000; do sleep 1; done' - - name: Server log on failure - if: failure() - run: cat /tmp/server.log || true - - run: npx playwright test e2e/visual/ - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: visual-diffs - path: e2e/test-results/ - retention-days: 7 diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium.png deleted file mode 100644 index 83d66d3..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium.png deleted file mode 100644 index df45026..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-auth-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium.png deleted file mode 100644 index 76301e1..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/about-mobile-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium.png deleted file mode 100644 index b2a406b..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-dashboard-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-events-create-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-events-create-desktop-chromium.png deleted file mode 100644 index a1671b1..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-events-create-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium.png deleted file mode 100644 index d13df4d..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/admin-members-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium.png deleted file mode 100644 index e6e2013..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium.png deleted file mode 100644 index d4b7b0e..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/coming-soon-mobile-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium.png deleted file mode 100644 index c22603b..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium.png deleted file mode 100644 index 33d7560..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/connections-mobile-auth-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium.png deleted file mode 100644 index dd3ef64..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium.png deleted file mode 100644 index e44b219..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/events-mobile-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium.png deleted file mode 100644 index 264d678..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium.png deleted file mode 100644 index 3a29d00..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/home-mobile-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium.png deleted file mode 100644 index 1adeaf6..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium.png deleted file mode 100644 index 21e87d6..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/join-mobile-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium.png deleted file mode 100644 index 4c3459d..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-mobile-auth-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-mobile-auth-chromium.png deleted file mode 100644 index c0c6e99..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-account-mobile-auth-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-activity-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-activity-desktop-chromium.png deleted file mode 100644 index c3d2e31..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-activity-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium.png deleted file mode 100644 index 799c70e..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium.png deleted file mode 100644 index 9aa23dd..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-dashboard-mobile-auth-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium.png deleted file mode 100644 index a810ac5..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium.png deleted file mode 100644 index da76ae2..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/member-profile-mobile-auth-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium.png deleted file mode 100644 index 2319a2d..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium.png deleted file mode 100644 index 9045113..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-detail-desktop-chromium.png and /dev/null differ diff --git a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium.png b/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium.png deleted file mode 100644 index 21e87d6..0000000 Binary files a/e2e/__screenshots__/visual/pages.spec.js-snapshots/members-mobile-chromium.png and /dev/null differ diff --git a/e2e/visual/pages.spec.js b/e2e/visual/pages.spec.js deleted file mode 100644 index 24062e7..0000000 --- a/e2e/visual/pages.spec.js +++ /dev/null @@ -1,180 +0,0 @@ -import { test, expect } from '@playwright/test' -import { loginAsAdmin } from '../helpers/auth.js' -import path from 'path' -import fs from 'fs' - -const viewports = { - desktop: { width: 1280, height: 720 }, - mobile: { width: 375, height: 667 }, -} - -const publicPages = [ - { name: 'home', path: '/' }, - { name: 'join', path: '/join' }, - { name: 'events', path: '/events' }, - { name: 'coming-soon', path: '/coming-soon' }, - // about and members have no auth middleware — accessible publicly - { name: 'about', path: '/about' }, - { name: 'members', path: '/members' }, -] - -const authenticatedPages = [ - { name: 'member-dashboard', path: '/member/dashboard' }, - { name: 'member-profile', path: '/member/profile' }, - { name: 'admin-members', path: '/admin/members' }, - { name: 'admin-events-create', path: '/admin/events/create' }, - // New authenticated pages - { name: 'member-account', path: '/member/account' }, - { name: 'connections', path: '/connections' }, - { name: 'admin-dashboard', path: '/admin' }, -] - -// Pages that need mobile coverage captured while authenticated. -// These cover column-collapse breakpoints critical for the page-shell refactor. -// Snapshots use the -mobile-auth suffix to distinguish from the public mobile loop -// (which also captures about-mobile unauthenticated, so names must not collide). -const authenticatedMobilePages = [ - { name: 'about', path: '/about' }, - { name: 'member-dashboard', path: '/member/dashboard' }, - { name: 'member-profile', path: '/member/profile' }, - { name: 'member-account', path: '/member/account' }, - { name: 'connections', path: '/connections' }, -] - -// Path where the saved admin auth state (cookies) will be stored within a run. -const authStatePath = path.resolve('e2e/.auth/admin.json') - -// Wait for fonts and images to load before taking screenshots -async function waitForStable(page) { - await page.waitForLoadState('networkidle') - // Wait for web fonts to load - await page.evaluate(() => document.fonts.ready) -} - -// Common mask selectors for dynamic content -function commonMasks(page) { - return [ - // Dates and times throughout the app - page.locator('.event-date'), - page.locator('.event-count'), - page.locator('time'), - page.locator('.member-since'), - // Activity log timestamps - page.locator('.tl-time'), - // Admin dashboard stat values (member counts, revenue, etc.) - page.locator('.stat-val'), - // Recent member join dates in admin dashboard - page.locator('.item-date'), - // Member avatars (ghost images may not load deterministically) - page.locator('.mc-avatar'), - page.locator('.cc-avatar'), - page.locator('.profile-avatar'), - // Member count text in members page filter bar - page.locator('.filter-count'), - // Connections page: filter bar and suggestions vary based on tag/topic - // state and async fetch ordering. Mask them to keep the structural - // (PageShell + page-level) regression coverage stable. - page.locator('.filter-bar'), - page.locator('.skills-bar'), - page.locator('.connections-section'), - page.locator('.loading-state'), - ] -} - -// All visual tests run serially in a single top-level describe block. -// -// Auth is handled with a beforeAll that saves the cookie to disk once. All -// authenticated sub-describes load from that saved state, avoiding repeated -// /api/dev/test-login calls that exhaust the dev server's MongoDB connections. -test.describe('visual regression', () => { - test.describe.configure({ mode: 'serial' }) - - // Log in once before all tests and save the auth cookie. - // serial mode guarantees this runs before any test in this describe tree. - test.beforeAll(async ({ browser }) => { - fs.mkdirSync(path.dirname(authStatePath), { recursive: true }) - const page = await browser.newPage() - await loginAsAdmin(page) - await page.context().storageState({ path: authStatePath }) - await page.close() - }) - - // ── Public pages (desktop + mobile) ────────────────────────────────────── - test.describe('public pages', () => { - for (const { name, path } of publicPages) { - for (const [viewportName, viewport] of Object.entries(viewports)) { - test(`${name} — ${viewportName}`, async ({ page }) => { - await page.setViewportSize(viewport) - await page.goto(path) - await waitForStable(page) - - await expect(page).toHaveScreenshot(`${name}-${viewportName}.png`, { - maxDiffPixelRatio: 0.01, - mask: commonMasks(page), - }) - }) - } - } - }) - - // ── Authenticated pages (desktop) ───────────────────────────────────────── - // Loads saved auth cookie — no repeated /api/dev/test-login calls. - test.describe('authenticated pages', () => { - test.use({ storageState: authStatePath }) - - for (const { name, path } of authenticatedPages) { - test(`${name} — desktop`, async ({ page }) => { - await page.setViewportSize(viewports.desktop) - await page.goto(path) - await waitForStable(page) - - await expect(page).toHaveScreenshot(`${name}-desktop.png`, { - maxDiffPixelRatio: 0.01, - mask: commonMasks(page), - }) - }) - } - - // members-detail: navigate to the test admin's own profile page. - // The test admin is created by /api/dev/test-login (email: test-admin@ghostguild.dev, - // status: active). We fetch their _id from /api/auth/member using the saved cookie. - // Even if showInDirectory is false, the page renders a stable error or profile shell. - test('members-detail — desktop', async ({ page }) => { - await page.setViewportSize(viewports.desktop) - const response = await page.request.get('/api/auth/member') - // /api/auth/member returns the member object directly (not nested under a 'member' key) - const authData = response.ok() ? await response.json() : null - const memberId = authData?._id || authData?.id - if (!memberId) { - // Skip gracefully if we can't retrieve the member ID - test.skip(true, 'Could not retrieve test admin member ID from /api/auth/member') - return - } - await page.goto(`/members/${memberId}`) - await waitForStable(page) - await expect(page).toHaveScreenshot('members-detail-desktop.png', { - maxDiffPixelRatio: 0.01, - mask: commonMasks(page), - }) - }) - }) - - // ── Authenticated pages (mobile — column-collapse coverage) ─────────────── - // Loads saved auth cookie — no repeated /api/dev/test-login calls. - test.describe('authenticated pages (mobile)', () => { - test.use({ storageState: authStatePath }) - - for (const { name, path } of authenticatedMobilePages) { - test(`${name} — mobile`, async ({ page }) => { - await page.setViewportSize(viewports.mobile) - await page.goto(path) - await waitForStable(page) - - await expect(page).toHaveScreenshot(`${name}-mobile-auth.png`, { - maxDiffPixelRatio: 0.01, - mask: commonMasks(page), - }) - }) - } - }) -}) diff --git a/playwright.config.js b/playwright.config.js index 0fc414b..70569f9 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -6,8 +6,6 @@ const BASE_URL = `http://localhost:${PORT}`; export default defineConfig({ testDir: "./e2e", outputDir: "e2e/test-results", - snapshotDir: "e2e/__screenshots__", - snapshotPathTemplate: "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}-{projectName}{ext}", fullyParallel: false, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 1,