@@ -655,6 +655,161 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withBilling] })('pricing tabl
655655 await fakeUser . deleteIfExists ( ) ;
656656 } ) ;
657657
658+ test ( 'displays billing history and navigates through statement and payment attempt details' , async ( {
659+ page,
660+ context,
661+ } ) => {
662+ const u = createTestUtils ( { app, page, context } ) ;
663+
664+ const fakeUser = u . services . users . createFakeUser ( ) ;
665+ await u . services . users . createBapiUser ( fakeUser ) ;
666+
667+ try {
668+ await u . po . signIn . goTo ( ) ;
669+ await u . po . signIn . signInWithEmailAndInstantPassword ( { email : fakeUser . email , password : fakeUser . password } ) ;
670+
671+ await u . po . page . goToRelative ( '/user' ) ;
672+ await u . po . userProfile . waitForMounted ( ) ;
673+ await u . po . userProfile . switchToBillingTab ( ) ;
674+
675+ const openBillingTab = async ( label : RegExp ) => {
676+ await page . getByRole ( 'tab' , { name : label } ) . click ( ) ;
677+ await page
678+ . locator ( '.cl-userProfile-root [role="tabpanel"] .cl-table' )
679+ . waitFor ( { state : 'visible' , timeout : 15000 } ) ;
680+ } ;
681+ const getBillingTableRows = ( ) => {
682+ return page . locator ( '.cl-userProfile-root .cl-tableBody .cl-tableRow' ) ;
683+ } ;
684+ const waitForBillingTableRows = async ( options ?: { hasText ?: string | RegExp } ) => {
685+ const rows = getBillingTableRows ( ) ;
686+ if ( options ?. hasText ) {
687+ await rows
688+ . filter ( {
689+ hasText : options . hasText ,
690+ } )
691+ . first ( )
692+ . waitFor ( { state : 'visible' , timeout : 15000 } ) ;
693+ } else {
694+ await rows . first ( ) . waitFor ( { state : 'visible' , timeout : 15000 } ) ;
695+ }
696+ return rows ;
697+ } ;
698+ const getBillingEmptyStateMessage = ( text : string | RegExp ) => {
699+ return page . locator ( '.cl-userProfile-root .cl-table' ) . getByText ( text ) ;
700+ } ;
701+ const waitForStatementPage = async ( ) => {
702+ const statementRoot = page . locator ( '.cl-statementRoot' ) ;
703+ await statementRoot . waitFor ( { state : 'visible' , timeout : 15000 } ) ;
704+ return statementRoot ;
705+ } ;
706+ const waitForPaymentAttemptPage = async ( ) => {
707+ const paymentAttemptRoot = page . locator ( '.cl-paymentAttemptRoot' ) ;
708+ await paymentAttemptRoot . waitFor ( { state : 'visible' , timeout : 15000 } ) ;
709+ return paymentAttemptRoot ;
710+ } ;
711+ const goBackToPaymentsList = async ( ) => {
712+ const paymentAttemptRoot = page . locator ( '.cl-paymentAttemptRoot' ) ;
713+ await Promise . all ( [
714+ page . waitForURL ( / t a b = p a y m e n t s / , { timeout : 15000 } ) ,
715+ page . getByRole ( 'link' , { name : / P a y m e n t s / i } ) . click ( ) ,
716+ ] ) ;
717+ await paymentAttemptRoot . waitFor ( { state : 'detached' , timeout : 15000 } ) ;
718+ } ;
719+
720+ await openBillingTab ( / S t a t e m e n t s / i) ;
721+ await expect ( getBillingEmptyStateMessage ( 'No statements to display' ) ) . toBeVisible ( ) ;
722+
723+ await u . po . page . goToRelative ( '/user' ) ;
724+ await u . po . userProfile . waitForMounted ( ) ;
725+ await u . po . userProfile . switchToBillingTab ( ) ;
726+ await u . po . page . getByRole ( 'button' , { name : 'Switch plans' } ) . click ( ) ;
727+
728+ await u . po . pricingTable . waitForMounted ( ) ;
729+ await u . po . pricingTable . startCheckout ( { planSlug : 'plus' } ) ;
730+ await u . po . checkout . waitForMounted ( ) ;
731+ await u . po . checkout . fillTestCard ( ) ;
732+ await u . po . checkout . clickPayOrSubscribe ( ) ;
733+ await expect ( u . po . page . getByText ( 'Payment was successful!' ) ) . toBeVisible ( {
734+ timeout : 15000 ,
735+ } ) ;
736+ await u . po . checkout . confirmAndContinue ( ) ;
737+
738+ await u . po . pricingTable . startCheckout ( { planSlug : 'pro' , shouldSwitch : true } ) ;
739+ await u . po . checkout . waitForMounted ( ) ;
740+ await u . po . checkout . root . getByText ( 'Add payment method' ) . click ( ) ;
741+ await u . po . checkout . fillCard ( {
742+ number : '4100000000000019' ,
743+ expiration : '1234' ,
744+ cvc : '123' ,
745+ country : 'United States' ,
746+ zip : '12345' ,
747+ } ) ;
748+ await u . po . checkout . clickPayOrSubscribe ( ) ;
749+ await expect ( u . po . checkout . root . getByText ( 'The card was declined.' ) . first ( ) ) . toBeVisible ( {
750+ timeout : 15000 ,
751+ } ) ;
752+ await u . po . checkout . closeDrawer ( ) ;
753+
754+ await u . po . page . goToRelative ( '/user' ) ;
755+ await u . po . userProfile . waitForMounted ( ) ;
756+ await u . po . userProfile . switchToBillingTab ( ) ;
757+
758+ await openBillingTab ( / S t a t e m e n t s / i) ;
759+ const date = new Date ( ) . toLocaleDateString ( 'en-US' , { year : 'numeric' , month : 'long' } ) ;
760+ await waitForBillingTableRows ( { hasText : new RegExp ( date , 'i' ) } ) ;
761+ await expect ( getBillingEmptyStateMessage ( 'No statements to display' ) ) . toBeHidden ( ) ;
762+
763+ const firstStatementRow = getBillingTableRows ( ) . first ( ) ;
764+ await firstStatementRow . click ( ) ;
765+
766+ const statementRoot = await waitForStatementPage ( ) ;
767+ await expect (
768+ statementRoot . locator ( '.cl-statementSectionContentDetailsHeaderTitle' ) . filter ( { hasText : / P l u s / i } ) . first ( ) ,
769+ ) . toBeVisible ( ) ;
770+
771+ const statementTotalText = ( await statementRoot . locator ( '.cl-statementFooterValue' ) . textContent ( ) ) ?. trim ( ) ;
772+ expect ( statementTotalText ) . toBeTruthy ( ) ;
773+
774+ await statementRoot
775+ . getByRole ( 'button' , { name : / V i e w p a y m e n t / i } )
776+ . first ( )
777+ . click ( ) ;
778+ const paymentAttemptRoot = await waitForPaymentAttemptPage ( ) ;
779+ await expect ( paymentAttemptRoot . locator ( '.cl-paymentAttemptHeaderBadge' ) ) . toHaveText ( / p a i d / i) ;
780+
781+ const paymentTotalText = (
782+ await paymentAttemptRoot . locator ( '.cl-paymentAttemptFooterValue' ) . textContent ( )
783+ ) ?. trim ( ) ;
784+ expect ( paymentTotalText ) . toBe ( statementTotalText ) ;
785+
786+ await expect (
787+ paymentAttemptRoot . locator ( '.cl-lineItemsTitle' ) . filter ( { hasText : / P l u s / i } ) . first ( ) ,
788+ ) . toBeVisible ( ) ;
789+
790+ await goBackToPaymentsList ( ) ;
791+ await openBillingTab ( / P a y m e n t s / i) ;
792+ await waitForBillingTableRows ( { hasText : / p a i d / i } ) ;
793+ await waitForBillingTableRows ( { hasText : / F a i l e d / i } ) ;
794+ await expect ( getBillingEmptyStateMessage ( 'No payment history' ) ) . toBeHidden ( ) ;
795+
796+ const failedPaymentRow = getBillingTableRows ( )
797+ . filter ( { hasText : / F a i l e d / i } )
798+ . first ( ) ;
799+ await failedPaymentRow . click ( ) ;
800+
801+ const failedPaymentAttemptRoot = await waitForPaymentAttemptPage ( ) ;
802+ await expect ( failedPaymentAttemptRoot . locator ( '.cl-paymentAttemptHeaderBadge' ) ) . toHaveText ( / f a i l e d / i) ;
803+ await expect (
804+ failedPaymentAttemptRoot . locator ( '.cl-lineItemsTitle' ) . filter ( { hasText : / P r o / i } ) . first ( ) ,
805+ ) . toBeVisible ( ) ;
806+
807+ await goBackToPaymentsList ( ) ;
808+ } finally {
809+ await fakeUser . deleteIfExists ( ) ;
810+ }
811+ } ) ;
812+
658813 test ( 'adds two payment methods and sets the last as default' , async ( { page, context } ) => {
659814 const u = createTestUtils ( { app, page, context } ) ;
660815
0 commit comments