import { test, expect, Browser, APIRequestContext } from '@playwright/test'; import { WebsiteAuthManager, AuthContext } from '../../shared/website/WebsiteAuthManager'; import { ConsoleErrorCapture } from '../../shared/website/ConsoleErrorCapture'; import { WebsiteRouteManager } from '../../shared/website/WebsiteRouteManager'; /** * E2E Tests for League Pages with Data Validation * * Tests cover: * 1. /leagues (Discovery Page) - League cards, filters, quick actions * 2. /leagues/[id] (Overview Page) - Stats, next race, season progress * 3. /leagues/[id]/schedule (Schedule Page) - Race list, registration, admin controls * 4. /leagues/[id]/standings (Standings Page) - Trend indicators, stats, team toggle * 5. /leagues/[id]/roster (Roster Page) - Driver cards, admin actions */ test.describe('League Pages - E2E with Data Validation', () => { const routeManager = new WebsiteRouteManager(); const leagueId = routeManager.resolvePathTemplate('/leagues/[id]', { id: WebsiteRouteManager.IDs.LEAGUE }); const CONSOLE_ALLOWLIST = [ /Download the React DevTools/i, /Next.js-specific warning/i, /Failed to load resource: the server responded with a status of 404/i, /Failed to load resource: the server responded with a status of 403/i, /Failed to load resource: the server responded with a status of 401/i, /Failed to load resource: the server responded with a status of 500/i, /net::ERR_NAME_NOT_RESOLVED/i, /net::ERR_CONNECTION_CLOSED/i, /net::ERR_ACCESS_DENIED/i, /Minified React error #418/i, /Event/i, /An error occurred in the Server Components render/i, /Route Error Boundary/i, ]; test.beforeEach(async ({ page }) => { const allowedHosts = [ new URL(process.env.PLAYWRIGHT_BASE_URL || 'http://website:3000').host, new URL(process.env.API_BASE_URL || 'http://api:3000').host, ]; await page.route('**/*', (route) => { const url = new URL(route.request().url()); if (allowedHosts.includes(url.host) || url.protocol === 'data:') { route.continue(); } else { route.abort('accessdenied'); } }); }); test.describe('1. /leagues (Discovery Page)', () => { test('Unauthenticated user can view league discovery page', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto('/leagues', { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/leagues'); // Verify featured leagues section displays await expect(page.getByTestId('featured-leagues-section')).toBeVisible(); // Verify league cards are present const leagueCards = page.getByTestId('league-card'); await expect(leagueCards.first()).toBeVisible(); // Verify league cards show correct metadata const firstCard = leagueCards.first(); await expect(firstCard.getByTestId('league-card-title')).toBeVisible(); await expect(firstCard.getByTestId('league-card-next-race')).toBeVisible(); await expect(firstCard.getByTestId('league-card-active-drivers')).toBeVisible(); // Verify category filters are present await expect(page.getByTestId('category-filters')).toBeVisible(); // Verify Quick Join/Follow buttons are present await expect(page.getByTestId('quick-join-button')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Authenticated user can view league discovery page', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto('/leagues', { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/leagues'); // Verify featured leagues section displays await expect(page.getByTestId('featured-leagues-section')).toBeVisible(); // Verify league cards are present const leagueCards = page.getByTestId('league-card'); await expect(leagueCards.first()).toBeVisible(); // Verify league cards show correct metadata const firstCard = leagueCards.first(); await expect(firstCard.getByTestId('league-card-title')).toBeVisible(); await expect(firstCard.getByTestId('league-card-next-race')).toBeVisible(); await expect(firstCard.getByTestId('league-card-active-drivers')).toBeVisible(); // Verify category filters are present await expect(page.getByTestId('category-filters')).toBeVisible(); // Verify Quick Join/Follow buttons are present await expect(page.getByTestId('quick-join-button')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Category filters work correctly', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto('/leagues', { waitUntil: 'commit', timeout: 15000 }); // Verify category filters are present await expect(page.getByTestId('category-filters')).toBeVisible(); // Click on a category filter const filterButton = page.getByTestId('category-filter-all'); await filterButton.click(); // Wait for filter to apply await page.waitForTimeout(1000); // Verify league cards are still visible after filtering const leagueCards = page.getByTestId('league-card'); await expect(leagueCards.first()).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); }); test.describe('2. /leagues/[id] (Overview Page)', () => { test('Unauthenticated user can view league overview', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto(leagueId, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/leagues/'); // Verify league name is displayed await expect(page.getByTestId('league-detail-title')).toBeVisible(); // Verify stats section displays await expect(page.getByTestId('league-stats-section')).toBeVisible(); // Verify Next Race countdown displays correctly await expect(page.getByTestId('next-race-countdown')).toBeVisible(); // Verify Season progress bar shows correct percentage await expect(page.getByTestId('season-progress-bar')).toBeVisible(); // Verify Activity feed shows recent activity await expect(page.getByTestId('activity-feed')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Authenticated user can view league overview', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(leagueId, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/leagues/'); // Verify league name is displayed await expect(page.getByTestId('league-detail-title')).toBeVisible(); // Verify stats section displays await expect(page.getByTestId('league-stats-section')).toBeVisible(); // Verify Next Race countdown displays correctly await expect(page.getByTestId('next-race-countdown')).toBeVisible(); // Verify Season progress bar shows correct percentage await expect(page.getByTestId('season-progress-bar')).toBeVisible(); // Verify Activity feed shows recent activity await expect(page.getByTestId('activity-feed')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Admin user can view admin widgets', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'admin'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(leagueId, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/leagues/'); // Verify admin widgets are visible for authorized users await expect(page.getByTestId('admin-widgets')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Stats match API values', async ({ page, request }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); // Fetch API data const apiResponse = await request.get(`${process.env.API_BASE_URL || 'http://api:3000'}/leagues/${WebsiteRouteManager.IDs.LEAGUE}`); const apiData = await apiResponse.json(); // Navigate to league overview await page.goto(leagueId, { waitUntil: 'commit', timeout: 15000 }); // Verify stats match API values const membersStat = page.getByTestId('stat-members'); const racesStat = page.getByTestId('stat-races'); const avgSofStat = page.getByTestId('stat-avg-sof'); await expect(membersStat).toBeVisible(); await expect(racesStat).toBeVisible(); await expect(avgSofStat).toBeVisible(); // Verify the stats contain expected values from API const membersText = await membersStat.textContent(); const racesText = await racesStat.textContent(); const avgSofText = await avgSofStat.textContent(); // Basic validation - stats should not be empty expect(membersText).toBeTruthy(); expect(racesText).toBeTruthy(); expect(avgSofText).toBeTruthy(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); }); test.describe('3. /leagues/[id]/schedule (Schedule Page)', () => { const schedulePath = routeManager.resolvePathTemplate('/leagues/[id]/schedule', { id: WebsiteRouteManager.IDs.LEAGUE }); test('Unauthenticated user can view schedule page', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto(schedulePath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/schedule'); // Verify races are grouped by month await expect(page.getByTestId('schedule-month-group')).toBeVisible(); // Verify race list is present const raceItems = page.getByTestId('race-item'); await expect(raceItems.first()).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Authenticated user can view schedule page', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(schedulePath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/schedule'); // Verify races are grouped by month await expect(page.getByTestId('schedule-month-group')).toBeVisible(); // Verify race list is present const raceItems = page.getByTestId('race-item'); await expect(raceItems.first()).toBeVisible(); // Verify Register/Withdraw buttons are present await expect(page.getByTestId('register-button')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Admin user can view admin controls', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'admin'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(schedulePath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/schedule'); // Verify admin controls are visible for authorized users await expect(page.getByTestId('admin-controls')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Race detail modal shows correct data', async ({ page, request }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); // Fetch API data const apiResponse = await request.get(`${process.env.API_BASE_URL || 'http://api:3000'}/leagues/${WebsiteRouteManager.IDs.LEAGUE}/schedule`); const apiData = await apiResponse.json(); // Navigate to schedule page await page.goto(schedulePath, { waitUntil: 'commit', timeout: 15000 }); // Click on a race item to open modal const raceItem = page.getByTestId('race-item').first(); await raceItem.click(); // Verify modal is visible await expect(page.getByTestId('race-detail-modal')).toBeVisible(); // Verify modal contains race data const modalContent = page.getByTestId('race-detail-modal'); await expect(modalContent.getByTestId('race-track')).toBeVisible(); await expect(modalContent.getByTestId('race-car')).toBeVisible(); await expect(modalContent.getByTestId('race-date')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); }); test.describe('4. /leagues/[id]/standings (Standings Page)', () => { const standingsPath = routeManager.resolvePathTemplate('/leagues/[id]/standings', { id: WebsiteRouteManager.IDs.LEAGUE }); test('Unauthenticated user can view standings page', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto(standingsPath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/standings'); // Verify standings table is present await expect(page.getByTestId('standings-table')).toBeVisible(); // Verify trend indicators display correctly await expect(page.getByTestId('trend-indicator')).toBeVisible(); // Verify championship stats show correct data await expect(page.getByTestId('championship-stats')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Authenticated user can view standings page', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(standingsPath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/standings'); // Verify standings table is present await expect(page.getByTestId('standings-table')).toBeVisible(); // Verify trend indicators display correctly await expect(page.getByTestId('trend-indicator')).toBeVisible(); // Verify championship stats show correct data await expect(page.getByTestId('championship-stats')).toBeVisible(); // Verify team standings toggle is present await expect(page.getByTestId('team-standings-toggle')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Team standings toggle works correctly', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto(standingsPath, { waitUntil: 'commit', timeout: 15000 }); // Verify team standings toggle is present await expect(page.getByTestId('team-standings-toggle')).toBeVisible(); // Click on team standings toggle const toggle = page.getByTestId('team-standings-toggle'); await toggle.click(); // Wait for toggle to apply await page.waitForTimeout(1000); // Verify team standings are visible await expect(page.getByTestId('team-standings-table')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Drop weeks are marked correctly', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto(standingsPath, { waitUntil: 'commit', timeout: 15000 }); // Verify drop weeks are marked const dropWeeks = page.getByTestId('drop-week-marker'); await expect(dropWeeks.first()).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Standings data matches API values', async ({ page, request }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); // Fetch API data const apiResponse = await request.get(`${process.env.API_BASE_URL || 'http://api:3000'}/leagues/${WebsiteRouteManager.IDs.LEAGUE}/standings`); const apiData = await apiResponse.json(); // Navigate to standings page await page.goto(standingsPath, { waitUntil: 'commit', timeout: 15000 }); // Verify standings table is present await expect(page.getByTestId('standings-table')).toBeVisible(); // Verify table rows match API data const tableRows = page.getByTestId('standings-row'); const rowCount = await tableRows.count(); // Basic validation - should have at least one row expect(rowCount).toBeGreaterThan(0); // Verify first row contains expected data const firstRow = tableRows.first(); await expect(firstRow.getByTestId('standing-position')).toBeVisible(); await expect(firstRow.getByTestId('standing-driver')).toBeVisible(); await expect(firstRow.getByTestId('standing-points')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); }); test.describe('5. /leagues/[id]/roster (Roster Page)', () => { const rosterPath = routeManager.resolvePathTemplate('/leagues/[id]/roster', { id: WebsiteRouteManager.IDs.LEAGUE }); test('Unauthenticated user can view roster page', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); await page.goto(rosterPath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/roster'); // Verify driver cards are present const driverCards = page.getByTestId('driver-card'); await expect(driverCards.first()).toBeVisible(); // Verify driver cards show correct stats const firstCard = driverCards.first(); await expect(firstCard.getByTestId('driver-card-name')).toBeVisible(); await expect(firstCard.getByTestId('driver-card-stats')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('Authenticated user can view roster page', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(rosterPath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/roster'); // Verify driver cards are present const driverCards = page.getByTestId('driver-card'); await expect(driverCards.first()).toBeVisible(); // Verify driver cards show correct stats const firstCard = driverCards.first(); await expect(firstCard.getByTestId('driver-card-name')).toBeVisible(); await expect(firstCard.getByTestId('driver-card-stats')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Admin user can view admin actions', async ({ browser, request }) => { const { context, page } = await WebsiteAuthManager.createAuthContext(browser, request, 'admin'); const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); try { await page.goto(rosterPath, { waitUntil: 'commit', timeout: 15000 }); // Verify page loads successfully expect(page.url()).toContain('/roster'); // Verify admin actions are visible for authorized users await expect(page.getByTestId('admin-actions')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); } finally { await context.close(); } }); test('Roster data matches API values', async ({ page, request }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); // Fetch API data const apiResponse = await request.get(`${process.env.API_BASE_URL || 'http://api:3000'}/leagues/${WebsiteRouteManager.IDs.LEAGUE}/memberships`); const apiData = await apiResponse.json(); // Navigate to roster page await page.goto(rosterPath, { waitUntil: 'commit', timeout: 15000 }); // Verify driver cards are present const driverCards = page.getByTestId('driver-card'); const cardCount = await driverCards.count(); // Basic validation - should have at least one driver expect(cardCount).toBeGreaterThan(0); // Verify first card contains expected data const firstCard = driverCards.first(); await expect(firstCard.getByTestId('driver-card-name')).toBeVisible(); await expect(firstCard.getByTestId('driver-card-stats')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); }); test.describe('6. Navigation Between League Pages', () => { test('User can navigate from discovery to league overview', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); // Navigate to leagues discovery page await page.goto('/leagues', { waitUntil: 'commit', timeout: 15000 }); // Click on a league card const leagueCard = page.getByTestId('league-card').first(); await leagueCard.click(); // Verify navigation to league overview await page.waitForURL(/\/leagues\/[^/]+$/, { timeout: 15000 }); expect(page.url()).toMatch(/\/leagues\/[^/]+$/); // Verify league overview content is visible await expect(page.getByTestId('league-detail-title')).toBeVisible(); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); test('User can navigate between league sub-pages', async ({ page }) => { const capture = new ConsoleErrorCapture(page); capture.setAllowlist(CONSOLE_ALLOWLIST); // Navigate to league overview await page.goto(leagueId, { waitUntil: 'commit', timeout: 15000 }); // Click on Schedule tab const scheduleTab = page.getByTestId('schedule-tab'); await scheduleTab.click(); // Verify navigation to schedule page await page.waitForURL(/\/leagues\/[^/]+\/schedule$/, { timeout: 15000 }); expect(page.url()).toMatch(/\/leagues\/[^/]+\/schedule$/); // Click on Standings tab const standingsTab = page.getByTestId('standings-tab'); await standingsTab.click(); // Verify navigation to standings page await page.waitForURL(/\/leagues\/[^/]+\/standings$/, { timeout: 15000 }); expect(page.url()).toMatch(/\/leagues\/[^/]+\/standings$/); // Click on Roster tab const rosterTab = page.getByTestId('roster-tab'); await rosterTab.click(); // Verify navigation to roster page await page.waitForURL(/\/leagues\/[^/]+\/roster$/, { timeout: 15000 }); expect(page.url()).toMatch(/\/leagues\/[^/]+\/roster$/); expect(capture.getUnexpectedErrors(), capture.format()).toHaveLength(0); }); }); });