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
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:
co-authored by
Claude Opus 4.8
parent
870075b33f
commit
5a6db9c20f
@@ -1,20 +1,25 @@
|
||||
---
|
||||
// InlineLeadForm — Three-field lead capture rendered inline on the homepage.
|
||||
// InlineLeadForm — Compact lead capture rendered inline on the homepage.
|
||||
//
|
||||
// The goal is to cut the friction step of routing every CTA through /contact.
|
||||
// Visitors who reach the bottom of the homepage can submit name + contact
|
||||
// (email OR phone) + project type without a navigation. The submission reuses
|
||||
// /api/contact under the hood and is tagged `source: 'homepage-inline'` so the
|
||||
// backend / analytics layer can segment these leads cleanly.
|
||||
// Goal: let visitors who reach the bottom of the homepage submit a project
|
||||
// enquiry without the friction of navigating to /contact. The submission
|
||||
// reuses the SAME /api/contact endpoint as the full contact page and is tagged
|
||||
// `source: 'homepage-inline'` so the backend / analytics layer can segment
|
||||
// these leads cleanly (it also routes them to the `[Project Inquiry]` admin
|
||||
// subject prefix).
|
||||
//
|
||||
// Why a single "contact" field instead of separate email + phone:
|
||||
// - 3 fields keeps the form psychologically tiny and dramatically lifts
|
||||
// completion. Real-world conversion testing repeatedly shows 3 → 5+ fields
|
||||
// is the biggest single-step drop-off on B2B lead forms.
|
||||
// - We sniff the value on submit. If it has an "@" we treat it as email.
|
||||
// Otherwise we treat it as a phone and synthesize a placeholder email to
|
||||
// satisfy the backend's strict email validation. The phone is also sent
|
||||
// through, so the admin email body shows the real reachable value.
|
||||
// Fields (kept to 5, mobile-first):
|
||||
// 1. Full name — required
|
||||
// 2. Email — required, type="email" (email keyboard on mobile)
|
||||
// 3. Phone — optional, type="tel" (numeric keypad on mobile)
|
||||
// 4. Project type — required, <select> mapped to the backend `subject`
|
||||
// 5. Message — optional <textarea>; synthesized when left blank so
|
||||
// the backend's message>=10 char rule always passes
|
||||
//
|
||||
// Only name + email + project type are *required* — phone and message stay
|
||||
// optional to keep completion friction low on mobile, which is where most of
|
||||
// these submissions originate. The backend already treats `phone` as optional
|
||||
// and only validates its format when present.
|
||||
---
|
||||
|
||||
<section
|
||||
@@ -108,42 +113,70 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Contact (email or phone) -->
|
||||
<!-- Email + Phone row -->
|
||||
<div class="grid sm:grid-cols-2 gap-5">
|
||||
<!-- Email (required) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-contact"
|
||||
for="inline-lead-email"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Email or phone <span class="text-red-500" aria-hidden="true">*</span>
|
||||
Email <span class="text-red-500" aria-hidden="true">*</span>
|
||||
<span class="sr-only">(required)</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="inline-lead-contact"
|
||||
name="contact"
|
||||
type="email"
|
||||
id="inline-lead-email"
|
||||
name="email"
|
||||
required
|
||||
autocomplete="email tel"
|
||||
maxlength="254"
|
||||
autocomplete="email"
|
||||
inputmode="email"
|
||||
aria-required="true"
|
||||
aria-describedby="inline-lead-contact-hint inline-lead-contact-error"
|
||||
placeholder="jane@company.com or +91 95614 17403"
|
||||
aria-describedby="inline-lead-email-error"
|
||||
placeholder="jane@company.com"
|
||||
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
|
||||
/>
|
||||
<p
|
||||
id="inline-lead-contact-hint"
|
||||
class="mt-1.5 text-xs text-secondary-500 dark:text-secondary-400"
|
||||
>
|
||||
Drop your best email or phone number — whichever is easiest.
|
||||
</p>
|
||||
<p
|
||||
id="inline-lead-contact-error"
|
||||
id="inline-lead-email-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please enter a valid email address or phone number.
|
||||
Please enter a valid email address.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Phone (optional) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-phone"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Phone
|
||||
<span class="font-normal text-secondary-400 dark:text-secondary-500">(optional)</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="inline-lead-phone"
|
||||
name="phone"
|
||||
autocomplete="tel"
|
||||
inputmode="tel"
|
||||
pattern="[+()0-9.\s\-]{7,}"
|
||||
maxlength="32"
|
||||
aria-describedby="inline-lead-phone-error"
|
||||
placeholder="+91 95614 17403"
|
||||
class="block w-full min-h-[48px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors"
|
||||
/>
|
||||
<p
|
||||
id="inline-lead-phone-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please enter a valid phone number, or leave it blank.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project type -->
|
||||
<div>
|
||||
<label
|
||||
@@ -184,6 +217,33 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Message (optional) -->
|
||||
<div>
|
||||
<label
|
||||
for="inline-lead-message"
|
||||
class="block text-sm font-semibold text-secondary-700 dark:text-secondary-200 mb-1.5"
|
||||
>
|
||||
Project details
|
||||
<span class="font-normal text-secondary-400 dark:text-secondary-500">(optional)</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="inline-lead-message"
|
||||
name="message"
|
||||
rows="3"
|
||||
maxlength="5000"
|
||||
aria-describedby="inline-lead-message-error"
|
||||
placeholder="A sentence or two about what you want to build, your timeline, or budget…"
|
||||
class="block w-full min-h-[96px] px-4 py-3 rounded-xl border border-secondary-200 dark:border-secondary-600 bg-secondary-50 dark:bg-secondary-700 text-secondary-900 dark:text-secondary-100 placeholder-secondary-400 dark:placeholder-secondary-500 text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/30 transition-colors resize-y"
|
||||
></textarea>
|
||||
<p
|
||||
id="inline-lead-message-error"
|
||||
class="mt-1.5 text-xs text-red-600 dark:text-red-400 hidden"
|
||||
role="alert"
|
||||
>
|
||||
Please add a little more detail (at least 10 characters), or leave it blank.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- General form error (server-side / network) -->
|
||||
<div
|
||||
id="inline-lead-form-error"
|
||||
@@ -267,13 +327,17 @@
|
||||
if (!form) return;
|
||||
|
||||
var nameInput = document.getElementById('inline-lead-name');
|
||||
var contactInput = document.getElementById('inline-lead-contact');
|
||||
var emailInput = document.getElementById('inline-lead-email');
|
||||
var phoneInput = document.getElementById('inline-lead-phone');
|
||||
var projectTypeInput = document.getElementById('inline-lead-project-type');
|
||||
var messageInput = document.getElementById('inline-lead-message');
|
||||
var websiteInput = document.getElementById('inline-lead-website');
|
||||
|
||||
var nameError = document.getElementById('inline-lead-name-error');
|
||||
var contactError = document.getElementById('inline-lead-contact-error');
|
||||
var emailError = document.getElementById('inline-lead-email-error');
|
||||
var phoneError = document.getElementById('inline-lead-phone-error');
|
||||
var projectTypeError = document.getElementById('inline-lead-project-type-error');
|
||||
var messageError = document.getElementById('inline-lead-message-error');
|
||||
var formError = document.getElementById('inline-lead-form-error');
|
||||
|
||||
var submitBtn = document.getElementById('inline-lead-submit');
|
||||
@@ -283,7 +347,7 @@
|
||||
var successPanel = document.getElementById('inline-lead-success');
|
||||
|
||||
// Tolerant phone test: allow +, digits, spaces, dashes, parens, dots.
|
||||
// We require at least 7 digits so single-digit / accidental presses fail.
|
||||
// Require at least 7 digits so single-digit / accidental presses fail.
|
||||
var PHONE_REGEX = /^[+]?[()\d\s.\-]{7,}$/;
|
||||
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
@@ -311,6 +375,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function phoneIsValid(val) {
|
||||
return PHONE_REGEX.test(val) && digitsOnly(val).length >= 7;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
var ok = true;
|
||||
|
||||
@@ -322,14 +390,21 @@
|
||||
showError(nameError, false);
|
||||
}
|
||||
|
||||
var contactVal = (contactInput.value || '').trim();
|
||||
var isEmail = EMAIL_REGEX.test(contactVal);
|
||||
var isPhone = PHONE_REGEX.test(contactVal) && digitsOnly(contactVal).length >= 7;
|
||||
if (!contactVal || (!isEmail && !isPhone)) {
|
||||
showError(contactError, true);
|
||||
var emailVal = (emailInput.value || '').trim();
|
||||
if (!EMAIL_REGEX.test(emailVal)) {
|
||||
showError(emailError, true);
|
||||
ok = false;
|
||||
} else {
|
||||
showError(contactError, false);
|
||||
showError(emailError, false);
|
||||
}
|
||||
|
||||
// Phone is optional — only validate format when something was typed.
|
||||
var phoneVal = (phoneInput.value || '').trim();
|
||||
if (phoneVal && !phoneIsValid(phoneVal)) {
|
||||
showError(phoneError, true);
|
||||
ok = false;
|
||||
} else {
|
||||
showError(phoneError, false);
|
||||
}
|
||||
|
||||
var projectVal = projectTypeInput.value || '';
|
||||
@@ -340,13 +415,24 @@
|
||||
showError(projectTypeError, false);
|
||||
}
|
||||
|
||||
// Message is optional — only enforce the min length when something was typed.
|
||||
var messageVal = (messageInput.value || '').trim();
|
||||
if (messageVal && messageVal.length < 10) {
|
||||
showError(messageError, true);
|
||||
ok = false;
|
||||
} else {
|
||||
showError(messageError, false);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
// Live-validate on input/change so the error UI doesn't linger after fix.
|
||||
// Live-validate on blur/change so the error UI clears once a field is fixed.
|
||||
nameInput.addEventListener('blur', validate);
|
||||
contactInput.addEventListener('blur', validate);
|
||||
emailInput.addEventListener('blur', validate);
|
||||
phoneInput.addEventListener('blur', validate);
|
||||
projectTypeInput.addEventListener('change', validate);
|
||||
messageInput.addEventListener('blur', validate);
|
||||
|
||||
// Map the user-friendly project type label to the backend subject code so
|
||||
// the existing /api/contact validator passes. Anything unmapped → 'other'.
|
||||
@@ -359,41 +445,39 @@
|
||||
};
|
||||
|
||||
function buildPayload() {
|
||||
var contactVal = (contactInput.value || '').trim();
|
||||
var isEmail = EMAIL_REGEX.test(contactVal);
|
||||
var nameVal = (nameInput.value || '').trim();
|
||||
var emailVal = (emailInput.value || '').trim();
|
||||
var phoneVal = (phoneInput.value || '').trim();
|
||||
var projectType = projectTypeInput.value || 'Other';
|
||||
var subject = SUBJECT_BY_PROJECT_TYPE[projectType] || 'other';
|
||||
var messageVal = (messageInput.value || '').trim();
|
||||
|
||||
var email, phone;
|
||||
if (isEmail) {
|
||||
email = contactVal;
|
||||
phone = undefined;
|
||||
// The backend requires a message of at least 10 characters. When the
|
||||
// visitor leaves the (optional) details blank, synthesize a concise
|
||||
// summary so the submission still goes through and the admin email
|
||||
// carries the useful context.
|
||||
var message;
|
||||
if (messageVal.length >= 10) {
|
||||
message = messageVal;
|
||||
} else {
|
||||
// Treat as phone. Synthesize a stable placeholder email so the
|
||||
// backend's strict email validator passes — the admin notification
|
||||
// surfaces the real phone in both Phone: and Message: fields so the
|
||||
// team always has a real way to reach the lead.
|
||||
var digits = digitsOnly(contactVal) || 'unknown';
|
||||
email = 'lead-' + digits + '@homepage.workroot.in';
|
||||
phone = contactVal;
|
||||
}
|
||||
|
||||
var message =
|
||||
'New inline lead from the homepage.\n\n' +
|
||||
message =
|
||||
'New project enquiry from the homepage form.\n\n' +
|
||||
'Project type: ' + projectType + '\n' +
|
||||
'Preferred contact: ' + contactVal;
|
||||
'Name: ' + nameVal +
|
||||
(phoneVal ? '\nPhone: ' + phoneVal : '') +
|
||||
(messageVal ? '\n\nNote: ' + messageVal : '');
|
||||
}
|
||||
|
||||
var payload = {
|
||||
name: nameVal,
|
||||
email: email,
|
||||
email: emailVal,
|
||||
subject: subject,
|
||||
message: message,
|
||||
source: 'homepage-inline',
|
||||
projectType: projectType,
|
||||
website: (websiteInput && websiteInput.value) || '',
|
||||
};
|
||||
if (phone) payload.phone = phone;
|
||||
if (phoneVal) payload.phone = phoneVal;
|
||||
return payload;
|
||||
}
|
||||
|
||||
|
||||
@@ -744,9 +744,11 @@ const techStack = [
|
||||
|
||||
<!-- Inline Lead Form — sits between the tech-stack social-proof block
|
||||
and the final CTA band so visitors who are ready to start a project can
|
||||
submit a 3-field enquiry without leaving the homepage. Submits to the
|
||||
same /api/contact endpoint and is tagged `source: 'homepage-inline'` so
|
||||
the lead can be segmented downstream. -->
|
||||
submit a compact enquiry (name, email, phone, project type, message)
|
||||
without leaving the homepage. Required fields are kept to name + email +
|
||||
project type for a low-friction mobile flow. Submits to the same
|
||||
/api/contact endpoint and is tagged `source: 'homepage-inline'` so the
|
||||
lead can be segmented downstream. -->
|
||||
<InlineLeadForm />
|
||||
|
||||
<!-- Homepage FAQ — single canonical FAQ section for /. Covers the cost /
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user