Skip to content

Commit c839b31

Browse files
committed
test(e2e): drive the full Razorpay subscription checkout (Start→Cards→RBI→OTP)
The card-entry leg now drives the real TEST-mode subscription flow observed live: Start Subscription → select Cards (INR checkout defaults to UPI) → keystroke card entry → Continue → RBI 'Yes, secure my card' mandate modal → OTP 1234 → Continue. Previously it assumed a one-shot 'Pay' card form and soft-skipped on the subscription DOM. Still fully resilient (every step best-effort; markup change soft-fails, never reds the nightly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 303b129 commit c839b31

1 file changed

Lines changed: 37 additions & 12 deletions

File tree

e2e/live-ui-payment.spec.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -366,39 +366,63 @@ async function driveRazorpayTestCard(page: Page): Promise<boolean> {
366366
await popup.waitForLoadState('domcontentloaded').catch(() => {})
367367
}
368368

369-
// Razorpay sometimes shows a "Card" payment-method tab first. Try to click it.
369+
// The flow, as observed driving a real TEST-mode INR subscription checkout
370+
// (2026-06-07): the short_url lands on a Razorpay "Subscription Details" page
371+
// with a "Start Subscription" button → that opens the checkout iframe listing
372+
// UPI / Cards / EMandate → pick Cards → fill card → "Continue" → an RBI
373+
// "Save your card as per RBI guidelines?" mandate modal (required for
374+
// recurring) → "Yes, secure my card" → a mock-bank OTP step → enter 1234 →
375+
// "Continue". Every step is best-effort (clickIfPresent never throws) so a
376+
// markup change still soft-fails rather than redding the nightly.
377+
378+
// 0) Subscription details page → "Start Subscription" opens the checkout iframe.
370379
await clickIfPresent(target, [
371-
'text=/^card$/i',
372-
'text=/cards?/i',
380+
'button:has-text("Start Subscription")',
381+
'button:has-text("Start subscription")',
382+
])
383+
// Give the checkout iframe a beat to mount before scanning frames.
384+
await target.waitForTimeout(2000).catch(() => {})
385+
386+
// 1) Select the "Cards" payment method (the INR checkout defaults to UPI).
387+
await clickIfPresent(target, [
388+
'[role="radio"][aria-label*="card" i]',
389+
'text=/^cards?$/i',
373390
'[data-testid="card"]',
374391
'button:has-text("Card")',
375392
])
376393

377-
// Find the card-number field across the page + its frames. Razorpay nests the
378-
// PCI card fields in iframes; we scan candidate frames for a recognisable
379-
// number input.
394+
// 2) Find + fill the card fields across the page + its (cross-origin) frames.
380395
const cardFilled = await fillCardAcrossFrames(target)
381396
if (!cardFilled) {
382397
// eslint-disable-next-line no-console
383398
console.warn('[live-ui-payment] could not locate the Razorpay card-number field in any frame — markup changed?')
384399
return false
385400
}
386401

387-
// Submit (Pay). The button label varies (Pay, Pay Now, ₹…).
402+
// 3) Submit the card. For SUBSCRIPTIONS the button is "Continue" (one-time
403+
// payments say "Pay"); try both.
388404
const submitted = await clickIfPresent(target, [
405+
'button:has-text("Continue")',
389406
'button:has-text("Pay")',
390407
'button:has-text("Subscribe")',
391408
'button[type="submit"]',
392409
'[data-testid="submit"]',
393410
])
394411
if (!submitted) {
395412
// eslint-disable-next-line no-console
396-
console.warn('[live-ui-payment] could not find the Razorpay Pay/Submit button — markup changed?')
413+
console.warn('[live-ui-payment] could not find the Razorpay Continue/Pay button — markup changed?')
397414
return false
398415
}
399416

400-
// Mock-bank / 3DS OTP step. Enter OTP 1234 and click Success/Submit. Both the
401-
// OTP field and the success button are in the bank-simulator frame on TEST.
417+
// 4) RBI save-card mandate modal ("Save your card as per RBI guidelines?") —
418+
// required for recurring. Best-effort; absent on some flows.
419+
await target.waitForTimeout(1000).catch(() => {})
420+
await clickIfPresent(target, [
421+
'button:has-text("Yes, secure my card")',
422+
'button:has-text("secure my card")',
423+
])
424+
425+
// 5) Mock-bank / 3DS OTP step. Enter OTP 1234 and click Continue/Success.
402426
const otpDone = await enterOtpAcrossFrames(target, TEST_OTP)
403427
if (!otpDone) {
404428
// The OTP step may be skipped for some test flows; don't fail solely on it.
@@ -501,9 +525,10 @@ async function enterOtpAcrossFrames(page: Page, otp: string): Promise<boolean> {
501525
}
502526
}
503527
}
504-
// Click Success / Submit on the bank simulator (TEST mode renders a "Success"
505-
// button; OTP 1234 is also valid).
528+
// Submit the OTP. The TEST bank simulator's button is "Continue" (newer
529+
// checkout) or "Success"/"Submit" (older) — try all.
506530
await clickIfPresent(page, [
531+
'button:has-text("Continue")',
507532
'button:has-text("Success")',
508533
'button:has-text("Submit")',
509534
'button[type="submit"]',

0 commit comments

Comments
 (0)