feat(test): implement comprehensive Playwright test suite

- Add complete E2E test coverage for authentication flows
- Implement component interaction and navigation testing
- Create responsive design validation across viewports
- Add theme switching and visual regression testing
- Include smoke tests for critical user paths
- Configure Playwright with proper test setup

Test suite ensures application reliability with automated validation
of user flows, accessibility, and visual consistency.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-16 12:44:32 -06:00
parent 3452f02afc
commit 48b9b680e3
11 changed files with 2710 additions and 0 deletions

View File

@@ -0,0 +1,365 @@
import { test, expect, Page } from '@playwright/test';
import path from 'path';
const DEMO_ACCOUNTS = {
admin: { email: 'admin@example.com', password: 'demo123' },
};
async function takeScreenshot(page: Page, name: string) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const fileName = `components_${name}_${timestamp}.png`;
await page.screenshot({
path: path.join('screenshots', fileName),
fullPage: true
});
return fileName;
}
async function loginAsAdmin(page: Page) {
await page.goto('/login');
await page.fill('[data-testid="email-input"]', DEMO_ACCOUNTS.admin.email);
await page.fill('[data-testid="password-input"]', DEMO_ACCOUNTS.admin.password);
await page.click('[data-testid="login-button"]');
await expect(page).toHaveURL('/dashboard', { timeout: 10000 });
}
test.describe('UI Components', () => {
test.beforeEach(async ({ page }) => {
// Clear auth storage
await page.evaluate(() => {
localStorage.removeItem('bct_auth_user');
localStorage.removeItem('bct_auth_remember');
});
});
test('should display button variants correctly', async ({ page }) => {
await page.goto('/login');
// Primary button (login button)
const loginButton = page.locator('[data-testid="login-button"]');
await expect(loginButton).toBeVisible();
await expect(loginButton).toHaveClass(/bg-blue|bg-primary/);
await takeScreenshot(page, 'button-primary-state');
// Test button hover state
await loginButton.hover();
await takeScreenshot(page, 'button-primary-hover');
// Test button disabled state
await page.fill('[data-testid="email-input"]', 'test@example.com');
await page.fill('[data-testid="password-input"]', 'test123');
await loginButton.click();
// Button should be disabled during loading
await expect(loginButton).toBeDisabled();
await takeScreenshot(page, 'button-primary-disabled');
});
test('should display form inputs correctly', async ({ page }) => {
await page.goto('/login');
// Email input
const emailInput = page.locator('[data-testid="email-input"]');
await expect(emailInput).toBeVisible();
await expect(emailInput).toHaveAttribute('type', 'email');
// Password input
const passwordInput = page.locator('[data-testid="password-input"]');
await expect(passwordInput).toBeVisible();
await expect(passwordInput).toHaveAttribute('type', 'password');
await takeScreenshot(page, 'form-inputs-empty');
// Test input focus states
await emailInput.focus();
await takeScreenshot(page, 'email-input-focused');
await passwordInput.focus();
await takeScreenshot(page, 'password-input-focused');
// Test input filled states
await emailInput.fill('user@example.com');
await passwordInput.fill('password123');
await takeScreenshot(page, 'form-inputs-filled');
// Test input validation states
await emailInput.fill('invalid-email');
await passwordInput.click(); // Trigger validation
await takeScreenshot(page, 'form-inputs-validation-error');
});
test('should display cards correctly', async ({ page }) => {
await loginAsAdmin(page);
// Navigate to events to see cards
await page.click('[data-testid="nav-events"]');
await expect(page).toHaveURL('/events');
// Check if event cards are displayed
const cards = page.locator('[data-testid^="event-card"]');
const cardCount = await cards.count();
if (cardCount > 0) {
await expect(cards.first()).toBeVisible();
// Test card hover effects
await cards.first().hover();
await takeScreenshot(page, 'card-hover-effect');
// Test card content
await expect(cards.first().locator('.card-title, h2, h3')).toBeVisible();
} else {
// If no cards, take screenshot of empty state
await takeScreenshot(page, 'cards-empty-state');
}
await takeScreenshot(page, 'cards-overview');
});
test('should display badges correctly', async ({ page }) => {
await loginAsAdmin(page);
// Look for badges in the dashboard or events page
await page.click('[data-testid="nav-events"]');
// Check for status badges
const badges = page.locator('[data-testid^="badge"], .badge, [class*="badge"]');
const badgeCount = await badges.count();
if (badgeCount > 0) {
await expect(badges.first()).toBeVisible();
await takeScreenshot(page, 'badges-display');
}
// Navigate to dashboard to check for other badges
await page.click('[data-testid="nav-dashboard"]');
await takeScreenshot(page, 'dashboard-badges');
});
test('should display alerts correctly', async ({ page }) => {
await page.goto('/login');
// Trigger error alert by submitting invalid form
await page.fill('[data-testid="email-input"]', 'invalid@example.com');
await page.fill('[data-testid="password-input"]', 'wrong');
await page.click('[data-testid="login-button"]');
// Error alert should appear
const errorAlert = page.locator('[data-testid="error-message"], .alert-error, [role="alert"]');
await expect(errorAlert).toBeVisible();
await takeScreenshot(page, 'alert-error-state');
// Check alert styling
await expect(errorAlert).toHaveClass(/error|red|danger/);
});
test('should display modals correctly', async ({ page }) => {
await loginAsAdmin(page);
// Look for modal triggers
const modalTriggers = page.locator('[data-testid*="modal"], [data-modal], [aria-haspopup="dialog"]');
const triggerCount = await modalTriggers.count();
if (triggerCount > 0) {
// Click first modal trigger
await modalTriggers.first().click();
// Modal should appear
const modal = page.locator('[role="dialog"], .modal, [data-testid*="modal-content"]');
await expect(modal).toBeVisible();
await takeScreenshot(page, 'modal-open');
// Modal should have overlay
const overlay = page.locator('.modal-overlay, [data-testid="modal-overlay"]');
if (await overlay.isVisible()) {
await takeScreenshot(page, 'modal-with-overlay');
}
// Close modal
const closeButton = page.locator('[data-testid="modal-close"], .modal-close, [aria-label="Close"]');
if (await closeButton.isVisible()) {
await closeButton.click();
await expect(modal).not.toBeVisible();
}
}
await takeScreenshot(page, 'modal-test-complete');
});
test('should display dropdown menus correctly', async ({ page }) => {
await loginAsAdmin(page);
// Test user menu dropdown
await page.click('[data-testid="user-menu"]');
const dropdown = page.locator('[data-testid="user-dropdown"]');
await expect(dropdown).toBeVisible();
await takeScreenshot(page, 'dropdown-user-menu');
// Test dropdown items
await expect(dropdown.locator('[data-testid="profile-link"]')).toBeVisible();
await expect(dropdown.locator('[data-testid="settings-link"]')).toBeVisible();
await expect(dropdown.locator('[data-testid="logout-button"]')).toBeVisible();
// Test dropdown hover effects
await dropdown.locator('[data-testid="profile-link"]').hover();
await takeScreenshot(page, 'dropdown-item-hover');
// Close dropdown by clicking outside
await page.click('body');
await expect(dropdown).not.toBeVisible();
});
test('should display loading states correctly', async ({ page }) => {
await page.goto('/login');
// Fill form and submit to trigger loading state
await page.fill('[data-testid="email-input"]', DEMO_ACCOUNTS.admin.email);
await page.fill('[data-testid="password-input"]', DEMO_ACCOUNTS.admin.password);
// Click login and quickly capture loading state
await page.click('[data-testid="login-button"]');
// Button should show loading state
const loadingButton = page.locator('[data-testid="login-button"]');
await expect(loadingButton).toBeDisabled();
// Check for loading spinner or text
const loadingIndicator = page.locator('[data-testid="loading-spinner"], .spinner, .loading');
if (await loadingIndicator.isVisible()) {
await takeScreenshot(page, 'loading-spinner');
}
await takeScreenshot(page, 'button-loading-state');
// Wait for login to complete
await expect(page).toHaveURL('/dashboard', { timeout: 10000 });
});
test('should display skeleton loaders correctly', async ({ page }) => {
await loginAsAdmin(page);
// Navigate to a page that might show skeleton loaders
await page.click('[data-testid="nav-events"]');
// Look for skeleton components
const skeletons = page.locator('[data-testid="skeleton"], .skeleton, [class*="skeleton"]');
const skeletonCount = await skeletons.count();
if (skeletonCount > 0) {
await takeScreenshot(page, 'skeleton-loaders');
}
// Check for loading states in general
const loadingElements = page.locator('[data-testid*="loading"], .loading, [aria-label*="loading"]');
const loadingCount = await loadingElements.count();
if (loadingCount > 0) {
await takeScreenshot(page, 'loading-elements');
}
});
test('should handle component interactions', async ({ page }) => {
await loginAsAdmin(page);
// Test checkbox interactions (if any)
const checkboxes = page.locator('input[type="checkbox"]');
const checkboxCount = await checkboxes.count();
if (checkboxCount > 0) {
const firstCheckbox = checkboxes.first();
// Test unchecked state
await expect(firstCheckbox).not.toBeChecked();
await takeScreenshot(page, 'checkbox-unchecked');
// Test checked state
await firstCheckbox.check();
await expect(firstCheckbox).toBeChecked();
await takeScreenshot(page, 'checkbox-checked');
}
// Test radio button interactions (if any)
const radioButtons = page.locator('input[type="radio"]');
const radioCount = await radioButtons.count();
if (radioCount > 0) {
await radioButtons.first().check();
await takeScreenshot(page, 'radio-button-selected');
}
// Test select dropdown interactions (if any)
const selects = page.locator('select, [data-testid*="select"]');
const selectCount = await selects.count();
if (selectCount > 0) {
const firstSelect = selects.first();
await firstSelect.click();
await takeScreenshot(page, 'select-dropdown-open');
// Select first option
const options = firstSelect.locator('option');
if (await options.count() > 1) {
await options.nth(1).click();
await takeScreenshot(page, 'select-option-selected');
}
}
});
test('should display tooltips correctly', async ({ page }) => {
await loginAsAdmin(page);
// Look for elements with tooltips
const tooltipTriggers = page.locator('[title], [data-tooltip], [aria-describedby]');
const triggerCount = await tooltipTriggers.count();
if (triggerCount > 0) {
// Hover over first element with tooltip
await tooltipTriggers.first().hover();
// Look for tooltip content
const tooltips = page.locator('[role="tooltip"], .tooltip, [data-testid="tooltip"]');
const tooltipCount = await tooltips.count();
if (tooltipCount > 0) {
await expect(tooltips.first()).toBeVisible();
await takeScreenshot(page, 'tooltip-display');
}
}
});
test('should handle keyboard navigation in components', async ({ page }) => {
await page.goto('/login');
// Test tab navigation through form
await page.keyboard.press('Tab'); // Skip to content link
await page.keyboard.press('Tab'); // Email input
await expect(page.locator('[data-testid="email-input"]')).toBeFocused();
await page.keyboard.press('Tab'); // Password input
await expect(page.locator('[data-testid="password-input"]')).toBeFocused();
await page.keyboard.press('Tab'); // Remember me checkbox
const rememberMeCheckbox = page.locator('[data-testid="remember-me"]');
if (await rememberMeCheckbox.isVisible()) {
await expect(rememberMeCheckbox).toBeFocused();
await page.keyboard.press('Tab'); // Login button
}
await expect(page.locator('[data-testid="login-button"]')).toBeFocused();
await takeScreenshot(page, 'keyboard-navigation-login-button');
// Test Enter key submission
await page.fill('[data-testid="email-input"]', DEMO_ACCOUNTS.admin.email);
await page.fill('[data-testid="password-input"]', DEMO_ACCOUNTS.admin.password);
await page.keyboard.press('Enter');
// Should submit the form
await expect(page).toHaveURL('/dashboard', { timeout: 10000 });
});
});