import { test, expect } from '@playwright/test'; const WA_HREF = 'https://wa.me/919561417403?text=Hi%20WorkRoot%2C%20I%27d%20like%20to%20discuss%20a%20project.'; /** * Verifies the global WhatsApp FAB and the Mobile Sticky CTA bar render * correctly across viewports, expose working contact links, and never * cause horizontal overflow on a 360px viewport. */ test.describe('WhatsApp FAB (global)', () => { test('renders on every page at desktop width', async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); for (const path of ['/', '/about', '/services', '/contact']) { await page.goto(path); const fab = page.getByTestId('whatsapp-fab'); await expect(fab, `FAB missing on ${path}`).toBeVisible(); await expect(fab).toHaveAttribute('href', WA_HREF); await expect(fab).toHaveAttribute('target', '_blank'); await expect(fab).toHaveAttribute('rel', /noopener/); await expect(fab).toHaveAttribute('aria-label', 'Chat on WhatsApp'); } }); test('renders on home at 360px mobile width', async ({ page }) => { await page.setViewportSize({ width: 360, height: 800 }); await page.goto('/'); const fab = page.getByTestId('whatsapp-fab'); await expect(fab).toBeVisible(); // FAB is 56x56 const box = await fab.boundingBox(); expect(box).not.toBeNull(); expect(box!.width).toBeGreaterThanOrEqual(48); expect(box!.height).toBeGreaterThanOrEqual(48); // FAB does not horizontally overflow the viewport expect(box!.x + box!.width).toBeLessThanOrEqual(360); }); }); test.describe('Mobile Sticky CTA Bar', () => { test('visible at 360px and exposes 3 working actions', async ({ page }) => { await page.setViewportSize({ width: 360, height: 800 }); await page.goto('/'); const bar = page.getByTestId('mobile-sticky-cta-bar'); await expect(bar).toBeVisible(); const callBtn = page.getByTestId('mobile-cta-call'); const waBtn = page.getByTestId('mobile-cta-whatsapp'); const quoteBtn = page.getByTestId('mobile-cta-quote'); await expect(callBtn).toBeVisible(); await expect(waBtn).toBeVisible(); await expect(quoteBtn).toBeVisible(); await expect(callBtn).toHaveAttribute('href', 'tel:+919561417403'); await expect(waBtn).toHaveAttribute('href', WA_HREF); await expect(quoteBtn).toHaveAttribute('href', '/contact'); // Each action is at least a 48px tall touch target for (const btn of [callBtn, waBtn, quoteBtn]) { const box = await btn.boundingBox(); expect(box).not.toBeNull(); expect(box!.height).toBeGreaterThanOrEqual(48); } // The bar spans the full viewport width and is pinned to the bottom const barBox = await bar.boundingBox(); expect(barBox).not.toBeNull(); expect(barBox!.x).toBeLessThanOrEqual(0.5); expect(barBox!.width).toBeGreaterThanOrEqual(355); expect(barBox!.width).toBeLessThanOrEqual(360); // No horizontal overflow at 360px (match existing site tolerance: viewport + 20px) const bodyWidth = await page.evaluate(() => document.body.scrollWidth); expect(bodyWidth).toBeLessThanOrEqual(360 + 20); // The page must not be horizontally scrolled — confirms fixed elements fit. const scrollX = await page.evaluate(() => window.scrollX); expect(scrollX).toBe(0); }); test('hidden at >=768px (desktop / tablet landscape)', async ({ page }) => { await page.setViewportSize({ width: 1280, height: 800 }); await page.goto('/'); const bar = page.getByTestId('mobile-sticky-cta-bar'); // Either not visible, or has md:hidden applied (display:none via media query) await expect(bar).toBeHidden(); }); test('renders on every key page on mobile', async ({ page }) => { await page.setViewportSize({ width: 360, height: 800 }); for (const path of ['/', '/about', '/services', '/portfolio', '/blog', '/contact']) { await page.goto(path); await expect( page.getByTestId('mobile-sticky-cta-bar'), `Sticky CTA bar missing on ${path}`, ).toBeVisible(); } }); test('FAB sits above the sticky bar on mobile (no overlap)', async ({ page }) => { await page.setViewportSize({ width: 360, height: 800 }); await page.goto('/'); const fab = page.getByTestId('whatsapp-fab'); const bar = page.getByTestId('mobile-sticky-cta-bar'); const fabBox = await fab.boundingBox(); const barBox = await bar.boundingBox(); expect(fabBox).not.toBeNull(); expect(barBox).not.toBeNull(); // The bottom of the FAB sits above the top of the sticky bar. expect(fabBox!.y + fabBox!.height).toBeLessThanOrEqual(barBox!.y); }); });