feat(contact): add global WhatsApp FAB and mobile sticky CTA bar
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
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
- 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.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user