import { test, expect, Page } from '@playwright/test'; // Test data const TEST_USER = { email: 'alice@example.com', password: 'password123', }; // Helper to capture console errors function setupErrorCapture(page: Page): string[] { const errors: string[] = []; page.on('console', msg => { if (msg.type() === 'error') { errors.push(msg.text()); } }); page.on('pageerror', err => { errors.push(err.message); }); return errors; } // Helper to filter out expected API errors (backend may not be running) function filterCriticalErrors(errors: string[]): string[] { return errors.filter(e => !e.includes('Failed to load resource') && !e.includes('ERR_CONNECTION_REFUSED') && !e.includes('NetworkError') && !e.includes('net::ERR') ); } test.describe('Home Page Loading', () => { test('should load home page without getting stuck in spinner', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/', { waitUntil: 'domcontentloaded' }); // Wait for initial content await page.waitForTimeout(3000); // Check H2H branding is visible await expect(page.locator('header h1:has-text("H2H")')).toBeVisible({ timeout: 5000 }); // Page should show content, not just a spinner // Either shows loading state briefly or actual content const hasContent = await page.locator('main').isVisible() || await page.locator('[class*="container"]').isVisible(); expect(hasContent).toBe(true); // Check for critical errors (not API errors from missing backend) const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); test('should show H2H header branding', async ({ page }) => { await page.goto('/'); await expect(page.locator('header h1:has-text("H2H")')).toBeVisible({ timeout: 5000 }); }); test('should have navigation links including Rewards', async ({ page }) => { await page.goto('/'); // Check Rewards navigation exists (dropdown button) await expect(page.locator('nav button:has-text("Rewards"), nav a:has-text("Rewards")').first()).toBeVisible({ timeout: 5000 }); }); }); test.describe('Rewards Pages Navigation', () => { test('should navigate to /rewards overview page', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/rewards', { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Check page title await expect(page.locator('h1:has-text("Rewards")')).toBeVisible({ timeout: 5000 }); // Check for no critical errors const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); test('should navigate to /rewards/leaderboard page', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/rewards/leaderboard', { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Check page title await expect(page.locator('h1:has-text("Leaderboard")')).toBeVisible({ timeout: 5000 }); const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); test('should navigate to /rewards/achievements page', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/rewards/achievements', { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Check page title await expect(page.locator('h1:has-text("Achievements")')).toBeVisible({ timeout: 5000 }); const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); test('should navigate to /rewards/loot-boxes page', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/rewards/loot-boxes', { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Check page title await expect(page.locator('h1:has-text("Loot Boxes")')).toBeVisible({ timeout: 5000 }); const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); test('should navigate to /rewards/activity page', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/rewards/activity', { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2000); // Check page title await expect(page.locator('h1:has-text("Activity")')).toBeVisible({ timeout: 5000 }); const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); }); test.describe('Header Rewards Dropdown', () => { test('should show Rewards dropdown button in header', async ({ page }) => { await page.goto('/'); // Find the Rewards dropdown button const rewardsButton = page.locator('nav button:has-text("Rewards")').first(); await expect(rewardsButton).toBeVisible({ timeout: 5000 }); }); test('should open dropdown menu when clicking Rewards', async ({ page }) => { await page.goto('/'); // Click Rewards dropdown const rewardsButton = page.locator('nav button:has-text("Rewards")').first(); await rewardsButton.click(); // Check dropdown menu appears with all options await expect(page.locator('a[href="/rewards"]')).toBeVisible({ timeout: 3000 }); await expect(page.locator('a[href="/rewards/leaderboard"]')).toBeVisible({ timeout: 3000 }); await expect(page.locator('a[href="/rewards/achievements"]')).toBeVisible({ timeout: 3000 }); await expect(page.locator('a[href="/rewards/loot-boxes"]')).toBeVisible({ timeout: 3000 }); await expect(page.locator('a[href="/rewards/activity"]')).toBeVisible({ timeout: 3000 }); }); test('should navigate to rewards overview from dropdown', async ({ page }) => { await page.goto('/'); // Click Rewards dropdown await page.locator('nav button:has-text("Rewards")').first().click(); // Click Overview link await page.locator('a[href="/rewards"]:has-text("Overview")').click(); // Verify navigation await expect(page).toHaveURL('/rewards'); await expect(page.locator('h1:has-text("Rewards")')).toBeVisible({ timeout: 5000 }); }); test('should navigate to leaderboard from dropdown', async ({ page }) => { await page.goto('/'); await page.locator('nav button:has-text("Rewards")').first().click(); await page.locator('a[href="/rewards/leaderboard"]').click(); await expect(page).toHaveURL('/rewards/leaderboard'); await expect(page.locator('h1:has-text("Leaderboard")')).toBeVisible({ timeout: 5000 }); }); test('should close dropdown when clicking outside', async ({ page }) => { await page.goto('/'); // Open dropdown await page.locator('nav button:has-text("Rewards")').first().click(); await expect(page.locator('a[href="/rewards/leaderboard"]')).toBeVisible(); // Click outside await page.locator('header h1:has-text("H2H")').click(); // Dropdown should close (link should not be visible in dropdown context) await page.waitForTimeout(500); // The dropdown should be hidden const dropdown = page.locator('div.absolute a[href="/rewards/leaderboard"]'); await expect(dropdown).not.toBeVisible({ timeout: 2000 }); }); }); test.describe('Theme Consistency - Light Theme', () => { test('rewards overview page should have light theme with white backgrounds', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // Check for Header component await expect(page.locator('header')).toBeVisible(); // Check header has white/light background const header = page.locator('header'); await expect(header).toHaveCSS('background-color', 'rgb(255, 255, 255)'); // Check page background is light (gray-50 = rgb(249, 250, 251)) const body = page.locator('div.min-h-screen'); const bgColor = await body.evaluate((el) => window.getComputedStyle(el).backgroundColor); expect(['rgb(249, 250, 251)', 'rgb(248, 250, 252)']).toContain(bgColor); }); test('leaderboard page should have light theme', async ({ page }) => { await page.goto('/rewards/leaderboard'); await page.waitForTimeout(2000); // Check for Header component await expect(page.locator('header')).toBeVisible(); // Page title should be dark text const title = page.locator('h1:has-text("Leaderboard")'); await expect(title).toHaveCSS('color', 'rgb(17, 24, 39)'); // gray-900 }); test('achievements page should have light theme', async ({ page }) => { await page.goto('/rewards/achievements'); await page.waitForTimeout(2000); await expect(page.locator('header')).toBeVisible(); const title = page.locator('h1:has-text("Achievements")'); await expect(title).toHaveCSS('color', 'rgb(17, 24, 39)'); }); test('loot boxes page should have light theme', async ({ page }) => { await page.goto('/rewards/loot-boxes'); await page.waitForTimeout(2000); await expect(page.locator('header')).toBeVisible(); const title = page.locator('h1:has-text("Loot Boxes")'); await expect(title).toHaveCSS('color', 'rgb(17, 24, 39)'); }); test('activity page should have light theme', async ({ page }) => { await page.goto('/rewards/activity'); await page.waitForTimeout(2000); await expect(page.locator('header')).toBeVisible(); const title = page.locator('h1:has-text("Activity")'); await expect(title).toHaveCSS('color', 'rgb(17, 24, 39)'); }); }); test.describe('Authentication Gates', () => { test('achievements page should show "Sign In Required" when not logged in', async ({ page }) => { await page.goto('/rewards/achievements'); await page.waitForTimeout(2000); // Should show sign in required message await expect(page.locator('text=Sign In Required')).toBeVisible({ timeout: 5000 }); // Should have sign in button await expect(page.locator('a[href="/login"]:has-text("Sign In")')).toBeVisible(); }); test('loot boxes page should show "Sign In Required" when not logged in', async ({ page }) => { await page.goto('/rewards/loot-boxes'); await page.waitForTimeout(2000); // Should show sign in required message await expect(page.locator('text=Sign In Required')).toBeVisible({ timeout: 5000 }); // Should have sign in button await expect(page.locator('a[href="/login"]:has-text("Sign In")')).toBeVisible(); }); test('rewards overview should show "Sign In to Track Progress" when not logged in', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // Should show sign in prompt await expect(page.locator('text=Sign In to Track Progress')).toBeVisible({ timeout: 5000 }); }); test('leaderboard page should be accessible without auth', async ({ page }) => { await page.goto('/rewards/leaderboard'); await page.waitForTimeout(2000); // Should NOT show sign in required await expect(page.locator('text=Sign In Required')).not.toBeVisible(); // Should show leaderboard content area await expect(page.locator('h1:has-text("Leaderboard")')).toBeVisible(); }); test('activity page should be accessible without auth', async ({ page }) => { await page.goto('/rewards/activity'); await page.waitForTimeout(2000); // Should NOT show sign in required await expect(page.locator('text=Sign In Required')).not.toBeVisible(); // Should show activity page await expect(page.locator('h1:has-text("Activity")')).toBeVisible(); }); }); test.describe('Gamification Component Rendering', () => { test('Leaderboard component should render on rewards page', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // Should have Leaderboard section with trophy icon/heading await expect(page.locator('h3:has-text("Leaderboard")').first()).toBeVisible({ timeout: 5000 }); }); test('Leaderboard component should have category tabs', async ({ page }) => { await page.goto('/rewards/leaderboard'); await page.waitForTimeout(2000); // Check for category buttons await expect(page.locator('button:has-text("Top Earners")').first()).toBeVisible({ timeout: 5000 }); await expect(page.locator('button:has-text("High Rollers")').first()).toBeVisible(); await expect(page.locator('button:has-text("Most Wins")').first()).toBeVisible(); }); test('WhaleTracker component should render on rewards page', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // WhaleTracker should be visible (may have whale icon or "Whale" text) const whaleSection = page.locator('text=/whale/i').first(); const isVisible = await whaleSection.isVisible().catch(() => false); // May show loading or content - just verify the section exists expect(isVisible || await page.locator('.bg-white.rounded-xl').count() > 0).toBe(true); }); test('rewards overview should have quick links section', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // Quick links to sub-pages await expect(page.locator('a[href="/rewards/leaderboard"]').first()).toBeVisible({ timeout: 5000 }); await expect(page.locator('a[href="/rewards/achievements"]').first()).toBeVisible(); await expect(page.locator('a[href="/rewards/loot-boxes"]').first()).toBeVisible(); await expect(page.locator('a[href="/rewards/activity"]').first()).toBeVisible(); }); test('rewards overview should have "How Tiers Work" section', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); await expect(page.locator('text=How Tiers Work')).toBeVisible({ timeout: 5000 }); }); test('activity page should render ActivityFeed component', async ({ page }) => { await page.goto('/rewards/activity'); await page.waitForTimeout(2000); // Activity page should show feed area (may be empty or loading) // The page should render without errors await expect(page.locator('h1:has-text("Activity")')).toBeVisible(); }); }); test.describe('Sub-Navigation on Rewards Pages', () => { test('rewards pages should have sub-navigation tabs', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // Check for sub-nav with all links const subNav = page.locator('nav a[href="/rewards"]'); await expect(subNav.first()).toBeVisible({ timeout: 5000 }); await expect(page.locator('nav a[href="/rewards/leaderboard"]').first()).toBeVisible(); await expect(page.locator('nav a[href="/rewards/achievements"]').first()).toBeVisible(); await expect(page.locator('nav a[href="/rewards/loot-boxes"]').first()).toBeVisible(); await expect(page.locator('nav a[href="/rewards/activity"]').first()).toBeVisible(); }); test('sub-navigation should highlight active page', async ({ page }) => { await page.goto('/rewards/leaderboard'); await page.waitForTimeout(2000); // The Leaderboard tab should have the active styling (border-primary) const leaderboardTab = page.locator('nav a[href="/rewards/leaderboard"]').first(); await expect(leaderboardTab).toHaveClass(/border-primary|text-primary/); }); test('clicking sub-nav should navigate between pages', async ({ page }) => { await page.goto('/rewards'); await page.waitForTimeout(2000); // Click Achievements in sub-nav await page.locator('nav a[href="/rewards/achievements"]').first().click(); await expect(page).toHaveURL('/rewards/achievements'); await expect(page.locator('h1:has-text("Achievements")')).toBeVisible({ timeout: 5000 }); // Click Activity await page.locator('nav a[href="/rewards/activity"]').first().click(); await expect(page).toHaveURL('/rewards/activity'); await expect(page.locator('h1:has-text("Activity")')).toBeVisible({ timeout: 5000 }); }); }); test.describe('Error Handling', () => { test('rewards page should handle API errors gracefully', async ({ page }) => { const errors = setupErrorCapture(page); await page.goto('/rewards'); await page.waitForTimeout(3000); // Page should still render even if API fails await expect(page.locator('h1:has-text("Rewards")')).toBeVisible({ timeout: 5000 }); // Should not have JS errors (API errors are expected) const criticalErrors = filterCriticalErrors(errors); expect(criticalErrors).toHaveLength(0); }); test('leaderboard should show empty state or loading when API unavailable', async ({ page }) => { await page.goto('/rewards/leaderboard'); await page.waitForTimeout(3000); // Should either show loading skeleton, empty state, or actual data const hasContent = await page.locator('.animate-pulse').count() > 0 || await page.locator('text=No entries').isVisible() || await page.locator('text=/\\#[0-9]+|🥇|🥈|🥉/').count() > 0; expect(hasContent).toBe(true); }); });