Files
gridpilot.gg/tests/e2e/website/league-pages.e2e.test.ts
2026-01-21 22:36:01 +01:00

629 lines
24 KiB
TypeScript

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);
});
});
});