11/**
22 * Flow 2: Deposit & Withdraw Transaction
33 */
4- import { test , expect , interceptApiRoutes , stubFreighterConnected , stubFreighterDisconnected } from './fixtures' ;
5-
6- const MOCK_ADDRESS = 'GABC1TEST2STELLAR3ADDRESS4FAKE5XYZ6ABCDEFGHIJKLMNOPQRSTU' ;
4+ import type { Page } from '@playwright/test' ;
5+ import {
6+ test ,
7+ expect ,
8+ interceptApiRoutes ,
9+ stubFreighterConnected ,
10+ stubFreighterDisconnected ,
11+ vaultSummaryAtCapacity ,
12+ } from './fixtures' ;
13+
14+ /** Valid Stellar public key (G + 55 base32 chars) for API validation in submitDeposit / submitWithdrawal. */
15+ const MOCK_ADDRESS = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5' ;
716const SHORT_ADDR = `${ MOCK_ADDRESS . substring ( 0 , 5 ) } ...${ MOCK_ADDRESS . substring ( MOCK_ADDRESS . length - 4 ) } ` ;
817
18+ async function goToConnectedVault ( page : Page ) {
19+ await page . goto ( '/' ) ;
20+ await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
21+ await expect ( page . getByLabel ( 'USDC wallet balance' ) ) . toContainText ( '1250.50' , { timeout : 20_000 } ) ;
22+ }
23+
924// Tests that verify unauthenticated UI no Freighter stub injected
1025test . describe ( 'Deposit panel no wallet' , ( ) => {
1126 test . beforeEach ( async ( { page } ) => {
@@ -43,22 +58,19 @@ test.describe('Deposit & Withdraw connected wallet', () => {
4358 } ) ;
4459
4560 test ( 'auto-connects wallet on mount when Freighter is already allowed' , async ( { page } ) => {
46- await page . goto ( '/' ) ;
47- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
61+ await goToConnectedVault ( page ) ;
4862 } ) ;
4963
5064 test ( 'deposit overlay is removed after wallet connects' , async ( { page } ) => {
51- await page . goto ( '/' ) ;
52- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
65+ await goToConnectedVault ( page ) ;
5366 await expect ( page . getByText ( 'Wallet Not Connected' ) ) . not . toBeVisible ( ) ;
5467 } ) ;
5568
5669 test ( 'deposit tab is active by default and can switch to withdraw' , async ( { page } ) => {
57- await page . goto ( '/' ) ;
58- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
70+ await goToConnectedVault ( page ) ;
5971
60- const depositTab = page . getByRole ( 'button ' , { name : 'Deposit' , exact : true } ) ;
61- const withdrawTab = page . getByRole ( 'button ' , { name : 'Withdraw' , exact : true } ) ;
72+ const depositTab = page . getByRole ( 'tab ' , { name : 'Deposit' , exact : true } ) ;
73+ const withdrawTab = page . getByRole ( 'tab ' , { name : 'Withdraw' , exact : true } ) ;
6274
6375 await expect ( page . getByText ( 'Amount to deposit' ) ) . toBeVisible ( ) ;
6476 await withdrawTab . click ( ) ;
@@ -67,51 +79,90 @@ test.describe('Deposit & Withdraw connected wallet', () => {
6779 await expect ( page . getByText ( 'Amount to deposit' ) ) . toBeVisible ( ) ;
6880 } ) ;
6981
70- test ( 'MAX button fills the amount input with the current balance' , async ( { page } ) => {
71- await page . goto ( '/' ) ;
72- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
82+ test ( 'MAX button pre-fills the deposit field with the displayed wallet balance' , async ( { page } ) => {
83+ await goToConnectedVault ( page ) ;
84+ const walletBanner = page . getByLabel ( 'USDC wallet balance' ) ;
85+ await expect ( walletBanner ) . toBeVisible ( ) ;
86+ const bannerText = ( await walletBanner . textContent ( ) ) ?? '' ;
87+ const match = bannerText . match ( / U S D C : \s * ( [ \d . ] + ) / ) ;
88+ expect ( match , 'expected USDC balance in wallet banner' ) . toBeTruthy ( ) ;
89+ const expectedBalance = match ! [ 1 ] ;
7390 await page . getByRole ( 'button' , { name : 'MAX' } ) . click ( ) ;
74- const value = await page . getByPlaceholder ( '0.00' ) . inputValue ( ) ;
75- expect ( value ) . toBeTruthy ( ) ;
91+ await expect ( page . getByLabel ( 'Deposit amount' ) ) . toHaveValue ( expectedBalance ) ;
7692 } ) ;
7793
7894 test ( 'performs a deposit and updates the balance' , async ( { page } ) => {
79- await page . goto ( '/' ) ;
80- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
95+ await goToConnectedVault ( page ) ;
8196
82- const amountInput = page . getByPlaceholder ( '0.00 ') ;
97+ const amountInput = page . getByLabel ( 'Deposit amount ') ;
8398 const submitBtn = page . getByRole ( 'button' , { name : / A p p r o v e & D e p o s i t / i } ) ;
8499
85100 await amountInput . fill ( '100' ) ;
86101 await expect ( submitBtn ) . toBeEnabled ( ) ;
87102 await submitBtn . click ( ) ;
88103
89- await expect ( page . getByRole ( 'button' , { name : / P r o c e s s i n g T r a n s a c t i o n / i } ) ) . toBeVisible ( ) ;
90- // Initial balance 1250.50 + 100 = 1350.50
91- await expect ( page . getByText ( '1350.50' ) ) . toBeVisible ( { timeout : 5000 } ) ;
104+ await expect ( page . getByRole ( 'button' , { name : / D i s m i s s D e p o s i t S u c c e s s f u l / i } ) ) . toBeVisible ( {
105+ timeout : 15_000 ,
106+ } ) ;
92107 await expect ( page . getByRole ( 'button' , { name : / A p p r o v e & D e p o s i t / i } ) ) . toBeVisible ( ) ;
93108 } ) ;
94109
95110 test ( 'performs a withdrawal and updates the balance' , async ( { page } ) => {
96- await page . goto ( '/' ) ;
97- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
111+ await goToConnectedVault ( page ) ;
98112
99- await page . getByRole ( 'button ' , { name : 'Withdraw' , exact : true } ) . click ( ) ;
113+ await page . getByRole ( 'tab ' , { name : 'Withdraw' , exact : true } ) . click ( ) ;
100114 await expect ( page . getByText ( 'Amount to withdraw' ) ) . toBeVisible ( ) ;
101115
102- await page . getByPlaceholder ( '0.00 ') . fill ( '50' ) ;
116+ await page . getByLabel ( 'Withdrawal amount ') . fill ( '50' ) ;
103117 const submitBtn = page . getByRole ( 'button' , { name : / W i t h d r a w F u n d s / i } ) ;
104118 await expect ( submitBtn ) . toBeEnabled ( ) ;
105119 await submitBtn . click ( ) ;
106120
107- await expect ( page . getByRole ( 'button' , { name : / P r o c e s s i n g T r a n s a c t i o n / i } ) ) . toBeVisible ( ) ;
108- // 1250.50 - 50 = 1200.50
109- await expect ( page . getByText ( '1200.50' ) ) . toBeVisible ( { timeout : 5000 } ) ;
121+ await expect ( page . getByRole ( 'button' , { name : / D i s m i s s W i t h d r a w a l S u c c e s s f u l / i } ) ) . toBeVisible ( {
122+ timeout : 15_000 ,
123+ } ) ;
124+ } ) ;
125+
126+ test ( 'deposit submit stays disabled with an empty amount field' , async ( { page } ) => {
127+ await goToConnectedVault ( page ) ;
128+ const depositInput = page . getByLabel ( 'Deposit amount' ) ;
129+ await expect ( depositInput ) . toHaveValue ( '' ) ;
130+ await expect ( page . getByRole ( 'button' , { name : / A p p r o v e & D e p o s i t / i } ) ) . toBeDisabled ( ) ;
131+ } ) ;
132+
133+ test ( 'deposit submit stays disabled when amount exceeds available USDC balance' , async ( { page } ) => {
134+ await goToConnectedVault ( page ) ;
135+ await page . getByLabel ( 'Deposit amount' ) . fill ( '999999' ) ;
136+ await expect ( page . getByRole ( 'button' , { name : / A p p r o v e & D e p o s i t / i } ) ) . toBeDisabled ( ) ;
137+ await expect ( page . getByRole ( 'alert' ) ) . toContainText ( / e x c e e d / i) ;
138+ } ) ;
139+
140+ test ( 'deposit is blocked when the vault is at capacity' , async ( { page } ) => {
141+ await page . route ( '**/mock-api/vault-summary.json' , async ( route ) => {
142+ await route . fulfill ( {
143+ status : 200 ,
144+ contentType : 'application/json' ,
145+ body : JSON . stringify ( vaultSummaryAtCapacity ) ,
146+ } ) ;
147+ } ) ;
148+ await goToConnectedVault ( page ) ;
149+ await expect ( page . getByText ( 'Vault Capacity Reached' ) ) . toBeVisible ( ) ;
150+ await expect ( page . getByLabel ( 'Deposit amount' ) ) . toBeDisabled ( ) ;
151+ await expect ( page . getByRole ( 'button' , { name : 'MAX' } ) ) . toBeDisabled ( ) ;
152+ await expect ( page . getByRole ( 'button' , { name : 'Vault is full' } ) ) . toBeDisabled ( ) ;
153+ } ) ;
154+
155+ test ( 'switching deposit/withdraw tabs clears the amount field' , async ( { page } ) => {
156+ await goToConnectedVault ( page ) ;
157+ await page . getByLabel ( 'Deposit amount' ) . fill ( '123.45' ) ;
158+ await page . getByRole ( 'tab' , { name : 'Withdraw' , exact : true } ) . click ( ) ;
159+ await expect ( page . getByLabel ( 'Withdrawal amount' ) ) . toHaveValue ( '' ) ;
160+ await page . getByRole ( 'tab' , { name : 'Deposit' , exact : true } ) . click ( ) ;
161+ await expect ( page . getByLabel ( 'Deposit amount' ) ) . toHaveValue ( '' ) ;
110162 } ) ;
111163
112164 test ( 'disconnect button clears wallet state and shows connect button' , async ( { page } ) => {
113- await page . goto ( '/' ) ;
114- await expect ( page . getByText ( SHORT_ADDR ) ) . toBeVisible ( { timeout : 5000 } ) ;
165+ await goToConnectedVault ( page ) ;
115166
116167 // Disable the stub so the auto-connect effect does not re-fire after disconnect
117168 await page . evaluate ( ( ) => {
0 commit comments