Skip to Content

Self-Signup — End-to-End Test Scenario

Overview

End-to-end test covering the full self-signup wizard — from landing on /signup through Razorpay payment to a fully provisioned tenant account ready for login.

Tested via: MCP Playwright browser (real browser, not CLI specs)
Portals: Dashboard (port 3000), API (port 3003)
Date verified: 2026-04-10 (full browser run — UPI success@razorpay, Professional plan, ₹58,998.82)


Prerequisites

  • All Docker containers healthy (docker ps)
  • All dev servers running (see startup procedure)
  • Scheduler worker running (pnpm --filter @leadmetrics/server-scheduler dev)
  • Razorpay test keys configured in apps/api/.env:
    RAZORPAY_KEY_ID=rzp_test_... RAZORPAY_KEY_SECRET=... RAZORPAY_WEBHOOK_SECRET=...
  • At least one Plan + Offering seeded in the database (run pnpm --filter api db:seed)
  • DASHBOARD_URL set in apps/api/.env (used in session-resume emails)

Actors

ActorCredentialsPortal
New customer (self-signup)(created during the test)Dashboard (3000)
Super admin (verification only)superadmin@leadmetrics.ai / adminManage (3001)

Session Storage Keys (reference)

KeySet byContent
signup_step1S1 on submit{ country, title, firstName, lastName, email, mobile, password }
signup_session_tokenS1 on submit (from API)JWT — passed as Bearer in all subsequent step calls
signup_step2S2 on submit{ companyName, companyWebsite, industry }
signup_step3S3 on submit{ state, hasGst, gstNumber, sezStatus, panNumber }

Scenario A — Full Happy Path (India, Professional Plan, UPI)

Step 1 — Contact Details (/signup)

  1. Navigate to http://localhost:3000/signup
  2. Select Country: India
  3. Fill contact fields:
    • Title: Mr.
    • First Name: Moble
    • Last Name: Joseph
    • Email: moble+signup-test-{timestamp}@leadmetrics.ai
    • Mobile: 9447884435
    • Password: Password123
  4. Click Continue →

API call: POST /auth/v1/register/start
Expected:

  • RegistrationSession row created in DB with currentStage: "step_1"
  • signup_session_token and signup_step1 written to sessionStorage
  • Navigated to /signup/company
  • Session-link email queued (template: signup-session-link)
  • Internal sales alert queued for scheduler (1 hr delay — signup.alert.internal)

Step 2 — Company Details (/signup/company)

  1. Fill company fields:
    • Company Name: Leadmetrics Test Pvt Ltd
    • Industry: Technology (select from dropdown)
    • Company Website: (leave blank — optional)
  2. Click Continue →

API call: POST /auth/v1/register/step/2 (with session token)
Expected:

  • RegistrationSession.currentStage updated to "step_2"
  • signup_step2 written to sessionStorage
  • Country is India → navigated to /signup/location
  • Early abandonment reminder queued for scheduler (1 hr — signup.reminder.early)

Step 3 — Location & Tax (/signup/location)

India-only step. Country shown read-only as “India” (from Step 1).

  1. Select State: Kerala
  2. Click Yes, I have GST button
  3. GST Number field appears — enter: 32AABCU9603R1ZX
  4. SEZ Status dropdown: No (default)
  5. PAN Number: AABCU9603R (optional — can leave blank)
  6. Click Continue →

API call: POST /auth/v1/register/step/3 (with session token)
Expected:

  • RegistrationSession.currentStage updated to "step_3"
  • signup_step3 written to sessionStorage with { state: "Kerala", hasGst: true, gstNumber: "32AABCU9603R1ZX", sezStatus: "no" }
  • Tax label below GST toggle updates to show: “GST Treatment: Registered Business — 18% CGST/SGST (intra-state)”
  • Navigated to /signup/plan

