veza/apps/web/e2e/visual-regression.spec.ts

282 lines
9.6 KiB
TypeScript

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(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/src/index.css">
</head>
<body style="padding: 20px; background: white;">
<div style="display: flex; flex-direction: column; gap: 10px;">
<button class="bg-primary text-white px-4 py-2 rounded">Primary Button</button>
<button class="bg-secondary text-white px-4 py-2 rounded">Secondary Button</button>
<button class="border px-4 py-2 rounded">Outline Button</button>
<button class="bg-destructive text-white px-4 py-2 rounded">Destructive Button</button>
<button class="bg-primary text-white px-4 py-2 rounded" disabled>Disabled Button</button>
</div>
</body>
</html>
`);
await page.waitForTimeout(500);
await expect(page).toHaveScreenshot('button-variants.png', {
maxDiffPixels: 50,
});
});
test('card component visual snapshot', async ({ page }) => {
await page.setContent(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/src/index.css">
</head>
<body style="padding: 20px; background: white;">
<div class="rounded-lg border bg-card shadow-sm p-6" style="max-width: 400px;">
<h3 class="text-lg font-semibold mb-2">Card Title</h3>
<p class="text-muted-foreground">This is a card component with some content.</p>
</div>
</body>
</html>
`);
await page.waitForTimeout(500);
await expect(page).toHaveScreenshot('card-component.png', {
maxDiffPixels: 50,
});
});
test('form elements visual snapshot', async ({ page }) => {
await page.setContent(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/src/index.css">
</head>
<body style="padding: 20px; background: white;">
<form style="max-width: 400px; display: flex; flex-direction: column; gap: 15px;">
<div>
<label class="block mb-1">Text Input</label>
<input type="text" class="w-full px-3 py-2 border rounded" placeholder="Enter text">
</div>
<div>
<label class="block mb-1">Email Input</label>
<input type="email" class="w-full px-3 py-2 border rounded" placeholder="email@example.com">
</div>
<div>
<label class="block mb-1">Password Input</label>
<input type="password" class="w-full px-3 py-2 border rounded" placeholder="Password">
</div>
<div>
<label class="block mb-1">Textarea</label>
<textarea class="w-full px-3 py-2 border rounded" rows="3" placeholder="Enter message"></textarea>
</div>
<button type="submit" class="bg-primary text-white px-4 py-2 rounded">Submit</button>
</form>
</body>
</html>
`);
await page.waitForTimeout(500);
await expect(page).toHaveScreenshot('form-elements.png', {
maxDiffPixels: 50,
});
});
});
test.describe('Error States', () => {
test('404 page visual snapshot', async ({ page }) => {
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-page`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
await expect(page).toHaveScreenshot('404-page.png', {
fullPage: true,
maxDiffPixels: 100,
});
});
});
test.describe('Responsive Design', () => {
test('mobile viewport dashboard snapshot', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
await expect(page).toHaveScreenshot('dashboard-mobile.png', {
fullPage: true,
maxDiffPixels: 200,
});
});
test('tablet viewport dashboard snapshot', async ({ page }) => {
// Set tablet viewport
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
await expect(page).toHaveScreenshot('dashboard-tablet.png', {
fullPage: true,
maxDiffPixels: 200,
});
});
});
});