Files
CompanySite/tests/sticky-cta.spec.ts
T
platform-mcp 85965e1a00
Deploy to Production / Build & Verify (push) Has been cancelled
Deploy to Production / Pre-Deploy Tests (push) Has been cancelled
Deploy to Production / Deploy to Railway (push) Has been cancelled
Deploy to Production / Deploy to Render (push) Has been cancelled
Deploy to Production / Deploy to VPS (PM2) (push) Has been cancelled
Deploy to Production / Deploy to Fly.io (push) Has been cancelled
Deploy to Production / Post-Deploy Verification (push) Has been cancelled
Deploy to Production / Notify on Failure (push) Has been cancelled
E2E Test Suite / Critical User Journeys (push) Has been cancelled
E2E Test Suite / API Integration Tests (push) Has been cancelled
E2E Test Suite / Form Interaction Tests (push) Has been cancelled
E2E Test Suite / Destructive & Chaos Tests (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (chromium) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (firefox) (push) Has been cancelled
E2E Test Suite / Cross-Browser Regression (webkit) (push) Has been cancelled
E2E Test Suite / Mobile Device Tests (push) Has been cancelled
E2E Test Suite / Security Header Tests (push) Has been cancelled
E2E Test Suite / Test Report Summary (push) Has been cancelled
E2E Test Suite / Smoke Tests (P0) (push) Has been cancelled
Ping Search Engines / Notify Search Engines (push) Failing after 13m5s
feat(contact): add global WhatsApp FAB and mobile sticky CTA bar
- WhatsAppFab: 56px brand-green floating button (wa.me/919561417403), shown
  on every page; safe-area aware; lifts above the mobile CTA bar on <md.
- MobileStickyCtaBar: 3-column sticky bottom bar (Call / WhatsApp / Quote)
  visible only below the md breakpoint, with safe-area padding, 48px+ touch
  targets, and dark-mode-aware styling. Bottom spacer keeps the footer
  clear of the bar on mobile.
- Mounted both components globally via BaseLayout.
- Added tests/sticky-cta.spec.ts covering 360px and 1280px viewports,
  link integrity, no-overflow, hidden-at-md, and FAB/bar non-overlap.
2026-06-17 11:40:23 +05:30

123 lines
4.6 KiB
TypeScript

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