- 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>
354 lines
12 KiB
TypeScript
354 lines
12 KiB
TypeScript
import { test, expect, Page } from '@playwright/test';
|
|
import path from 'path';
|
|
|
|
const DEMO_ACCOUNTS = {
|
|
admin: { email: 'admin@example.com', password: 'demo123' },
|
|
};
|
|
|
|
const VIEWPORTS = {
|
|
mobile: { width: 375, height: 667 },
|
|
mobileLarge: { width: 414, height: 896 },
|
|
tablet: { width: 768, height: 1024 },
|
|
tabletLarge: { width: 1024, height: 768 },
|
|
desktop: { width: 1280, height: 720 },
|
|
desktopLarge: { width: 1920, height: 1080 },
|
|
};
|
|
|
|
async function takeScreenshot(page: Page, name: string) {
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const fileName = `responsive_${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('Responsive Design', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Clear auth storage
|
|
await page.evaluate(() => {
|
|
localStorage.removeItem('bct_auth_user');
|
|
localStorage.removeItem('bct_auth_remember');
|
|
});
|
|
});
|
|
|
|
test('should display correctly on mobile devices', async ({ page }) => {
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
await loginAsAdmin(page);
|
|
|
|
// Mobile menu button should be visible
|
|
await expect(page.locator('[data-testid="mobile-menu-button"]')).toBeVisible();
|
|
|
|
// Sidebar should be hidden initially
|
|
await expect(page.locator('[data-testid="sidebar"]')).not.toBeVisible();
|
|
|
|
// Main content should take full width
|
|
const mainContent = page.locator('[data-testid="main-content"]');
|
|
await expect(mainContent).toBeVisible();
|
|
|
|
await takeScreenshot(page, 'mobile-dashboard');
|
|
|
|
// Test mobile navigation
|
|
await page.click('[data-testid="mobile-menu-button"]');
|
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
|
await takeScreenshot(page, 'mobile-menu-open');
|
|
|
|
// Navigate to events
|
|
await page.click('[data-testid="nav-events"]');
|
|
await expect(page).toHaveURL('/events');
|
|
await takeScreenshot(page, 'mobile-events-page');
|
|
});
|
|
|
|
test('should display correctly on tablet devices', async ({ page }) => {
|
|
await page.setViewportSize(VIEWPORTS.tablet);
|
|
await loginAsAdmin(page);
|
|
|
|
// Should still show mobile menu on smaller tablets
|
|
await expect(page.locator('[data-testid="mobile-menu-button"]')).toBeVisible();
|
|
|
|
await takeScreenshot(page, 'tablet-dashboard');
|
|
|
|
// Test tablet navigation
|
|
await page.click('[data-testid="mobile-menu-button"]');
|
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
|
await takeScreenshot(page, 'tablet-menu-open');
|
|
});
|
|
|
|
test('should display correctly on large tablets', async ({ page }) => {
|
|
await page.setViewportSize(VIEWPORTS.tabletLarge);
|
|
await loginAsAdmin(page);
|
|
|
|
// Large tablets should show desktop layout
|
|
await expect(page.locator('[data-testid="mobile-menu-button"]')).not.toBeVisible();
|
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
|
|
|
await takeScreenshot(page, 'tablet-large-dashboard');
|
|
});
|
|
|
|
test('should display correctly on desktop', async ({ page }) => {
|
|
await page.setViewportSize(VIEWPORTS.desktop);
|
|
await loginAsAdmin(page);
|
|
|
|
// Desktop should show full sidebar
|
|
await expect(page.locator('[data-testid="mobile-menu-button"]')).not.toBeVisible();
|
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
|
|
|
// Sidebar should be properly sized
|
|
const sidebar = page.locator('[data-testid="sidebar"]');
|
|
const sidebarBox = await sidebar.boundingBox();
|
|
expect(sidebarBox?.width).toBeGreaterThan(200);
|
|
expect(sidebarBox?.width).toBeLessThan(300);
|
|
|
|
await takeScreenshot(page, 'desktop-dashboard');
|
|
|
|
// Navigate to events
|
|
await page.click('[data-testid="nav-events"]');
|
|
await expect(page).toHaveURL('/events');
|
|
await takeScreenshot(page, 'desktop-events-page');
|
|
});
|
|
|
|
test('should handle touch interactions on mobile', async ({ page }) => {
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
await loginAsAdmin(page);
|
|
|
|
// Test touch tap on mobile menu
|
|
await page.tap('[data-testid="mobile-menu-button"]');
|
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
|
|
|
// Test touch tap on navigation
|
|
await page.tap('[data-testid="nav-events"]');
|
|
await expect(page).toHaveURL('/events');
|
|
|
|
await takeScreenshot(page, 'mobile-touch-navigation');
|
|
});
|
|
|
|
test('should handle swipe gestures for menu', async ({ page }) => {
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
await loginAsAdmin(page);
|
|
|
|
// Test swipe to open menu (if implemented)
|
|
const viewport = page.viewportSize();
|
|
if (viewport) {
|
|
// Swipe from left edge
|
|
await page.touchscreen.tap(10, viewport.height / 2);
|
|
await page.mouse.move(10, viewport.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(200, viewport.height / 2);
|
|
await page.mouse.up();
|
|
|
|
await takeScreenshot(page, 'mobile-swipe-gesture');
|
|
}
|
|
});
|
|
|
|
test('should adapt card layouts across breakpoints', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
|
|
// Test mobile card layout
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
await page.click('[data-testid="nav-events"]');
|
|
await expect(page).toHaveURL('/events');
|
|
|
|
// Cards should stack on mobile
|
|
const cards = page.locator('[data-testid^="event-card"]');
|
|
const cardCount = await cards.count();
|
|
|
|
if (cardCount > 0) {
|
|
// Check that cards are stacked (full width)
|
|
const firstCard = cards.first();
|
|
const cardBox = await firstCard.boundingBox();
|
|
const viewportWidth = VIEWPORTS.mobile.width;
|
|
|
|
if (cardBox) {
|
|
expect(cardBox.width).toBeGreaterThan(viewportWidth * 0.8);
|
|
}
|
|
}
|
|
|
|
await takeScreenshot(page, 'mobile-card-layout');
|
|
|
|
// Test desktop card layout
|
|
await page.setViewportSize(VIEWPORTS.desktop);
|
|
await page.waitForTimeout(500); // Allow layout to adjust
|
|
|
|
await takeScreenshot(page, 'desktop-card-layout');
|
|
});
|
|
|
|
test('should handle text scaling across devices', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
|
|
// Test mobile text scaling
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
|
|
const mobileHeading = page.locator('h1').first();
|
|
const mobileFontSize = await mobileHeading.evaluate((el) =>
|
|
getComputedStyle(el).fontSize
|
|
);
|
|
|
|
await takeScreenshot(page, 'mobile-text-scaling');
|
|
|
|
// Test desktop text scaling
|
|
await page.setViewportSize(VIEWPORTS.desktop);
|
|
|
|
const desktopHeading = page.locator('h1').first();
|
|
const desktopFontSize = await desktopHeading.evaluate((el) =>
|
|
getComputedStyle(el).fontSize
|
|
);
|
|
|
|
await takeScreenshot(page, 'desktop-text-scaling');
|
|
|
|
// Desktop font should generally be larger or equal
|
|
const mobileSize = parseFloat(mobileFontSize);
|
|
const desktopSize = parseFloat(desktopFontSize);
|
|
expect(desktopSize).toBeGreaterThanOrEqual(mobileSize);
|
|
});
|
|
|
|
test('should handle form layouts responsively', async ({ page }) => {
|
|
await page.goto('/login');
|
|
|
|
// Test mobile form layout
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
|
|
const form = page.locator('[data-testid="login-form"]');
|
|
await expect(form).toBeVisible();
|
|
|
|
// Form should take most of the width on mobile
|
|
const formBox = await form.boundingBox();
|
|
if (formBox) {
|
|
expect(formBox.width).toBeGreaterThan(VIEWPORTS.mobile.width * 0.8);
|
|
}
|
|
|
|
await takeScreenshot(page, 'mobile-form-layout');
|
|
|
|
// Test desktop form layout
|
|
await page.setViewportSize(VIEWPORTS.desktop);
|
|
|
|
// Form should be centered and more constrained on desktop
|
|
const desktopFormBox = await form.boundingBox();
|
|
if (desktopFormBox) {
|
|
expect(desktopFormBox.width).toBeLessThan(VIEWPORTS.desktop.width * 0.6);
|
|
}
|
|
|
|
await takeScreenshot(page, 'desktop-form-layout');
|
|
});
|
|
|
|
test('should handle button sizes across devices', async ({ page }) => {
|
|
await page.goto('/login');
|
|
|
|
// Test mobile button sizes
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
|
|
const button = page.locator('[data-testid="login-button"]');
|
|
const mobileButtonBox = await button.boundingBox();
|
|
|
|
// Buttons should be touch-friendly on mobile (min 44px height)
|
|
if (mobileButtonBox) {
|
|
expect(mobileButtonBox.height).toBeGreaterThanOrEqual(40);
|
|
}
|
|
|
|
await takeScreenshot(page, 'mobile-button-sizes');
|
|
|
|
// Test desktop button sizes
|
|
await page.setViewportSize(VIEWPORTS.desktop);
|
|
|
|
const desktopButtonBox = await button.boundingBox();
|
|
|
|
await takeScreenshot(page, 'desktop-button-sizes');
|
|
|
|
// Compare button sizes
|
|
if (mobileButtonBox && desktopButtonBox) {
|
|
console.log('Mobile button height:', mobileButtonBox.height);
|
|
console.log('Desktop button height:', desktopButtonBox.height);
|
|
}
|
|
});
|
|
|
|
test('should handle navigation consistency across devices', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
|
|
// Test that navigation items are consistent across devices
|
|
const navItems = ['Dashboard', 'Events'];
|
|
|
|
for (const viewport of Object.values(VIEWPORTS)) {
|
|
await page.setViewportSize(viewport);
|
|
await page.waitForTimeout(500); // Allow layout to adjust
|
|
|
|
// Open mobile menu if needed
|
|
const mobileMenuButton = page.locator('[data-testid="mobile-menu-button"]');
|
|
if (await mobileMenuButton.isVisible()) {
|
|
await mobileMenuButton.click();
|
|
}
|
|
|
|
// Check that all navigation items are present
|
|
for (const item of navItems) {
|
|
await expect(page.locator(`[data-testid="nav-${item.toLowerCase()}"]`)).toBeVisible();
|
|
}
|
|
|
|
await takeScreenshot(page, `navigation-${viewport.width}x${viewport.height}`);
|
|
|
|
// Close mobile menu if opened
|
|
if (await mobileMenuButton.isVisible()) {
|
|
await page.click('body');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should handle image scaling and loading', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
|
|
// Test mobile image handling
|
|
await page.setViewportSize(VIEWPORTS.mobile);
|
|
|
|
// Check user avatar scaling
|
|
const avatar = page.locator('[data-testid="user-avatar"]');
|
|
if (await avatar.isVisible()) {
|
|
const avatarBox = await avatar.boundingBox();
|
|
|
|
if (avatarBox) {
|
|
// Avatar should be reasonably sized for mobile
|
|
expect(avatarBox.width).toBeGreaterThan(20);
|
|
expect(avatarBox.width).toBeLessThan(80);
|
|
}
|
|
}
|
|
|
|
await takeScreenshot(page, 'mobile-image-scaling');
|
|
|
|
// Test desktop image handling
|
|
await page.setViewportSize(VIEWPORTS.desktop);
|
|
|
|
if (await avatar.isVisible()) {
|
|
const desktopAvatarBox = await avatar.boundingBox();
|
|
await takeScreenshot(page, 'desktop-image-scaling');
|
|
|
|
if (desktopAvatarBox) {
|
|
console.log('Desktop avatar size:', desktopAvatarBox.width, 'x', desktopAvatarBox.height);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should handle orientation changes on mobile', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
|
|
// Test portrait orientation
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await takeScreenshot(page, 'mobile-portrait');
|
|
|
|
// Test landscape orientation
|
|
await page.setViewportSize({ width: 667, height: 375 });
|
|
await takeScreenshot(page, 'mobile-landscape');
|
|
|
|
// Navigation should still work in landscape
|
|
const mobileMenuButton = page.locator('[data-testid="mobile-menu-button"]');
|
|
if (await mobileMenuButton.isVisible()) {
|
|
await mobileMenuButton.click();
|
|
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible();
|
|
await takeScreenshot(page, 'mobile-landscape-menu-open');
|
|
}
|
|
});
|
|
}); |