Step 4 — Plan & Payment (/signup/plan)

  1. Wait for plan cards to load — Professional plan card should appear (₹49,999/month + 18% tax)
  2. Click Professional plan card (should be pre-selected — violet border + checkmark badge)
  3. Fill billing address:
    • Billing Name: Leadmetrics Test Pvt Ltd (pre-filled from Step 2)
    • Billing Email: moble+signup-test-{timestamp}@leadmetrics.ai (pre-filled from Step 1)
    • Street Address: 42 MG Road, Infopark
    • City: Kochi
    • Postal / ZIP Code: 682030
    • State: Kerala (pre-filled from Step 3)
    • Country: India (read-only)
  4. Click Complete Sign-Up & Pay →

API call: POST /auth/v1/register/complete (with session token + plan + billing)
Expected:

  • Razorpay Checkout modal opens with amount ₹58,998.82 (₹49,999 × 1.18)
  • Modal header shows “Leadmetrics”
  • Mobile pre-filled as +91 94478 84435

Step 5 — Razorpay UPI Payment

  1. In the Razorpay modal, click UPI in the payment method list (left rail)
  2. Scroll to “Pay with UPI ID / Number” section
  3. Enter UPI ID: success@razorpay
  4. Click Verify and Pay
  5. Modal shows “Confirming Payment — This will only take a few seconds.”
  6. Modal transitions to “Payment Successful”

Expected on Razorpay success screen:

  • Heading: “Payment Successful”
  • “You will be redirected in 4 seconds”
  • Amount: ₹58,998.82
  • Method: UPI
  • Payment ID: pay_… (Razorpay test ID)

Step 6 — Payment Verification & Account Creation

After the Razorpay modal auto-redirects:

Page shows: “Confirming payment… Please wait while we verify your payment.”

API call (client-side): POST /auth/v1/register/payment/verify
Expected (server-side actions):

  • Razorpay signature verified
  • Tenant record created with status: onboarding, createdVia: "self-signup", signupSessionId
  • User record created and linked to tenant
  • TenantMember row created with role owner
  • Subscription record created (Professional plan)
  • Welcome email queued
  • RegistrationSession.currentStage set to "complete"

Final redirect: http://localhost:3000/login?registered=1


Step 7 — Login with New Account

Page shows: Login form with green banner “Account created! Sign in to get started.”

  1. Enter the email used in Step 1
  2. Enter password: Password123
  3. Click Sign in

Expected: Redirected to /dashboard showing the GettingStartedView (onboarding wizard — “AI agents are working on your account…”).


Portal: Manage → /tenants

  1. Log in as superadmin@leadmetrics.ai / admin
  2. Search for the new tenant by company name
  3. Click to open the tenant detail page

Expected:

  • Status: onboarding
  • Plan: Professional
  • createdVia: self-signup
  • Subscriptions tab: one active Professional subscription
  • Knowledge Base: 4 default datasets present

Scenario B — Non-India Customer (Step 3 Skipped)

For customers outside India, Step 3 (Location & Tax) is auto-completed server-side and the wizard skips directly from Step 2 to Step 4.

Steps

  1. Complete Step 1 with Country: United Arab Emirates (AE)
  2. Complete Step 2 with company details
  3. Expected: After submitting Step 2, page navigates directly to /signup/plan/signup/location is skipped
  4. Complete Step 4 — plan cards should show AED pricing if configured, else fallback to the default region

API behaviour: POST /auth/v1/register/step/2 detects non-India country and auto-creates the step 3 record server-side (autoSkipped: true in stepHistory).


Scenario C — Abandoned Signup (Reminder Emails)

Tests the scheduler’s abandonment handling. Requires scheduler worker to be running.

Setup

  1. Complete Step 1 only (do not proceed to Step 2)
  2. Note the signup_session_token from sessionStorage

What the scheduler does

After Step 1, two BullMQ jobs are enqueued with delays:

JobDelayTemplateHandler
signup.reminder.early1 hoursignup-reminder-earlyhandleSignupReminderEmail
signup.reminder.late24 hourssignup-reminder-latehandleSignupReminderEmail
signup.alert.internal1 hour (once per session)signup-alert-internalhandleSignupAlertEmail

