@@ -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