- Dark palette: background 0.11, card 0.14, sidebar 0.08 (Spotify #121212 feel) - MiniPlayer: h-16, thinner progress bar, compact cover/times, rounded actions - Sidebar: minimal header, lighter section labels, clean nav hover (no heavy border) - Header: h-16, rounded search pill, compact user pill and dropdown - Dashboard: pt-20 to match header, StatCard typography and spacing tweaks - Visual: register-page snapshot + small maxDiffPixels tolerance for font variance Co-authored-by: Cursor <cursoragent@cursor.com>
167 lines
6.1 KiB
TypeScript
167 lines
6.1 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { TEST_CONFIG } from '../../utils/test-helpers';
|
|
|
|
/** Pixel-perfect visual regression: strict by default. Relax in CI if needed via VISUAL_MAX_DIFF_PIXELS. */
|
|
const MAX_DIFF_PIXELS = process.env.VISUAL_MAX_DIFF_PIXELS ? parseInt(process.env.VISUAL_MAX_DIFF_PIXELS, 10) : 0;
|
|
const ANIMATION_SETTLE_MS = 800;
|
|
|
|
async function ensureDarkTheme(page: import('@playwright/test').Page) {
|
|
await page.evaluate(() => {
|
|
document.documentElement.classList.add('dark');
|
|
document.documentElement.setAttribute('data-theme', 'dark');
|
|
});
|
|
await page.waitForTimeout(100);
|
|
}
|
|
|
|
test.describe('Visual regression (pixel-perfect)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
});
|
|
|
|
test.describe('Auth pages (no storage)', () => {
|
|
test.use({ storageState: { cookies: [], origins: [] } });
|
|
|
|
test('login page', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/login`);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForSelector('form', { timeout: 10000 });
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(page).toHaveScreenshot('login-page.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
|
|
test('register page', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/register`);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForSelector('form, [role="form"], input[type="email"]', { timeout: 15000 }).catch(() => {});
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(page).toHaveScreenshot('register-page.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: Math.max(MAX_DIFF_PIXELS, 10), // allow minor font/subpixel variance
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('App shell (authenticated)', () => {
|
|
test('dashboard full page', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForSelector('main, [role="main"]', { timeout: 15000 });
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(page).toHaveScreenshot('dashboard-full.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
|
|
test('dashboard header only', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
const header = page.locator('header').first();
|
|
await header.waitFor({ timeout: 10000 });
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(header).toHaveScreenshot('dashboard-header.png', {
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
|
|
test('dashboard sidebar only', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
const sidebar = page.locator('aside').first();
|
|
const visible = await sidebar.waitFor({ state: 'visible', timeout: 12000 }).then(() => true).catch(() => false);
|
|
if (!visible) {
|
|
test.skip(true, 'Sidebar not visible (e.g. not authenticated or mobile layout)');
|
|
return;
|
|
}
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(sidebar).toHaveScreenshot('dashboard-sidebar.png', {
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
|
|
test('global player bar', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
const playerBar = page.locator('div.fixed.bottom-0.left-0.right-0').first();
|
|
await playerBar.waitFor({ state: 'visible', timeout: 5000 }).catch(() => {});
|
|
if ((await playerBar.count()) === 0) {
|
|
test.skip();
|
|
return;
|
|
}
|
|
await expect(playerBar).toHaveScreenshot('player-bar.png', {
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('Key routes', () => {
|
|
test('playlists page', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/playlists`);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForSelector('main, [role="main"]', { timeout: 10000 }).catch(() => {});
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(page).toHaveScreenshot('playlists-page.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
|
|
test('404 page', async ({ page }) => {
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/non-existent-route-404`);
|
|
await page.waitForLoadState('networkidle');
|
|
await ensureDarkTheme(page);
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
|
|
await expect(page).toHaveScreenshot('404-page.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('Viewports', () => {
|
|
test('dashboard mobile 375x667', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
await ensureDarkTheme(page);
|
|
|
|
await expect(page).toHaveScreenshot('dashboard-mobile.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
|
|
test('dashboard tablet 768x1024', async ({ page }) => {
|
|
await page.setViewportSize({ width: 768, height: 1024 });
|
|
await page.goto(`${TEST_CONFIG.FRONTEND_URL}/dashboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
await page.waitForTimeout(ANIMATION_SETTLE_MS);
|
|
await ensureDarkTheme(page);
|
|
|
|
await expect(page).toHaveScreenshot('dashboard-tablet.png', {
|
|
fullPage: true,
|
|
maxDiffPixels: MAX_DIFF_PIXELS,
|
|
});
|
|
});
|
|
});
|
|
});
|