Reminder email behaviour

  • handleSignupReminderEmail first checks RegistrationSession.currentStage
  • If currentStage is step_4 or "complete" — email is skipped (user finished or paid)
  • Otherwise — email sent with resumeUrl = {DASHBOARD_URL}/signup?session={token}
  • dedupeKey prevents duplicate sends: signup-reminder-early__{sessionId}

Internal alert behaviour

  • handleSignupAlertEmail sends to SIGNUP_ALERT_EMAIL (e.g. sales@leadmetrics.ai)
  • Includes: name, email, mobile, company, step number, timestamp
  • Priority: high
  • Does NOT query the DB — fire-and-forget
  • dedupeKey = signup-alert-internal__{email} (one alert per email address)

Testing without waiting 1 hour

To test immediately, reduce the job delay in the scheduler and trigger manually, or call the task handler directly in a unit test (see apps/servers/scheduler/src/__tests__/signup-emails.test.ts).


Scenario D — Session Resume (Returning Visitor)

A user who starts signup but closes the browser can resume via the session link email.

Flow

  1. User completes Step 1 → receives session-link email with URL: http://localhost:3000/signup?session={token}
  2. User opens the link (new browser session, empty sessionStorage)
  3. /signup page detects ?session= query param
  4. API call: GET /auth/v1/register/session (Bearer: token)
  5. If data.expired is true → immediately redirects to /signup/expired
  6. Otherwise → Step 1 form is pre-filled with: Country, First Name, Last Name, Email, Mobile, Title
  7. signup_session_token written to sessionStorage; signup_session_restored = "1" flag set
  8. Password field is left empty — user must re-enter it
  9. User types password and clicks Continue →
  10. Step 1 re-submits (POST /auth/v1/register/start), new session token is created
  11. Wizard proceeds from /signup/company (Step 2) as normal

Expected:

  • URL stays at /signup?session=… while pre-filling
  • Form fields populated (name, email, mobile, country visible)
  • Password field empty
  • No automatic redirect to a later step — user always returns to Step 1

Note on currentStage: The frontend does NOT use the server’s currentStage to skip steps on resume. Regardless of which step the user abandoned, they are always brought back to Step 1 with pre-filled contact details. They re-enter their password and the wizard continues from Step 2 onward.

Expired token: If the API returns { expired: true } (which happens for valid-format tokens older than 30 days), the page calls router.replace("/signup/expired"). Malformed or invalid tokens are silently swallowed by the .catch() handler — the page shows a blank Step 1 form.


Scenario E — Expired Session Page (/signup/expired)

  1. Navigate directly to http://localhost:3000/signup/expired

Expected:

  • Heading: “Your Signup Link Has Expired”
  • Body: explains the 30-day inactivity limit
  • “Start New Signup” link → href="/signup" (starts fresh)
  • “Contact Sales” link visible

Scenario F — Duplicate Email

A second signup attempt using an already-registered email.

  1. Navigate to /signup
  2. Enter an email that is already registered (e.g. moble+signup-a-1@leadmetrics.ai from a prior Scenario A run)
  3. Fill remaining Step 1 fields and click Continue →

API call: POST /auth/v1/register/start
Expected:

  • API returns 409 Conflict immediately at Step 1
  • User stays on /signup — does not navigate to /signup/company
  • Inline error shown under the email field: “An account with this email already exists.”

Note: A second 409 guard also exists at register/complete (line 757 of auth.ts) to catch race conditions, but in practice the error is always surfaced at Step 1.


Scenario G — Payment Failure / Retry

  1. Complete Steps 1–4 (billing address filled, new unused email)
  2. Click Complete Sign-Up & Pay →
  3. In the Razorpay modal, enter UPI ID: failure@razorpay → click Verify and Pay
  4. Razorpay shows “Payment could not be completed” failure screen
  5. Click the back arrow on the failure screen → returns to UPI options
  6. Click Close Checkout → Razorpay “Are you sure you want to exit?” overlay appears
  7. Click Yes, exit

