forked from cardosofelipe/fast-next-template
Add E2E tests for authenticated navigation and theme toggle
- **Authenticated Navigation:** Test header, footer, settings navigation, user menu interactions, and settings tabs for authenticated users. Validate logout and active tab highlighting. - **Theme Toggle:** Add tests for theme persistence and switching on both public and private pages. Verify localStorage integration and DOM updates across scenarios.
This commit is contained in:
212
frontend/e2e/authenticated-navigation.spec.ts
Normal file
212
frontend/e2e/authenticated-navigation.spec.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* E2E Tests for Authenticated Navigation
|
||||
* Tests header, footer, and settings navigation for authenticated users
|
||||
* Requires backend to be running
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Authenticated Navigation', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login before each test
|
||||
await page.goto('/login');
|
||||
|
||||
// Fill in login credentials (using backend test user)
|
||||
await page.fill('input[name="email"]', 'admin@example.com');
|
||||
await page.fill('input[name="password"]', 'admin123');
|
||||
|
||||
// Submit and wait for redirect to home
|
||||
await Promise.all([
|
||||
page.waitForURL('/'),
|
||||
page.click('button[type="submit"]'),
|
||||
]);
|
||||
});
|
||||
|
||||
test.describe('Header Navigation', () => {
|
||||
test('displays logo and navigates to home when clicked', async ({ page }) => {
|
||||
// Navigate to settings first
|
||||
await page.goto('/settings/profile');
|
||||
|
||||
// Click logo
|
||||
const logo = page.getByRole('link', { name: /fastnext/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('/'),
|
||||
logo.click(),
|
||||
]);
|
||||
|
||||
await expect(page).toHaveURL('/');
|
||||
});
|
||||
|
||||
test('displays home link', async ({ page }) => {
|
||||
const homeLink = page.getByRole('link', { name: /^home$/i }).first();
|
||||
await expect(homeLink).toBeVisible();
|
||||
await expect(homeLink).toHaveAttribute('href', '/');
|
||||
});
|
||||
|
||||
test('displays user avatar', async ({ page }) => {
|
||||
// Avatar button should be visible
|
||||
const avatarButton = page.locator('button[class*="rounded-full"]').first();
|
||||
await expect(avatarButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays theme toggle button', async ({ page }) => {
|
||||
const themeToggle = page.getByRole('button', { name: /toggle theme/i });
|
||||
await expect(themeToggle).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('User Dropdown Menu', () => {
|
||||
test('opens and displays user menu', async ({ page }) => {
|
||||
// Click avatar
|
||||
const avatarButton = page.locator('button[class*="rounded-full"]').first();
|
||||
await avatarButton.click();
|
||||
|
||||
// Check menu items
|
||||
await expect(page.getByRole('menuitem', { name: /profile/i })).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: /settings/i })).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: /log out/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('navigates to profile from dropdown', async ({ page }) => {
|
||||
// Open dropdown
|
||||
const avatarButton = page.locator('button[class*="rounded-full"]').first();
|
||||
await avatarButton.click();
|
||||
|
||||
// Click profile
|
||||
const profileLink = page.getByRole('menuitem', { name: /^profile$/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('/settings/profile'),
|
||||
profileLink.click(),
|
||||
]);
|
||||
|
||||
await expect(page).toHaveURL('/settings/profile');
|
||||
});
|
||||
|
||||
test('logs out user', async ({ page }) => {
|
||||
// Open dropdown
|
||||
const avatarButton = page.locator('button[class*="rounded-full"]').first();
|
||||
await avatarButton.click();
|
||||
|
||||
// Click logout
|
||||
const logoutButton = page.getByRole('menuitem', { name: /log out/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('/login'),
|
||||
logoutButton.click(),
|
||||
]);
|
||||
|
||||
// Should redirect to login
|
||||
await expect(page).toHaveURL('/login');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Settings Pages', () => {
|
||||
test('displays settings tabs', async ({ page }) => {
|
||||
await page.goto('/settings/profile');
|
||||
|
||||
// All tabs should be visible
|
||||
await expect(page.getByRole('link', { name: /^profile$/i })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: /^password$/i })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: /^sessions$/i })).toBeVisible();
|
||||
await expect(page.getByRole('link', { name: /^preferences$/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('can navigate to password tab', async ({ page }) => {
|
||||
await page.goto('/settings/profile');
|
||||
|
||||
const passwordTab = page.getByRole('link', { name: /^password$/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('/settings/password'),
|
||||
passwordTab.click(),
|
||||
]);
|
||||
|
||||
await expect(page).toHaveURL('/settings/password');
|
||||
});
|
||||
|
||||
test('can navigate to sessions tab', async ({ page }) => {
|
||||
await page.goto('/settings/profile');
|
||||
|
||||
const sessionsTab = page.getByRole('link', { name: /^sessions$/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('/settings/sessions'),
|
||||
sessionsTab.click(),
|
||||
]);
|
||||
|
||||
await expect(page).toHaveURL('/settings/sessions');
|
||||
});
|
||||
|
||||
test('can navigate to preferences tab', async ({ page }) => {
|
||||
await page.goto('/settings/profile');
|
||||
|
||||
const preferencesTab = page.getByRole('link', { name: /^preferences$/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('/settings/preferences'),
|
||||
preferencesTab.click(),
|
||||
]);
|
||||
|
||||
await expect(page).toHaveURL('/settings/preferences');
|
||||
});
|
||||
|
||||
test('active tab is highlighted', async ({ page }) => {
|
||||
await page.goto('/settings/password');
|
||||
|
||||
const passwordTab = page.getByRole('link', { name: /^password$/i });
|
||||
const className = await passwordTab.getAttribute('class');
|
||||
expect(className).toContain('border-primary');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Footer', () => {
|
||||
test('displays copyright text', async ({ page }) => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const copyright = page.getByText(`© ${currentYear} FastNext Template`);
|
||||
await expect(copyright).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays settings link in footer', async ({ page }) => {
|
||||
const settingsLinks = page.getByRole('link', { name: /^settings$/i });
|
||||
// Should have at least one (in footer)
|
||||
await expect(settingsLinks.last()).toBeVisible();
|
||||
});
|
||||
|
||||
test('displays GitHub link with correct attributes', async ({ page }) => {
|
||||
const githubLink = page.getByRole('link', { name: /github/i });
|
||||
await expect(githubLink).toBeVisible();
|
||||
await expect(githubLink).toHaveAttribute('target', '_blank');
|
||||
await expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Theme Toggle Integration', () => {
|
||||
test('can switch to dark theme from header', async ({ page }) => {
|
||||
// Open theme menu
|
||||
const themeButton = page.getByRole('button', { name: /toggle theme/i });
|
||||
await themeButton.click();
|
||||
|
||||
// Select dark theme
|
||||
const darkOption = page.getByRole('menuitem', { name: /^dark$/i });
|
||||
await darkOption.click();
|
||||
|
||||
// Wait for theme to apply
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify dark theme is active
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
});
|
||||
|
||||
test('theme persists on settings pages', async ({ page }) => {
|
||||
// Set dark theme
|
||||
const themeButton = page.getByRole('button', { name: /toggle theme/i });
|
||||
await themeButton.click();
|
||||
await page.getByRole('menuitem', { name: /^dark$/i }).click();
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Navigate to settings
|
||||
await page.goto('/settings/profile');
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
|
||||
// Navigate to different settings tab
|
||||
await page.goto('/settings/password');
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
});
|
||||
});
|
||||
});
|
||||
83
frontend/e2e/theme-toggle.spec.ts
Normal file
83
frontend/e2e/theme-toggle.spec.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* E2E Tests for Theme Toggle
|
||||
* Tests theme switching on public pages (login/register)
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Theme Toggle on Public Pages', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Clear localStorage before each test
|
||||
await page.goto('/login');
|
||||
await page.evaluate(() => localStorage.clear());
|
||||
});
|
||||
|
||||
test('theme is applied on login page', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Wait for page to load and theme to be applied
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check that a theme class is applied
|
||||
const htmlElement = page.locator('html');
|
||||
const className = await htmlElement.getAttribute('class');
|
||||
|
||||
// Should have either 'light' or 'dark' class
|
||||
expect(className).toMatch(/light|dark/);
|
||||
});
|
||||
|
||||
test('theme persists across page navigation', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Set theme to dark via localStorage
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('theme', 'dark');
|
||||
});
|
||||
|
||||
// Reload to apply theme
|
||||
await page.reload();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify dark theme is applied
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
|
||||
// Navigate to register page
|
||||
await page.goto('/register');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Theme should still be dark
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
|
||||
// Navigate to password reset
|
||||
await page.goto('/password-reset');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Theme should still be dark
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
});
|
||||
|
||||
test('can switch theme programmatically', async ({ page }) => {
|
||||
await page.goto('/login');
|
||||
|
||||
// Set to light theme
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('theme', 'light');
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.locator('html')).toHaveClass(/light/);
|
||||
await expect(page.locator('html')).not.toHaveClass(/dark/);
|
||||
|
||||
// Switch to dark theme
|
||||
await page.evaluate(() => {
|
||||
localStorage.setItem('theme', 'dark');
|
||||
});
|
||||
await page.reload();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
await expect(page.locator('html')).not.toHaveClass(/light/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user