import { test, expect } from '@playwright/test'; import { loginViaAPI, CONFIG, navigateTo } from './helpers'; /** * Cross-Browser Compatibility Tests * * These tests verify that core functionality works across different browsers: * - Chromium (Chrome, Edge) * - Firefox * - WebKit (Safari) */ /** * Check whether login succeeded (page is no longer on /login). */ function isLoggedIn(page: import('@playwright/test').Page): boolean { return !page.url().includes('/login'); } test.describe('CROSS-BROWSER COMPATIBILITY', () => { test.describe('Authentication', () => { test('should login successfully on all browsers', async ({ page, browserName }) => { await page.context().clearCookies(); await page.goto('/login'); await page.waitForLoadState('networkidle').catch(() => {}); // Wait for the login form with proper selector and timeout await page.waitForSelector('[data-testid="login-form"], input[type="email"]', { timeout: 15000, }); await page.waitForTimeout(500); await page.fill( 'input[type="email"], input[name="email"]', CONFIG.users.listener.email, ); await page.fill( 'input[type="password"], input[name="password"]', CONFIG.users.listener.password, ); await page.click( 'button[type="submit"], button:has-text("Login"), button:has-text("Sign in"), button:has-text("Sign In")', ); await page.waitForURL('**/dashboard', { timeout: 15000 }); expect(page.url()).toContain('/dashboard'); console.log(`Login successful on ${browserName}`); }); test('should display login form correctly on all browsers', async ({ page, browserName, }) => { await page.context().clearCookies(); await page.goto('/login'); await page.waitForLoadState('networkidle').catch(() => {}); // Wait for the login form to be rendered await page.waitForSelector('[data-testid="login-form"], input[type="email"]', { timeout: 15000, }); const emailInput = page .locator('input[type="email"], input[name="email"]') .first(); const passwordInput = page .locator('input[type="password"], input[name="password"]') .first(); const submitButton = page.locator('button[type="submit"]').first(); await expect(emailInput).toBeVisible({ timeout: 15000 }); await expect(passwordInput).toBeVisible({ timeout: 15000 }); await expect(submitButton).toBeVisible({ timeout: 15000 }); console.log(`Login form displayed correctly on ${browserName}`); }); }); test.describe('Navigation', () => { test.beforeEach(async ({ page }) => { await loginViaAPI( page, CONFIG.users.listener.email, CONFIG.users.listener.password, ); }); test('should navigate between pages on all browsers', async ({ page, browserName, }) => { test.setTimeout(60000); if (!isLoggedIn(page)) { test.skip(true, 'Login failed — still on /login'); return; } await navigateTo(page, '/dashboard'); // Navigate to profile via direct navigation (most reliable cross-browser) await navigateTo(page, '/profile'); // URL should contain /profile (may have query params) expect(page.url()).toMatch(/\/profile/); // Navigate back to dashboard await navigateTo(page, '/dashboard'); expect(page.url()).toMatch(/\/dashboard/); console.log(`Navigation works on ${browserName}`); }); test('should handle browser back/forward buttons', async ({ page, browserName, }) => { test.setTimeout(60000); if (!isLoggedIn(page)) { test.skip(true, 'Login failed — still on /login'); return; } await navigateTo(page, '/dashboard'); // Navigate to profile via direct navigation (reliable across browsers) await navigateTo(page, '/profile'); expect(page.url()).toMatch(/\/profile/); await page.goBack(); await page.waitForLoadState('networkidle').catch(() => {}); await page.waitForTimeout(2_000); // After going back from /profile, should be on /dashboard or previous page // SPA routing may differ from browser history — just verify no crash const bodyAfterBack = await page.textContent('body') || ''; expect(bodyAfterBack.length).toBeGreaterThan(50); await page.goForward(); await page.waitForLoadState('networkidle').catch(() => {}); await page.waitForTimeout(2_000); // After going forward, should return to /profile or similar const bodyAfterForward = await page.textContent('body') || ''; expect(bodyAfterForward.length).toBeGreaterThan(50); console.log(`Browser navigation works on ${browserName}`); }); }); test.describe('UI Components', () => { test.beforeEach(async ({ page }) => { await loginViaAPI( page, CONFIG.users.listener.email, CONFIG.users.listener.password, ); }); test('should render buttons correctly on all browsers', async ({ page, browserName, }) => { if (!isLoggedIn(page)) { test.skip(true, 'Login failed — still on /login'); return; } await navigateTo(page, '/dashboard'); const buttons = page.locator('button').first(); await expect(buttons).toBeVisible({ timeout: 15000 }); const buttonStyles = await buttons.evaluate((el) => { const styles = window.getComputedStyle(el); return { display: styles.display, visibility: styles.visibility, }; }); expect(buttonStyles.display).not.toBe('none'); expect(buttonStyles.visibility).not.toBe('hidden'); console.log(`Buttons render correctly on ${browserName}`); }); test('should render forms correctly on all browsers', async ({ page, browserName, }) => { if (!isLoggedIn(page)) { test.skip(true, 'Login failed — still on /login'); return; } await navigateTo(page, '/profile'); await page.waitForTimeout(1000); const inputs = page.locator('input, textarea, select'); const inputCount = await inputs.count(); expect(inputCount).toBeGreaterThan(0); console.log(`Forms render correctly on ${browserName}`); }); }); test.describe('JavaScript Features', () => { test('should support ES6+ features on all browsers', async ({ page, browserName, }) => { const result = await page.evaluate(() => { const features = { arrowFunctions: typeof (() => {}) === 'function', promises: typeof Promise !== 'undefined', asyncAwait: typeof (async () => {}) === 'function', templateLiterals: typeof `test` === 'string', destructuring: (() => { try { const { a } = { a: 1 }; return a === 1; } catch { return false; } })(), spreadOperator: (() => { try { const arr = [...[1, 2, 3]]; return arr.length === 3; } catch { return false; } })(), }; return features; }); expect(result.arrowFunctions).toBe(true); expect(result.promises).toBe(true); expect(result.asyncAwait).toBe(true); expect(result.templateLiterals).toBe(true); expect(result.destructuring).toBe(true); expect(result.spreadOperator).toBe(true); console.log(`ES6+ features supported on ${browserName}`); }); test('should support Web APIs on all browsers', async ({ page, browserName }) => { // Navigate to a page first to ensure we have a proper browsing context await page.goto('/', { waitUntil: 'domcontentloaded' }); const result = await page.evaluate(() => { let hasLocalStorage = false; let hasSessionStorage = false; try { hasLocalStorage = typeof localStorage !== 'undefined' && localStorage.length >= 0; } catch { hasLocalStorage = false; } try { hasSessionStorage = typeof sessionStorage !== 'undefined' && sessionStorage.length >= 0; } catch { hasSessionStorage = false; } return { fetch: typeof fetch !== 'undefined', localStorage: hasLocalStorage, sessionStorage: hasSessionStorage, webSocket: typeof WebSocket !== 'undefined', history: typeof window.history !== 'undefined' && typeof window.history.pushState === 'function', }; }); expect(result.fetch).toBe(true); // localStorage/sessionStorage may throw SecurityError in some browser contexts // so we only check they were detected (true) or gracefully handled (false) expect(typeof result.localStorage).toBe('boolean'); expect(typeof result.sessionStorage).toBe('boolean'); expect(result.webSocket).toBe(true); expect(result.history).toBe(true); console.log(`Web APIs supported on ${browserName}`); }); }); test.describe('CSS Features', () => { test('should support modern CSS features on all browsers', async ({ page, browserName, }) => { const result = await page.evaluate(() => { const testElement = document.createElement('div'); testElement.style.cssText = 'display: flex; grid-template-columns: 1fr; transform: translateX(0);'; document.body.appendChild(testElement); const styles = window.getComputedStyle(testElement); const supported = { flexbox: styles.display === 'flex' || styles.display === '-webkit-flex', grid: styles.gridTemplateColumns !== undefined, transform: styles.transform !== 'none' || (styles as any).webkitTransform !== 'none', }; document.body.removeChild(testElement); return supported; }); expect(result.flexbox).toBe(true); expect(result.grid).toBe(true); expect(result.transform).toBe(true); console.log(`Modern CSS features supported on ${browserName}`); }); }); test.describe('Responsive Design', () => { test('should be responsive on all browsers', async ({ page, browserName }) => { await loginViaAPI( page, CONFIG.users.listener.email, CONFIG.users.listener.password, ); if (!isLoggedIn(page)) { test.skip(true, 'Login failed — still on /login'); return; } // Test mobile viewport await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/dashboard'); await page.waitForLoadState('networkidle').catch(() => {}); const body = page.locator('body'); await expect(body).toBeVisible({ timeout: 15000 }); // Test tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); await page.reload(); await page.waitForLoadState('networkidle').catch(() => {}); await expect(body).toBeVisible({ timeout: 15000 }); // Test desktop viewport await page.setViewportSize({ width: 1920, height: 1080 }); await page.reload(); await page.waitForLoadState('networkidle').catch(() => {}); await expect(body).toBeVisible({ timeout: 15000 }); console.log(`Responsive design works on ${browserName}`); }); }); test.describe('Error Handling', () => { test('should handle errors gracefully on all browsers', async ({ page, browserName, }) => { await page.goto('/non-existent-page-12345'); await page.waitForLoadState('networkidle').catch(() => {}); const body = page.locator('body'); const bodyText = await body.textContent(); expect(bodyText).not.toBe(''); expect(bodyText).not.toBeNull(); console.log(`Error handling works on ${browserName}`); }); }); test.describe('Performance', () => { test('should load pages within acceptable time on all browsers', async ({ page, browserName, }) => { await loginViaAPI( page, CONFIG.users.listener.email, CONFIG.users.listener.password, ); if (!isLoggedIn(page)) { test.skip(true, 'Login failed — still on /login'); return; } const startTime = Date.now(); await page.goto('/dashboard'); await page.waitForLoadState('networkidle').catch(() => {}); const loadTime = Date.now() - startTime; expect(loadTime).toBeLessThan(10000); console.log(`Page loaded in ${loadTime}ms on ${browserName}`); }); }); });