diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index f4eb8893a..b6466316a 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -10227,8 +10227,10 @@ "description": "Add screenshot tests for UI components", "owner": "frontend", "estimated_hours": 6, - "status": "todo", - "files_involved": [], + "status": "completed", + "files_involved": [ + "apps/web/e2e/visual-regression.spec.ts" + ], "implementation_steps": [ { "step": 1, @@ -10248,7 +10250,14 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completed_at": "2025-12-25T18:45:00.281678", + "validation": { + "typescript_compilation": "No errors in new file", + "linter": "No linting errors", + "test_file_created": "e2e/visual-regression.spec.ts", + "coverage": "Visual regression tests for pages and components" + } }, { "id": "FE-TEST-015", diff --git a/apps/web/e2e/visual-regression.spec.ts b/apps/web/e2e/visual-regression.spec.ts new file mode 100644 index 000000000..d7f13354f --- /dev/null +++ b/apps/web/e2e/visual-regression.spec.ts @@ -0,0 +1,282 @@ +import { test, expect } from '@playwright/test'; +import { loginAsUser, TEST_CONFIG } from './utils/test-helpers'; + +/** + * Visual Regression Tests + * + * These tests capture screenshots of UI components and pages + * to detect visual regressions. Screenshots are stored in: + * - test-results/visual-regression.spec.ts-snapshots/ + * + * To update screenshots after intentional changes: + * - Run: npx playwright test --update-snapshots + * + * To run only visual tests: + * - Run: npx playwright test visual-regression + */ + +test.describe('Visual Regression Tests', () => { + // Use authenticated state for most tests + test.use({ storageState: 'e2e/.auth/user.json' }); + + test.describe('Authentication Pages', () => { + test('login page visual snapshot', async ({ page }) => { + // Use unauthenticated state for login page + await page.context().clearCookies(); + + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`); + await page.waitForLoadState('networkidle'); + + // Wait for form to be fully rendered + await page.waitForSelector('form', { timeout: 5000 }); + await page.waitForTimeout(500); // Allow animations to settle + + await expect(page).toHaveScreenshot('login-page.png', { + fullPage: true, + maxDiffPixels: 100, // Allow small differences (fonts, anti-aliasing) + }); + }); + + test('register page visual snapshot', async ({ page }) => { + // Use unauthenticated state for register page + await page.context().clearCookies(); + + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`); + await page.waitForLoadState('networkidle'); + + // Wait for form to be fully rendered + await page.waitForSelector('form', { timeout: 5000 }); + await page.waitForTimeout(500); + + await expect(page).toHaveScreenshot('register-page.png', { + fullPage: true, + maxDiffPixels: 100, + }); + }); + }); + + test.describe('Dashboard Pages', () => { + test('dashboard page visual snapshot', async ({ page }) => { + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); + await page.waitForLoadState('networkidle'); + + // Wait for main content to load + await page.waitForSelector('main, [role="main"]', { timeout: 10000 }); + await page.waitForTimeout(1000); // Allow data to load + + await expect(page).toHaveScreenshot('dashboard-page.png', { + fullPage: true, + maxDiffPixels: 200, + }); + }); + + test('dashboard header visual snapshot', async ({ page }) => { + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); + await page.waitForLoadState('networkidle'); + + // Wait for header + const header = page.locator('header').first(); + await header.waitFor({ timeout: 5000 }); + await page.waitForTimeout(500); + + await expect(header).toHaveScreenshot('dashboard-header.png', { + maxDiffPixels: 50, + }); + }); + + test('dashboard sidebar visual snapshot', async ({ page }) => { + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`); + await page.waitForLoadState('networkidle'); + + // Wait for sidebar + const sidebar = page.locator('aside').first(); + await sidebar.waitFor({ timeout: 5000 }); + await page.waitForTimeout(500); + + await expect(sidebar).toHaveScreenshot('dashboard-sidebar.png', { + maxDiffPixels: 50, + }); + }); + }); + + test.describe('Profile Page', () => { + test('profile page visual snapshot', async ({ page }) => { + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/profile`); + await page.waitForLoadState('networkidle'); + + // Wait for profile content + await page.waitForSelector('main, [role="main"]', { timeout: 10000 }); + await page.waitForTimeout(1000); + + await expect(page).toHaveScreenshot('profile-page.png', { + fullPage: true, + maxDiffPixels: 200, + }); + }); + }); + + test.describe('Tracks Pages', () => { + test('tracks list page visual snapshot', async ({ page }) => { + // Navigate to tracks page (adjust route as needed) + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/tracks`); + await page.waitForLoadState('networkidle'); + + // Wait for tracks list to load + await page.waitForTimeout(2000); // Allow tracks to load + + await expect(page).toHaveScreenshot('tracks-list-page.png', { + fullPage: true, + maxDiffPixels: 200, + }); + }); + }); + + test.describe('Playlists Pages', () => { + test('playlists page visual snapshot', async ({ page }) => { + await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`); + await page.waitForLoadState('networkidle'); + + // Wait for playlists to load + await page.waitForTimeout(2000); + + await expect(page).toHaveScreenshot('playlists-page.png', { + fullPage: true, + maxDiffPixels: 200, + }); + }); + }); + + test.describe('UI Components', () => { + test('button variants visual snapshot', async ({ page }) => { + // Create a test page with button variants + await page.setContent(` + + +
+ + + +This is a card component with some content.
+