import { test, expect } from '@playwright/test'; /** * iPad UI Optimization Test * * Tests the UI responsiveness on iPad viewport sizes: * - Portrait iPad: 768x1024 * - Landscape iPad: 1024x768 * * Verifies: * 1. Mobile navigation drawer works on portrait iPad * 2. Desktop navigation appears on landscape iPad * 3. Tables convert to cards on portrait, show as tables on landscape * 4. Touch targets are properly sized * 5. Modals are properly sized for both orientations */ test.describe('iPad UI - Portrait Mode (768x1024)', () => { test.use({ viewport: { width: 768, height: 1024 } }); test('should show mobile navigation drawer', async ({ page }) => { test.setTimeout(120000); // 2 minutes // Login first await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { // Try automatic login await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"], button:has-text("Continue"), button:has-text("Log in")').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { // Manual login fallback await page.waitForURL('**/dashboard', { timeout: 180000 }); } // Verify we're on dashboard expect(page.url()).toContain('/dashboard'); // Hamburger menu button should be visible on portrait iPad const hamburgerButton = page.locator('button[aria-label="Open menu"]'); await expect(hamburgerButton).toBeVisible(); // Desktop navigation should be hidden const desktopNav = page.locator('nav a:has-text("VIPs")').first(); await expect(desktopNav).not.toBeVisible(); // Click hamburger to open drawer await hamburgerButton.click(); // Drawer should appear const drawer = page.locator('text=VIP Coordinator').nth(1); // Second instance (in drawer) await expect(drawer).toBeVisible(); // Drawer should have navigation links await expect(page.locator('a:has-text("Dashboard")').nth(1)).toBeVisible(); await expect(page.locator('a:has-text("VIPs")').nth(1)).toBeVisible(); // Close button should be visible and properly sized const closeButton = page.locator('button[aria-label="Close menu"]'); await expect(closeButton).toBeVisible(); // Verify close button size (should be at least 44x44px) const closeBox = await closeButton.boundingBox(); expect(closeBox?.width).toBeGreaterThanOrEqual(44); expect(closeBox?.height).toBeGreaterThanOrEqual(44); await page.screenshot({ path: 'test-results/ipad-portrait-drawer-open.png', fullPage: true }); }); test('should show card layout for VIP list', async ({ page }) => { test.setTimeout(120000); // Login and navigate await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"]').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { await page.waitForURL('**/dashboard', { timeout: 180000 }); } // Open drawer and navigate to VIPs await page.locator('button[aria-label="Open menu"]').click(); await page.locator('a:has-text("VIPs")').nth(1).click(); await page.waitForLoadState('networkidle'); // Desktop table should be hidden const desktopTable = page.locator('table').first(); await expect(desktopTable).not.toBeVisible(); // Card layout should be visible // Look for card-specific elements (cards have rounded-lg class and shadow) const cards = page.locator('.bg-white.shadow.rounded-lg.p-4'); const cardCount = await cards.count(); if (cardCount > 0) { console.log(`✓ Found ${cardCount} VIP cards in portrait mode`); // Verify first card has proper touch targets const firstCard = cards.first(); const editButton = firstCard.locator('button:has-text("Edit")'); const editBox = await editButton.boundingBox(); expect(editBox?.height).toBeGreaterThanOrEqual(44); console.log(`✓ Edit button height: ${editBox?.height}px (minimum 44px)`); } else { console.log('ℹ No VIPs in database - card layout not tested'); } await page.screenshot({ path: 'test-results/ipad-portrait-vip-cards.png', fullPage: true }); }); test('should properly size modal forms', async ({ page }) => { test.setTimeout(120000); // Login and navigate await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"]').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { await page.waitForURL('**/dashboard', { timeout: 180000 }); } // Navigate to VIPs page await page.locator('button[aria-label="Open menu"]').click(); await page.locator('a:has-text("VIPs")').nth(1).click(); await page.waitForLoadState('networkidle'); // Click "Add VIP" button await page.locator('button:has-text("Add VIP")').click(); await page.waitForTimeout(500); // Modal should appear const modal = page.locator('text=Add New VIP').first(); await expect(modal).toBeVisible(); // Verify form inputs have proper height const nameInput = page.locator('input[name="name"]'); const inputBox = await nameInput.boundingBox(); expect(inputBox?.height).toBeGreaterThanOrEqual(44); console.log(`✓ Input height: ${inputBox?.height}px (minimum 44px)`); // Verify submit button has proper height const submitButton = page.locator('button:has-text("Create VIP")'); const buttonBox = await submitButton.boundingBox(); expect(buttonBox?.height).toBeGreaterThanOrEqual(44); console.log(`✓ Button height: ${buttonBox?.height}px (minimum 44px)`); // Modal should not be wider than viewport const modalContainer = page.locator('.bg-white.rounded-lg.shadow-xl').first(); const modalBox = await modalContainer.boundingBox(); expect(modalBox?.width).toBeLessThanOrEqual(768); console.log(`✓ Modal width: ${modalBox?.width}px (viewport: 768px)`); await page.screenshot({ path: 'test-results/ipad-portrait-modal.png', fullPage: true }); }); }); test.describe('iPad UI - Landscape Mode (1024x768)', () => { test.use({ viewport: { width: 1024, height: 768 } }); test('should show desktop navigation', async ({ page }) => { test.setTimeout(120000); // Login await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"]').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { await page.waitForURL('**/dashboard', { timeout: 180000 }); } // Desktop navigation should be visible const desktopNav = page.locator('nav a:has-text("VIPs")').first(); await expect(desktopNav).toBeVisible(); // Hamburger menu should be hidden const hamburgerButton = page.locator('button[aria-label="Open menu"]'); await expect(hamburgerButton).not.toBeVisible(); // Verify navigation has clickable links await expect(page.locator('nav a:has-text("Dashboard")')).toBeVisible(); await expect(page.locator('nav a:has-text("War Room")')).toBeVisible(); await expect(page.locator('nav a:has-text("Drivers")')).toBeVisible(); await page.screenshot({ path: 'test-results/ipad-landscape-navigation.png', fullPage: true }); }); test('should show table layout for VIP list', async ({ page }) => { test.setTimeout(120000); // Login and navigate await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"]').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { await page.waitForURL('**/dashboard', { timeout: 180000 }); } // Navigate to VIPs using desktop nav await page.locator('nav a:has-text("VIPs")').first().click(); await page.waitForLoadState('networkidle'); // Desktop table should be visible const desktopTable = page.locator('table').first(); await expect(desktopTable).toBeVisible(); // Table headers should be visible await expect(page.locator('th:has-text("Name")')).toBeVisible(); await expect(page.locator('th:has-text("Organization")')).toBeVisible(); await expect(page.locator('th:has-text("Department")')).toBeVisible(); await page.screenshot({ path: 'test-results/ipad-landscape-vip-table.png', fullPage: true }); }); test('should show stats in 4-column grid', async ({ page }) => { test.setTimeout(120000); // Login await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"]').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { await page.waitForURL('**/dashboard', { timeout: 180000 }); } // Should be on dashboard expect(page.url()).toContain('/dashboard'); // Stats cards should be visible await expect(page.locator('text=Total VIPs')).toBeVisible(); await expect(page.locator('text=Active Drivers')).toBeVisible(); await expect(page.locator('text=Events Today')).toBeVisible(); await expect(page.locator('text=Flights Today')).toBeVisible(); await page.screenshot({ path: 'test-results/ipad-landscape-dashboard.png', fullPage: true }); }); }); test.describe('iPad UI - Touch Target Verification', () => { test.use({ viewport: { width: 768, height: 1024 } }); test('all interactive elements meet 44px minimum', async ({ page }) => { test.setTimeout(120000); // Login await page.goto('/login'); await page.locator('button:has-text("Sign in with Auth0")').click(); try { await page.locator('input[name="username"], input[type="email"]').first().fill('test@test.com'); await page.locator('input[name="password"], input[type="password"]').first().fill('P@ssw0rd!'); await page.locator('button[type="submit"]').first().click(); await page.waitForURL('**/dashboard', { timeout: 30000 }); } catch { await page.waitForURL('**/dashboard', { timeout: 180000 }); } const touchTargets = [ { name: 'Hamburger Menu', selector: 'button[aria-label="Open menu"]' }, { name: 'Sign Out Button', selector: 'button:has-text("Sign Out")' }, ]; console.log('\n🎯 Verifying Touch Target Sizes:'); console.log('================================'); for (const target of touchTargets) { const element = page.locator(target.selector).first(); if (await element.isVisible()) { const box = await element.boundingBox(); if (box) { const meetsMinimum = box.height >= 44 && box.width >= 44; const status = meetsMinimum ? '✓' : '✗'; console.log(`${status} ${target.name}:`); console.log(` Width: ${box.width}px, Height: ${box.height}px`); expect(box.height).toBeGreaterThanOrEqual(44); } } } console.log('================================\n'); }); });