Refactor e2e tests for improved reliability and consistency
- Updated `auth-guard.spec.ts` to configure localStorage before navigation using `context.addInitScript`. - Enhanced test stability with explicit `waitForLoadState` calls after page reloads. - Refactored `admin-dashboard.spec.ts` for more descriptive test names aligning with chart updates. Adjusted lazy-loading behavior in the analytics section. - Reworked `homepage.spec.ts` tests to improve headline and badge visibility checks. Added scroll-triggered animation handling for stats section. - Enhanced MSW handler in `auth.ts` with mock data for user growth and registration activity charts. Added organization and user status distribution data.
This commit is contained in:
@@ -114,13 +114,19 @@ test.describe('Admin Dashboard - Analytics Charts', () => {
|
||||
});
|
||||
|
||||
test('should display user growth chart', async ({ page }) => {
|
||||
await expect(page.getByText('User Growth')).toBeVisible();
|
||||
// Scroll to charts section and wait for it to load
|
||||
const chartsHeading = page.getByRole('heading', { name: 'Analytics Overview' });
|
||||
await chartsHeading.scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(500); // Wait for any lazy-loaded components
|
||||
|
||||
const userGrowthHeading = page.getByText('User Growth');
|
||||
await expect(userGrowthHeading).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Total and active users over the last 30 days')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display session activity chart', async ({ page }) => {
|
||||
await expect(page.getByText('Session Activity')).toBeVisible();
|
||||
await expect(page.getByText('Active and new sessions over the last 14 days')).toBeVisible();
|
||||
test('should display registration activity chart', async ({ page }) => {
|
||||
await expect(page.getByText('User Registration Activity')).toBeVisible();
|
||||
await expect(page.getByText('New user registrations over the last 14 days')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display organization distribution chart', async ({ page }) => {
|
||||
@@ -134,16 +140,21 @@ test.describe('Admin Dashboard - Analytics Charts', () => {
|
||||
});
|
||||
|
||||
test('should display all four charts in grid layout', async ({ page }) => {
|
||||
// Scroll to charts section and wait for lazy-loaded components
|
||||
const chartsHeading = page.getByRole('heading', { name: 'Analytics Overview' });
|
||||
await chartsHeading.scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// All charts should be visible
|
||||
const userGrowthChart = page.getByText('User Growth');
|
||||
const sessionActivityChart = page.getByText('Session Activity');
|
||||
const registrationActivityChart = page.getByText('User Registration Activity');
|
||||
const orgDistributionChart = page.getByText('Organization Distribution');
|
||||
const userStatusChart = page.getByText('User Status Distribution');
|
||||
|
||||
await expect(userGrowthChart).toBeVisible();
|
||||
await expect(sessionActivityChart).toBeVisible();
|
||||
await expect(orgDistributionChart).toBeVisible();
|
||||
await expect(userStatusChart).toBeVisible();
|
||||
await expect(userGrowthChart).toBeVisible({ timeout: 10000 });
|
||||
await expect(registrationActivityChart).toBeVisible({ timeout: 10000 });
|
||||
await expect(orgDistributionChart).toBeVisible({ timeout: 10000 });
|
||||
await expect(userStatusChart).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -43,10 +43,9 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
await expect(page.locator('h2')).toContainText('Reset your password');
|
||||
});
|
||||
|
||||
test('should persist authentication across page reloads', async ({ page }) => {
|
||||
// Manually set a mock token in localStorage for testing
|
||||
await page.goto('/en');
|
||||
await page.evaluate(() => {
|
||||
test('should persist authentication across page reloads', async ({ page, context }) => {
|
||||
// Set localStorage before navigation using context
|
||||
await context.addInitScript(() => {
|
||||
const mockToken = {
|
||||
access_token: 'mock-access-token',
|
||||
refresh_token: 'mock-refresh-token',
|
||||
@@ -61,8 +60,13 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
localStorage.setItem('auth_token', JSON.stringify(mockToken));
|
||||
});
|
||||
|
||||
// Now navigate - localStorage will already be set
|
||||
await page.goto('/en');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Reload the page
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Should still have the token
|
||||
const hasToken = await page.evaluate(() => {
|
||||
@@ -72,8 +76,11 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
});
|
||||
|
||||
test('should clear authentication on logout', async ({ page }) => {
|
||||
// Set up authenticated state
|
||||
// Navigate first without any auth
|
||||
await page.goto('/en');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Now inject auth token after page is loaded
|
||||
await page.evaluate(() => {
|
||||
const mockToken = {
|
||||
access_token: 'mock-access-token',
|
||||
@@ -89,8 +96,11 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
localStorage.setItem('auth_token', JSON.stringify(mockToken));
|
||||
});
|
||||
|
||||
// Reload to apply token
|
||||
await page.reload();
|
||||
// Verify token was set
|
||||
const hasToken = await page.evaluate(() => {
|
||||
return localStorage.getItem('auth_token') !== null;
|
||||
});
|
||||
expect(hasToken).toBe(true);
|
||||
|
||||
// Simulate logout by clearing storage
|
||||
await page.evaluate(() => {
|
||||
@@ -100,18 +110,18 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Storage should be clear
|
||||
const hasToken = await page.evaluate(() => {
|
||||
// Storage should be clear after reload
|
||||
const tokenCleared = await page.evaluate(() => {
|
||||
return localStorage.getItem('auth_token') === null;
|
||||
});
|
||||
expect(hasToken).toBe(true);
|
||||
expect(tokenCleared).toBe(true);
|
||||
});
|
||||
|
||||
test('should not allow access to auth pages when already logged in', async ({ page }) => {
|
||||
// Set up authenticated state
|
||||
await page.goto('/en');
|
||||
await page.evaluate(() => {
|
||||
test('should not allow access to auth pages when already logged in', async ({ page, context }) => {
|
||||
// Set up authenticated state before navigation
|
||||
await context.addInitScript(() => {
|
||||
const mockToken = {
|
||||
access_token: 'mock-access-token',
|
||||
refresh_token: 'mock-refresh-token',
|
||||
@@ -128,6 +138,7 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
|
||||
// Try to access login page
|
||||
await page.goto('/en/login');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait a bit for potential redirect
|
||||
await page.waitForTimeout(2000);
|
||||
@@ -139,10 +150,9 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
expect(currentUrl).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should handle expired tokens gracefully', async ({ page }) => {
|
||||
// Set up authenticated state with expired token
|
||||
await page.goto('/en');
|
||||
await page.evaluate(() => {
|
||||
test('should handle expired tokens gracefully', async ({ page, context }) => {
|
||||
// Set up authenticated state with expired token before navigation
|
||||
await context.addInitScript(() => {
|
||||
const expiredToken = {
|
||||
access_token: 'expired-access-token',
|
||||
refresh_token: 'expired-refresh-token',
|
||||
@@ -159,7 +169,8 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
|
||||
// Try to access a protected route
|
||||
// Backend should return 401, triggering logout
|
||||
await page.reload();
|
||||
await page.goto('/en');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait for potential redirect to login
|
||||
await page.waitForTimeout(3000);
|
||||
@@ -168,13 +179,12 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
// This depends on token refresh logic
|
||||
});
|
||||
|
||||
test('should preserve intended destination after login', async ({ page }) => {
|
||||
test('should preserve intended destination after login', async ({ page, context }) => {
|
||||
// This is a nice-to-have feature that requires protected routes
|
||||
// For now, just verify the test doesn't crash
|
||||
await page.goto('/en');
|
||||
|
||||
// Login (via localStorage for testing)
|
||||
await page.evaluate(() => {
|
||||
await context.addInitScript(() => {
|
||||
const mockToken = {
|
||||
access_token: 'mock-access-token',
|
||||
refresh_token: 'mock-refresh-token',
|
||||
@@ -189,9 +199,9 @@ test.describe('AuthGuard - Route Protection', () => {
|
||||
localStorage.setItem('auth_token', JSON.stringify(mockToken));
|
||||
});
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
await page.waitForTimeout(1000);
|
||||
// Navigate with auth already set
|
||||
await page.goto('/en');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Verify page loaded successfully
|
||||
expect(page.url()).toBeTruthy();
|
||||
|
||||
@@ -353,17 +353,50 @@ export async function setupSuperuserMocks(page: Page): Promise<void> {
|
||||
}
|
||||
});
|
||||
|
||||
// Mock GET /api/v1/admin/stats - Get dashboard statistics
|
||||
// Mock GET /api/v1/admin/stats - Get dashboard statistics with chart data
|
||||
await page.route(`${baseURL}/api/v1/admin/stats`, async (route: Route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
// Generate user growth data for last 30 days
|
||||
const userGrowth = [];
|
||||
const today = new Date();
|
||||
for (let i = 29; i >= 0; i--) {
|
||||
const date = new Date(today);
|
||||
date.setDate(date.getDate() - i);
|
||||
userGrowth.push({
|
||||
date: date.toISOString().split('T')[0],
|
||||
total_users: 50 + Math.floor((29 - i) * 1.5),
|
||||
active_users: Math.floor((50 + (29 - i) * 1.5) * 0.8),
|
||||
});
|
||||
}
|
||||
|
||||
// Generate registration activity for last 14 days
|
||||
const registrationActivity = [];
|
||||
for (let i = 13; i >= 0; i--) {
|
||||
const date = new Date(today);
|
||||
date.setDate(date.getDate() - i);
|
||||
registrationActivity.push({
|
||||
date: date.toISOString().split('T')[0],
|
||||
count: Math.floor(Math.random() * 5) + 1,
|
||||
});
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
total_users: 150,
|
||||
active_users: 120,
|
||||
total_organizations: 25,
|
||||
active_sessions: 45,
|
||||
user_growth: userGrowth,
|
||||
registration_activity: registrationActivity,
|
||||
organization_distribution: [
|
||||
{ name: 'Acme Corporation', value: 12 },
|
||||
{ name: 'Tech Innovators', value: 8 },
|
||||
{ name: 'Global Solutions Inc', value: 25 },
|
||||
{ name: 'Startup Ventures', value: 5 },
|
||||
{ name: 'Inactive Corp', value: 3 },
|
||||
],
|
||||
user_status: [
|
||||
{ name: 'Active', value: 89 },
|
||||
{ name: 'Inactive', value: 11 },
|
||||
],
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -227,22 +227,25 @@ test.describe('Homepage - Hero Section', () => {
|
||||
});
|
||||
|
||||
test('should display main headline', async ({ page }) => {
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /Everything You Need to Build/i }).first()
|
||||
).toBeVisible();
|
||||
await expect(page.getByText(/Modern Web Applications/i).first()).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: /The Pragmatic/i }).first()).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: /Full-Stack Template/i }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display badge with key highlights', async ({ page }) => {
|
||||
await expect(page.getByText('MIT Licensed').first()).toBeVisible();
|
||||
await expect(page.getByText(/97% Test Coverage/).first()).toBeVisible();
|
||||
await expect(page.getByText('Production Ready').first()).toBeVisible();
|
||||
await expect(page.getByText('Comprehensive Tests').first()).toBeVisible();
|
||||
await expect(page.getByText('Pragmatic by Design').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display test coverage stats', async ({ page }) => {
|
||||
await expect(page.getByText('97%').first()).toBeVisible();
|
||||
await expect(page.getByText('743').first()).toBeVisible();
|
||||
await expect(page.getByText(/Passing Tests/).first()).toBeVisible();
|
||||
test('should display quality stats section', async ({ page }) => {
|
||||
// Scroll to stats section to trigger animations
|
||||
const statsSection = page.getByText('Built with Quality in Mind').first();
|
||||
await statsSection.scrollIntoViewIfNeeded();
|
||||
await expect(statsSection).toBeVisible();
|
||||
|
||||
// Wait for animated counter to render (it starts at 0 and counts up)
|
||||
await page.waitForTimeout(500);
|
||||
await expect(page.getByText('Open Source').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should navigate to GitHub when clicking View on GitHub', async ({ page }) => {
|
||||
@@ -444,19 +447,17 @@ test.describe('Homepage - Feature Sections', () => {
|
||||
});
|
||||
|
||||
test('should display tech stack section', async ({ page }) => {
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /Modern, Type-Safe, Production-Grade Stack/i })
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: /A Stack You Can Trust/i })).toBeVisible();
|
||||
|
||||
// Check for key technologies
|
||||
await expect(page.getByText('FastAPI').first()).toBeVisible();
|
||||
await expect(page.getByText('Next.js 15').first()).toBeVisible();
|
||||
await expect(page.getByText('Next.js').first()).toBeVisible();
|
||||
await expect(page.getByText('PostgreSQL').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('should display philosophy section', async ({ page }) => {
|
||||
await expect(page.getByRole('heading', { name: /Why This Template Exists/i })).toBeVisible();
|
||||
await expect(page.getByText(/Free forever, MIT licensed/i)).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: /Why PragmaStack/i })).toBeVisible();
|
||||
await expect(page.getByText(/MIT licensed/i).first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user