@@ -418,8 +418,8 @@ async function opAdminDeposit(page: Page, state: TestState) {
418418 const depositAmount = randomInt ( 50 , 500 ) ;
419419
420420 // Do the deposit via API since admin UI requires navigating to account detail
421- await state . api . deposit ( state . adminToken , account . id , depositAmount , `Chaos deposit ${ RUN_ID } ` ) ;
422- account . balance += depositAmount ;
421+ const txn = await state . api . deposit ( state . adminToken , account . id , depositAmount , `Chaos deposit ${ RUN_ID } ` ) ;
422+ account . balance = Number ( txn . balance_after ) ;
423423
424424 state . operationLog . push ( `API_DEPOSIT: account=${ account . id } amount=${ depositAmount } new_balance=${ account . balance } ` ) ;
425425}
@@ -428,20 +428,21 @@ async function opAdminWithdraw(page: Page, state: TestState) {
428428 if ( state . bankAccounts . length === 0 ) return ;
429429 const account = pick ( state . bankAccounts ) ;
430430
431- if ( account . balance <= 0 ) {
432- state . operationLog . push ( `SKIP_WITHDRAW: account=${ account . id } balance=${ account . balance } ` ) ;
431+ // Refresh balance from API before withdrawing
432+ const apiAccount = await state . api . getBankAccount ( state . adminToken , account . id ) ;
433+ const currentBalance = Number ( apiAccount . current_balance ?? apiAccount . balance ?? 0 ) ;
434+ account . balance = currentBalance ;
435+
436+ if ( currentBalance <= 0 ) {
437+ state . operationLog . push ( `SKIP_WITHDRAW: account=${ account . id } balance=${ currentBalance } ` ) ;
433438 return ;
434439 }
435440
436- const maxWithdraw = Math . min ( account . balance , 200 ) ;
437- const withdrawAmount = randomInt ( 1 , maxWithdraw ) ;
441+ const maxWithdraw = Math . min ( currentBalance , 200 ) ;
442+ const withdrawAmount = randomInt ( 1 , Math . floor ( maxWithdraw ) ) ;
438443
439- const result = await state . api . withdraw ( state . adminToken , account . id , withdrawAmount , `Chaos withdraw ${ RUN_ID } ` ) ;
440- if ( result . balance_after !== undefined ) {
441- account . balance = result . balance_after ;
442- } else {
443- account . balance -= withdrawAmount ;
444- }
444+ const txn = await state . api . withdraw ( state . adminToken , account . id , withdrawAmount , `Chaos withdraw ${ RUN_ID } ` ) ;
445+ account . balance = Number ( txn . balance_after ) ;
445446
446447 state . operationLog . push ( `API_WITHDRAW: account=${ account . id } amount=${ withdrawAmount } balance=${ account . balance } ` ) ;
447448}
@@ -762,8 +763,10 @@ test.describe(`Chaos Cycle — Iteration ${ITERATION}`, () => {
762763 console . log ( ` Created borrower: ${ borrowerEmail } ` ) ;
763764
764765 // Login as test users (stagger to respect 5/min rate limit — 5 req/min per IP)
765- await page . waitForTimeout ( 15000 ) ;
766+ // Wait for rate limit window to clear from prior iterations
767+ await page . waitForTimeout ( 20000 ) ;
766768 const creditorLogin = await api . login ( creditorEmail , "TestPass123!" ) ;
769+ await page . waitForTimeout ( 13000 ) ;
767770 const borrowerLogin = await api . login ( borrowerEmail , "TestPass123!" ) ;
768771
769772 // Create bank accounts for both users
@@ -834,19 +837,27 @@ test.describe(`Chaos Cycle — Iteration ${ITERATION}`, () => {
834837 // ──────────────────────────────────────────────────────────────────────
835838 console . log ( "\nStep 2: Logging in via UI..." ) ;
836839
837- // Set auth token directly via localStorage to avoid rate limiting on login endpoint
840+ // Login via UI form — use the actual login flow
838841 await page . goto ( "/login" ) ;
839- await page . evaluate (
840- ( [ token ] ) => {
841- localStorage . setItem ( "lendq_access_token" , token ) ;
842- } ,
843- [ creditorLogin . access_token ] ,
844- ) ;
845- await page . goto ( "/dashboard" ) ;
842+ await page . getByLabel ( "Email Address" ) . fill ( creditorEmail ) ;
843+ await page . getByLabel ( "Password" ) . fill ( "TestPass123!" ) ;
844+ await page . getByRole ( "button" , { name : "Sign In" } ) . click ( ) ;
845+
846+ // Wait for navigation to dashboard (generous timeout for staging)
847+ try {
848+ await page . waitForURL ( "**/dashboard" , { timeout : 30000 } ) ;
849+ } catch {
850+ // If rate limited, inject token directly as fallback
851+ console . log ( " UI login timed out, falling back to token injection..." ) ;
852+ await page . evaluate (
853+ ( [ token ] ) => localStorage . setItem ( "lendq_access_token" , token ) ,
854+ [ creditorLogin . access_token ] ,
855+ ) ;
856+ await page . goto ( "/dashboard" ) ;
857+ }
846858
847- // Wait for dashboard to load with generous timeout for staging
848859 await page . waitForSelector ( "[data-testid='metric-total-lent-out']" , { timeout : 30000 } ) ;
849- console . log ( " Logged in successfully (via token injection) " ) ;
860+ console . log ( " Logged in successfully" ) ;
850861
851862 // ──────────────────────────────────────────────────────────────────────
852863 // STEP 3: Perform 30 random operations
@@ -927,12 +938,9 @@ test.describe(`Chaos Cycle — Iteration ${ITERATION}`, () => {
927938 // ──────────────────────────────────────────────────────────────────────
928939 console . log ( "\nStep 6: Cleaning up test data..." ) ;
929940
930- // Refresh admin token
931- const freshAdminLogin = await api . login ( "admin@family.com" , "password123" ) ;
932- const freshAdminToken = freshAdminLogin . access_token ;
933-
934- await api . purgeUser ( freshAdminToken , creditorUser . id ) ;
935- await api . purgeUser ( freshAdminToken , borrowerUser . id ) ;
941+ // Use existing admin token (avoid extra login for rate limit)
942+ await api . purgeUser ( adminToken , creditorUser . id ) ;
943+ await api . purgeUser ( adminToken , borrowerUser . id ) ;
936944 console . log ( " Test users purged" ) ;
937945
938946 console . log ( `\n=== ITERATION ${ ITERATION } COMPLETE ===` ) ;
0 commit comments