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'); } }); });