feat(homepage): expand inline lead form to name/email/phone/type/message
Ping Search Engines / Notify Search Engines (push) Successful in 3s
E2E Test Suite / Smoke Tests (P0) (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
Deploy to Production / Pre-Deploy Tests (push) Failing after 10m58s
Deploy to Production / Build & Verify (push) Failing after 16m37s
Deploy to Production / Deploy to Fly.io (push) Failing after 14m3s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 14m3s
Deploy to Production / Deploy to Render (push) Failing after 14m55s
Deploy to Production / Deploy to Railway (push) Failing after 14m55s
Deploy to Production / Post-Deploy Verification (push) Failing after 13m32s
Deploy to Production / Notify on Failure (push) Failing after 13m51s

Rework the homepage InlineLeadForm from a single combined "email or phone"
contact field to an explicit 5-field layout — name, email (type=email),
phone (type=tel), project type and an optional message — so each field gets
the correct mobile keyboard and leads carry a real email + phone separately.

- Required fields kept to name + email + project type for a low-friction
  mobile flow; phone and message are optional.
- Email uses type=email + inputmode=email; phone uses type=tel + inputmode=tel
  for the numeric keypad. Message is an optional textarea; when blank the
  client synthesizes a >=10 char summary so the /api/contact validator passes.
- Still posts to the same /api/contact endpoint tagged source=homepage-inline,
  delivering leads exactly the same way the /contact page does (no page nav).
- Update inline-lead-form.spec.ts to the new field set and keyboards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
WorkRoot Agent
2026-06-17 20:33:26 +05:30
co-authored by Claude Opus 4.8
parent 870075b33f
commit 5a6db9c20f
3 changed files with 200 additions and 96 deletions
+32 -14
View File
@@ -6,7 +6,10 @@ import { test, expect } from '@playwright/test';
* Covers:
* - The form is mounted on the homepage between FAQ/tech-stack and the
* final CTA band.
* - Filling all three fields and submitting calls /api/contact and shows
* - It exposes the compact 5-field layout (name, email, phone, project type,
* message) with the correct mobile keyboard hints per field. Only name,
* email and project type are required; phone and message are optional.
* - Filling the required fields and submitting calls /api/contact and shows
* an in-place thank-you state without a full-page navigation.
* - The submission is tagged `source: 'homepage-inline'` so leads can be
* segmented downstream.
@@ -15,7 +18,7 @@ import { test, expect } from '@playwright/test';
*/
test.describe('Inline Lead Form (homepage)', () => {
test('form is visible on the homepage and has the three required fields', async ({ page }) => {
test('form is visible on the homepage with the compact field set and mobile keyboards', async ({ page }) => {
await page.goto('/');
const form = page.getByTestId('inline-lead-form');
@@ -23,8 +26,10 @@ test.describe('Inline Lead Form (homepage)', () => {
await expect(form).toBeVisible();
await expect(page.locator('#inline-lead-name')).toBeVisible();
await expect(page.locator('#inline-lead-contact')).toBeVisible();
await expect(page.locator('#inline-lead-email')).toBeVisible();
await expect(page.locator('#inline-lead-phone')).toBeVisible();
await expect(page.locator('#inline-lead-project-type')).toBeVisible();
await expect(page.locator('#inline-lead-message')).toBeVisible();
// Touch-target sanity: the submit button must be at least 48px tall.
const submit = page.locator('#inline-lead-submit');
@@ -32,9 +37,17 @@ test.describe('Inline Lead Form (homepage)', () => {
expect(submitBox).not.toBeNull();
expect(submitBox!.height).toBeGreaterThanOrEqual(48);
// Correct mobile-friendly attributes on the contact field.
await expect(page.locator('#inline-lead-contact')).toHaveAttribute('autocomplete', 'email tel');
await expect(page.locator('#inline-lead-contact')).toHaveAttribute('inputmode', 'email');
// Correct mobile keyboard per field.
await expect(page.locator('#inline-lead-email')).toHaveAttribute('type', 'email');
await expect(page.locator('#inline-lead-email')).toHaveAttribute('inputmode', 'email');
await expect(page.locator('#inline-lead-email')).toHaveAttribute('autocomplete', 'email');
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('type', 'tel');
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('inputmode', 'tel');
await expect(page.locator('#inline-lead-phone')).toHaveAttribute('autocomplete', 'tel');
// Phone + message are optional (no `required` attribute).
await expect(page.locator('#inline-lead-phone')).not.toHaveAttribute('required', /.*/);
await expect(page.locator('#inline-lead-message')).not.toHaveAttribute('required', /.*/);
});
test('blocks submit when required fields are empty', async ({ page }) => {
@@ -57,12 +70,12 @@ test.describe('Inline Lead Form (homepage)', () => {
// The blank-field errors should surface and the network should NOT be hit.
await expect(page.locator('#inline-lead-name-error')).toBeVisible();
await expect(page.locator('#inline-lead-contact-error')).toBeVisible();
await expect(page.locator('#inline-lead-email-error')).toBeVisible();
await expect(page.locator('#inline-lead-project-type-error')).toBeVisible();
expect(apiCalled).toBe(false);
});
test('fills and submits the form successfully with an email contact', async ({ page }) => {
test('fills the required fields and submits successfully', async ({ page }) => {
await page.goto('/');
const form = page.getByTestId('inline-lead-form');
await form.scrollIntoViewIfNeeded();
@@ -85,7 +98,7 @@ test.describe('Inline Lead Form (homepage)', () => {
});
await page.locator('#inline-lead-name').fill('Inline Lead Tester');
await page.locator('#inline-lead-contact').fill('inline-lead@example.com');
await page.locator('#inline-lead-email').fill('inline-lead@example.com');
await page.locator('#inline-lead-project-type').selectOption('Web App');
await page.locator('#inline-lead-submit').click();
@@ -103,11 +116,14 @@ test.describe('Inline Lead Form (homepage)', () => {
expect(captured.name).toBe('Inline Lead Tester');
expect(captured.email).toBe('inline-lead@example.com');
expect(captured.subject).toBe('web-development');
// Message is synthesized when left blank so the backend's >=10 char rule passes.
expect(typeof captured.message).toBe('string');
expect(captured.message.length).toBeGreaterThanOrEqual(10);
// Honeypot must be empty for a real user submission.
expect(captured.website ?? '').toBe('');
});
test('routes a phone-shaped contact value into the phone field', async ({ page }) => {
test('sends the phone and a typed message through their own fields', async ({ page }) => {
await page.goto('/');
const form = page.getByTestId('inline-lead-form');
await form.scrollIntoViewIfNeeded();
@@ -127,17 +143,19 @@ test.describe('Inline Lead Form (homepage)', () => {
});
await page.locator('#inline-lead-name').fill('Phone Lead');
await page.locator('#inline-lead-contact').fill('+91 95614 17403');
await page.locator('#inline-lead-email').fill('phone-lead@example.com');
await page.locator('#inline-lead-phone').fill('+91 95614 17403');
await page.locator('#inline-lead-project-type').selectOption('Government / GeM');
await page.locator('#inline-lead-message').fill('We need a GeM-ready procurement portal.');
await page.locator('#inline-lead-submit').click();
await expect(page.getByTestId('inline-lead-success')).toBeVisible();
expect(captured).toBeTruthy();
// The real email and phone are sent as-is in their own fields.
expect(captured.email).toBe('phone-lead@example.com');
expect(captured.phone).toBe('+91 95614 17403');
// The client synthesizes a placeholder email so the backend's strict email
// validator accepts phone-only contacts. The real phone is what matters.
expect(captured.email).toMatch(/^lead-\d+@homepage\.workroot\.in$/);
expect(captured.message).toBe('We need a GeM-ready procurement portal.');
expect(captured.subject).toBe('government-systems');
expect(captured.source).toBe('homepage-inline');
});