246 lines
9.6 KiB
TypeScript
246 lines
9.6 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { WebsiteRouteManager } from '../../shared/website/WebsiteRouteManager';
|
|
import { WebsiteAuthManager } from '../../shared/website/WebsiteAuthManager';
|
|
import { ConsoleErrorCapture } from '../../shared/website/ConsoleErrorCapture';
|
|
|
|
const WEBSITE_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000';
|
|
|
|
test.describe('Website Pages - TypeORM Integration', () => {
|
|
let routeManager: WebsiteRouteManager;
|
|
|
|
test.beforeEach(() => {
|
|
routeManager = new WebsiteRouteManager();
|
|
});
|
|
|
|
test('website loads and connects to API', async ({ page }) => {
|
|
// Test that the website loads
|
|
const response = await page.goto(WEBSITE_BASE_URL);
|
|
expect(response?.ok()).toBe(true);
|
|
|
|
// Check that the page renders (body is visible)
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('all routes from RouteConfig are discoverable', async () => {
|
|
expect(() => routeManager.getWebsiteRouteInventory()).not.toThrow();
|
|
});
|
|
|
|
test('public routes are accessible without authentication', async ({ page }) => {
|
|
const routes = routeManager.getWebsiteRouteInventory();
|
|
const publicRoutes = routes.filter(r => r.access === 'public').slice(0, 5);
|
|
|
|
for (const route of publicRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
const response = await page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
const status = response?.status();
|
|
const finalUrl = page.url();
|
|
|
|
console.log(`[TEST DEBUG] Public route - Path: ${path}, Status: ${status}, Final URL: ${finalUrl}`);
|
|
if (status === 500) {
|
|
console.log(`[TEST DEBUG] 500 error on ${path} - Page title: ${await page.title()}`);
|
|
}
|
|
|
|
// The /500 error page intentionally returns 500 status
|
|
// All other routes should load successfully or show 404
|
|
if (path === '/500') {
|
|
expect(response?.status()).toBe(500);
|
|
} else {
|
|
expect(response?.ok() || response?.status() === 404).toBeTruthy();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('protected routes redirect unauthenticated users to login', async ({ page }) => {
|
|
const routes = routeManager.getWebsiteRouteInventory();
|
|
const protectedRoutes = routes.filter(r => r.access !== 'public').slice(0, 3);
|
|
|
|
for (const route of protectedRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
await page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
|
|
const currentUrl = new URL(page.url());
|
|
expect(currentUrl.pathname).toBe('/auth/login');
|
|
expect(currentUrl.searchParams.get('returnTo')).toBe(path);
|
|
}
|
|
});
|
|
|
|
test('admin routes require admin role', async ({ browser, request }) => {
|
|
const routes = routeManager.getWebsiteRouteInventory();
|
|
const adminRoutes = routes.filter(r => r.access === 'admin').slice(0, 2);
|
|
|
|
for (const route of adminRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
|
|
// Regular auth user should be redirected to their home page (dashboard)
|
|
{
|
|
const auth = await WebsiteAuthManager.createAuthContext(browser, request, 'auth');
|
|
const response = await auth.page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
const finalUrl = auth.page.url();
|
|
console.log(`[TEST DEBUG] Admin route test - Path: ${path}`);
|
|
console.log(`[TEST DEBUG] Response status: ${response?.status()}`);
|
|
console.log(`[TEST DEBUG] Final URL: ${finalUrl}`);
|
|
console.log(`[TEST DEBUG] Page title: ${await auth.page.title()}`);
|
|
expect(auth.page.url().includes('dashboard')).toBeTruthy();
|
|
await auth.context.close();
|
|
}
|
|
|
|
// Admin user should have access
|
|
{
|
|
const admin = await WebsiteAuthManager.createAuthContext(browser, request, 'admin');
|
|
await admin.page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
expect(admin.page.url().includes(path)).toBeTruthy();
|
|
await admin.context.close();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('sponsor routes require sponsor role', async ({ browser, request }) => {
|
|
const routes = routeManager.getWebsiteRouteInventory();
|
|
const sponsorRoutes = routes.filter(r => r.access === 'sponsor').slice(0, 2);
|
|
|
|
for (const route of sponsorRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
|
|
// Regular auth user should be redirected to their home page (dashboard)
|
|
{
|
|
const auth = await WebsiteAuthManager.createAuthContext(browser, request, 'auth');
|
|
await auth.page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
const finalUrl = auth.page.url();
|
|
console.log(`[DEBUG] Final URL: ${finalUrl}`);
|
|
console.log(`[DEBUG] Includes 'dashboard': ${finalUrl.includes('dashboard')}`);
|
|
expect(finalUrl.includes('dashboard')).toBeTruthy();
|
|
await auth.context.close();
|
|
}
|
|
|
|
// Sponsor user should have access
|
|
{
|
|
const sponsor = await WebsiteAuthManager.createAuthContext(browser, request, 'sponsor');
|
|
await sponsor.page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
expect(sponsor.page.url().includes(path)).toBeTruthy();
|
|
await sponsor.context.close();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('auth routes redirect authenticated users away', async ({ browser, request }) => {
|
|
const routes = routeManager.getWebsiteRouteInventory();
|
|
const authRoutes = routes.filter(r => r.access === 'auth').slice(0, 2);
|
|
|
|
for (const route of authRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
|
|
const auth = await WebsiteAuthManager.createAuthContext(browser, request, 'auth');
|
|
await auth.page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
|
|
// Should redirect to dashboard or stay on the page
|
|
const currentUrl = auth.page.url();
|
|
expect(currentUrl.includes('dashboard') || currentUrl.includes(path)).toBeTruthy();
|
|
|
|
await auth.context.close();
|
|
}
|
|
});
|
|
|
|
test('parameterized routes handle edge cases', async ({ page }) => {
|
|
const edgeCases = routeManager.getParamEdgeCases();
|
|
|
|
for (const route of edgeCases) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
const response = await page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
|
|
// Client-side pages return 200 even when data doesn't exist
|
|
// They show error messages in the UI instead of HTTP 404
|
|
// This is expected behavior for CSR pages in Next.js
|
|
if (route.allowNotFound) {
|
|
const status = response?.status();
|
|
expect([200, 404, 500].includes(status ?? 0)).toBeTruthy();
|
|
|
|
// If it's 200, verify error message is shown in the UI
|
|
if (status === 200) {
|
|
const bodyText = await page.textContent('body');
|
|
const hasErrorMessage = bodyText?.includes('not found') ||
|
|
bodyText?.includes('doesn\'t exist') ||
|
|
bodyText?.includes('Error');
|
|
expect(hasErrorMessage).toBeTruthy();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
test('no console or page errors on critical routes', async ({ page }) => {
|
|
const faultRoutes = routeManager.getFaultInjectionRoutes();
|
|
|
|
for (const route of faultRoutes) {
|
|
const capture = new ConsoleErrorCapture(page);
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
|
|
await page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
await page.waitForTimeout(500);
|
|
|
|
const errors = capture.getErrors();
|
|
|
|
// Filter out known/expected errors
|
|
const unexpectedErrors = errors.filter(error => {
|
|
const msg = error.message.toLowerCase();
|
|
// Filter out hydration warnings and other expected Next.js warnings
|
|
return !msg.includes('hydration') &&
|
|
!msg.includes('text content does not match') &&
|
|
!msg.includes('warning:') &&
|
|
!msg.includes('download the react devtools');
|
|
});
|
|
|
|
if (unexpectedErrors.length > 0) {
|
|
console.log(`[TEST DEBUG] Unexpected errors on ${path}:`, unexpectedErrors);
|
|
}
|
|
|
|
expect(unexpectedErrors.length).toBe(0);
|
|
}
|
|
});
|
|
|
|
test('TypeORM session persistence across routes', async ({ page }) => {
|
|
const routes = routeManager.getWebsiteRouteInventory();
|
|
const testRoutes = routes.filter(r => r.access === 'public').slice(0, 5);
|
|
|
|
for (const route of testRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
const response = await page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
|
|
// The /500 error page intentionally returns 500 status
|
|
if (path === '/500') {
|
|
expect(response?.status()).toBe(500);
|
|
} else {
|
|
expect(response?.ok() || response?.status() === 404).toBeTruthy();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('auth drift scenarios', async ({ page }) => {
|
|
const driftRoutes = routeManager.getAuthDriftRoutes();
|
|
|
|
for (const route of driftRoutes) {
|
|
const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params);
|
|
|
|
// Try accessing protected route without auth
|
|
await page.goto(`${WEBSITE_BASE_URL}${path}`);
|
|
const currentUrl = page.url();
|
|
|
|
expect(currentUrl.includes('login') || currentUrl.includes('auth')).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('handles invalid routes gracefully', async ({ page }) => {
|
|
const invalidRoutes = [
|
|
'/invalid-route',
|
|
'/leagues/invalid-id',
|
|
'/drivers/invalid-id',
|
|
];
|
|
|
|
for (const route of invalidRoutes) {
|
|
const response = await page.goto(`${WEBSITE_BASE_URL}${route}`);
|
|
|
|
const status = response?.status();
|
|
const url = page.url();
|
|
|
|
expect([200, 404].includes(status ?? 0) || url.includes('/auth/login')).toBe(true);
|
|
}
|
|
});
|
|
}); |