Expected:

  • Page URL stays /signup/plan
  • Plan form is replaced by a “Payment Incomplete” screen
  • Heading: “Payment Incomplete” (⚠️ icon)
  • Body: “Your account has been created. Complete your payment to unlock all features.”
  • Three action buttons:
    • Retry Payment — re-opens Razorpay to attempt payment again
    • Pay via Bank Transfermailto:support@leadmetrics.ai?subject=Bank+Transfer+Payment
    • Contact Sales: +91 8590 468 816tel:+918590468816
  • Footer link: “You can also log in now and complete payment from your dashboard.”

Note: The Tenant and User records ARE created at register/complete (before Razorpay opens). Payment failure does not roll back account creation — the account exists but is in an unpaid state. Clicking “Retry Payment” re-opens the Razorpay modal for the same order.


Razorpay Test Credentials

MethodValueBehaviour
UPI IDsuccess@razorpayInstant success, no OTP — use for all E2E tests
UPI IDfailure@razorpaySimulates payment failure
Card (no 3DS)5267 3181 8797 5449 / exp 02/26 / CVV 123Mastercard — success without OTP popup
Card (3DS, OTP)4208 3000 9609 2278 / OTP 1234Visa — OTP prompt within Razorpay frame
Avoid4111 1111 1111 1111External bank 3DS popup — Playwright cannot interact with it

Sample Test Data

Leadmetrics Test Account (verified 2026-04-10)

  • Email: moble+test1@leadmetrics.ai
  • Company: Leadmetrics Pvt Ltd
  • Location: Kochi, Kerala — GST registered
  • Plan: Professional (₹49,999/month)
  • Amount charged: ₹58,998.82 (incl. 18% GST)
  • Payment ID: pay_Sbp1J63X04LoAK (Razorpay test mode)
  • Payment method: UPI — success@razorpay
  • Post-payment URL: /login?registered=1

Known Issues / Gotchas

IssueDetail
Razorpay modal is in an iframeAll Playwright interactions inside the modal must use f1e*-prefixed refs (iframe element refs). Normal page.click() selectors won’t reach the modal. Use browser_snapshot to get current refs.
Card 4111 1111 1111 1111 triggers external 3DS popupRazorpay routes this card through a bank-hosted OTP page in a separate tab. Playwright cannot switch to it in the MCP session. Always use success@razorpay UPI instead.
Razorpay modal has a “confirm close” overlayClicking the close (✕) button shows a “Are you sure?” overlay. Clicking ✕ again on the overlay is needed to fully dismiss it.
planId is optional in register/completeThe Fastify JSON schema does NOT have planId in required. The handler auto-selects the first available plan if none is provided. Do not re-add it to required — it would cause a 400 before the auth check runs.
Plan cards only load if API server is runningThe plan step fetches GET /api/auth/v1/plans on mount. If the API server is down, the submit button stays disabled.
Session token not persisted to localStoragesignup_session_token lives only in sessionStorage. Closing the browser tab and opening a new one clears it. The session-link email is the recovery path.
billingPostal + billingState reach the APIBoth fields are sent to register/complete and stored on the RegistrationSession. They are included in the billing address object used for invoice generation.
Scheduler worker is separate from the API workerAbandonment emails are sent by apps/servers/scheduler, not the BullMQ API worker. Start it with pnpm --filter @leadmetrics/server-scheduler dev.
BullMQ dedupeKey blocks same-day re-runsThe dedupeKey on reminder emails means re-triggering the scheduler on the same day won’t re-send. Clear the Redis key or use a different sessionId to test. See feedback memory: feedback_bullmq_dedupe_rerun.md.
Step 3 auto-skipped for non-India but session must be validFor non-India customers, register/step/2 marks step 3 as autoSkipped: true in stepHistory. The frontend skips /signup/location but still reads signup_step3 from sessionStorage at Step 4. Step 3 data defaults to empty strings — this is fine.
title (Mr./Mrs.) is collected in Step 1 UI but not yet persisted to DBIt is stored in signup_step1 in sessionStorage and sent to register/start, but the API currently ignores it. The User table does not have a title column. This is a known medium-priority gap.

© 2026 Leadmetrics — Internal use only