1+ {
2+ "title" : " HL balance validation — phase={{phaseLabel}} expectedMode={{expectedMode}}" ,
3+ "description" : " Captures and asserts the three balance readouts the TAT-3016 hotfix touches: (1) PerpsController.state.accountState (availableBalance vs availableToTradeBalance), (2) PerpsMarketDetails balance text (perps-market-available-balance-text — gates order-entry Long/Add-Funds CTA), (3) PerpsWithdraw balance text (perps-withdraw-available-balance-text — must show availableBalance only, never the spot fold). Screenshots per screen tagged with phaseLabel for reviewer before/after comparison. Fold invariant (trade >= avail) is asserted unconditionally. expectedMode='unified' additionally asserts the fold amount is non-trivial when spot USDC is present; expectedMode='standard' asserts withdrawable==availableBalance (no leak from fold into the withdrawable path)." ,
4+ "inputs" : {
5+ "expectedMode" : {
6+ "type" : " string" ,
7+ "default" : " unified" ,
8+ "description" : " HL abstraction mode expected in this phase: 'unified' or 'standard'"
9+ },
10+ "phaseLabel" : {
11+ "type" : " string" ,
12+ "default" : " phase" ,
13+ "description" : " Short label used in screenshot filenames and trace annotations (e.g. 'initial-unified', 'after-flip-standard', 'restored-unified')"
14+ }
15+ },
16+ "validate" : {
17+ "workflow" : {
18+ "pre_conditions" : [
19+ " wallet.unlocked" ,
20+ " perps.feature_enabled"
21+ ],
22+ "entry" : " refresh-state" ,
23+ "nodes" : {
24+ "refresh-state" : {
25+ "action" : " eval_async" ,
26+ "expression" : " Engine.context.PerpsController.getAccountState().then(function(r){return JSON.stringify({ok:true,availableBalance:r.availableBalance,availableToTradeBalance:r.availableToTradeBalance,totalBalance:r.totalBalance,phase:'{{phaseLabel}}'})}).catch(function(e){return JSON.stringify({ok:false,error:String(e),phase:'{{phaseLabel}}'})})" ,
27+ "assert" : { "operator" : " eq" , "field" : " ok" , "value" : true },
28+ "timeout_ms" : 20000 ,
29+ "next" : " capture-controller-state"
30+ },
31+ "capture-controller-state" : {
32+ "action" : " eval_ref" ,
33+ "ref" : " perps/hl-fixture-state" ,
34+ "assert" : {
35+ "operator" : " not_null" ,
36+ "field" : " availableToTradeBalance"
37+ },
38+ "next" : " assert-fold-invariant"
39+ },
40+ "assert-fold-invariant" : {
41+ "action" : " eval_sync" ,
42+ "expression" : " (function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,spotFold:spotFold,foldInvariant:trade>=avail,phase:'{{phaseLabel}}'})})()" ,
43+ "assert" : {
44+ "operator" : " eq" ,
45+ "field" : " foldInvariant" ,
46+ "value" : true
47+ },
48+ "next" : " nav-market-list"
49+ },
50+ "nav-market-list" : {
51+ "action" : " navigate" ,
52+ "target" : " PerpsMarketListView" ,
53+ "next" : " wait-market-list"
54+ },
55+ "wait-market-list" : {
56+ "action" : " wait_for" ,
57+ "expression" : " (function(){try{var r=globalThis.__AGENTIC__&&globalThis.__AGENTIC__.getRoute();return JSON.stringify({route:r?r.name:'unknown'})}catch(e){return JSON.stringify({route:'unknown'})}})()" ,
58+ "assert" : {
59+ "operator" : " eq" ,
60+ "field" : " route" ,
61+ "value" : " PerpsMarketListView"
62+ },
63+ "timeout_ms" : 15000 ,
64+ "next" : " settle-market-list"
65+ },
66+ "settle-market-list" : {
67+ "action" : " wait_for" ,
68+ "expression" : " (function(){var a=globalThis.__AGENTIC__;var mounted=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-market-available-balance-text'));return JSON.stringify({mounted:mounted})})()" ,
69+ "assert" : { "operator" : " eq" , "field" : " mounted" , "value" : true },
70+ "timeout_ms" : 15000 ,
71+ "next" : " capture-market-list-balance"
72+ },
73+ "capture-market-list-balance" : {
74+ "action" : " eval_sync" ,
75+ "expression" : " (function(){try{var a=globalThis.__AGENTIC__;var display=a&&a.getTextByTestId('perps-market-available-balance-text')||'';var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var tradeFloor=Math.floor(trade);var tradeStr=tradeFloor.toString();var mentionsTrade=display.length>0&&(display.indexOf(tradeStr)>=0||display.indexOf('$0')>=0&&trade<1);return JSON.stringify({display:display,availableToTradeBalance:trade,displayMatchesTradeBalance:mentionsTrade,phase:'{{phaseLabel}}'})}catch(e){return JSON.stringify({error:String(e)})}})()" ,
76+ "assert" : {
77+ "operator" : " not_null" ,
78+ "field" : " display"
79+ },
80+ "next" : " screenshot-market-list"
81+ },
82+ "screenshot-market-list" : {
83+ "action" : " screenshot" ,
84+ "filename" : " tat3016-{{phaseLabel}}-market-list.png" ,
85+ "next" : " nav-withdraw"
86+ },
87+ "nav-withdraw" : {
88+ "action" : " navigate" ,
89+ "target" : " PerpsWithdraw" ,
90+ "next" : " wait-withdraw"
91+ },
92+ "wait-withdraw" : {
93+ "action" : " wait_for" ,
94+ "expression" : " (function(){try{var r=globalThis.__AGENTIC__&&globalThis.__AGENTIC__.getRoute();return JSON.stringify({route:r?r.name:'unknown'})}catch(e){return JSON.stringify({route:'unknown'})}})()" ,
95+ "assert" : {
96+ "operator" : " eq" ,
97+ "field" : " route" ,
98+ "value" : " PerpsWithdraw"
99+ },
100+ "timeout_ms" : 15000 ,
101+ "next" : " settle-withdraw"
102+ },
103+ "settle-withdraw" : {
104+ "action" : " wait_for" ,
105+ "expression" : " (function(){var a=globalThis.__AGENTIC__;var mounted=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-withdraw-available-balance-text'));return JSON.stringify({mounted:mounted})})()" ,
106+ "assert" : { "operator" : " eq" , "field" : " mounted" , "value" : true },
107+ "timeout_ms" : 15000 ,
108+ "next" : " capture-withdraw-balance"
109+ },
110+ "capture-withdraw-balance" : {
111+ "action" : " eval_sync" ,
112+ "expression" : " (function(){try{var a=globalThis.__AGENTIC__;var display=a&&a.getTextByTestId('perps-withdraw-available-balance-text')||'';var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var availFloor=Math.floor(avail);var availStr=availFloor.toString();var mentionsAvail=display.length>0&&(display.indexOf(availStr)>=0||(avail<1&&display.indexOf('$0')>=0));var leaksFold=trade>avail+0.5&&display.indexOf(Math.floor(trade).toString())>=0;return JSON.stringify({display:display,availableBalance:avail,availableToTradeBalance:trade,displayMatchesWithdrawable:mentionsAvail,displayLeaksFold:leaksFold,phase:'{{phaseLabel}}'})}catch(e){return JSON.stringify({error:String(e)})}})()" ,
113+ "assert" : {
114+ "all" : [
115+ { "operator" : " eq" , "field" : " displayMatchesWithdrawable" , "value" : true },
116+ { "operator" : " eq" , "field" : " displayLeaksFold" , "value" : false }
117+ ]
118+ },
119+ "next" : " screenshot-withdraw"
120+ },
121+ "screenshot-withdraw" : {
122+ "action" : " screenshot" ,
123+ "filename" : " tat3016-{{phaseLabel}}-withdraw.png" ,
124+ "next" : " nav-market-details"
125+ },
126+ "nav-market-details" : {
127+ "action" : " navigate" ,
128+ "target" : " PerpsMarketDetails" ,
129+ "params" : {
130+ "market" : {
131+ "symbol" : " BTC" ,
132+ "name" : " BTC" ,
133+ "price" : " 0" ,
134+ "change24h" : " 0" ,
135+ "change24hPercent" : " 0" ,
136+ "volume" : " 0" ,
137+ "maxLeverage" : " 100"
138+ }
139+ },
140+ "next" : " wait-long-button"
141+ },
142+ "wait-long-button" : {
143+ "action" : " wait_for" ,
144+ "expression" : " (function(){var a=globalThis.__AGENTIC__;var mounted=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-market-details-long-button'));return JSON.stringify({mounted:mounted})})()" ,
145+ "assert" : { "operator" : " eq" , "field" : " mounted" , "value" : true },
146+ "timeout_ms" : 15000 ,
147+ "next" : " press-long"
148+ },
149+ "press-long" : {
150+ "action" : " press" ,
151+ "test_id" : " perps-market-details-long-button" ,
152+ "next" : " wait-order-form"
153+ },
154+ "wait-order-form" : {
155+ "action" : " wait_for" ,
156+ "expression" : " (function(){var a=globalThis.__AGENTIC__;var btn=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('perps-order-view-place-order-button'));var payWith=!!(a&&a.findFiberByTestId&&a.findFiberByTestId('pay-with-symbol'));return JSON.stringify({placeOrderMounted:btn,payWithMounted:payWith,ready:btn&&payWith})})()" ,
157+ "assert" : { "operator" : " eq" , "field" : " ready" , "value" : true },
158+ "timeout_ms" : 15000 ,
159+ "next" : " capture-order-form"
160+ },
161+ "capture-order-form" : {
162+ "action" : " eval_sync" ,
163+ "expression" : " (function(){try{var a=globalThis.__AGENTIC__;var paySymbol=a&&a.getTextByTestId('pay-with-symbol')||'';var placeOrderMounted=!!(a&&a.findFiberByTestId('perps-order-view-place-order-button'));var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var expectsPerpsBalance=trade>0;var paysWithPerps=paySymbol.toLowerCase().indexOf('perps')>=0;return JSON.stringify({paySymbol:paySymbol,paysWithPerpsBalance:paysWithPerps,placeOrderButtonMounted:placeOrderMounted,defaultsCorrectly:expectsPerpsBalance?paysWithPerps:true,phase:'{{phaseLabel}}'})}catch(e){return JSON.stringify({error:String(e)})}})()" ,
164+ "assert" : {
165+ "all" : [
166+ { "operator" : " eq" , "field" : " defaultsCorrectly" , "value" : true },
167+ { "operator" : " eq" , "field" : " placeOrderButtonMounted" , "value" : true }
168+ ]
169+ },
170+ "next" : " screenshot-order-form"
171+ },
172+ "screenshot-order-form" : {
173+ "action" : " screenshot" ,
174+ "filename" : " tat3016-{{phaseLabel}}-order-form.png" ,
175+ "next" : " back-to-wallet"
176+ },
177+ "back-to-wallet" : {
178+ "action" : " navigate" ,
179+ "target" : " Wallet" ,
180+ "next" : " mode-switch"
181+ },
182+ "mode-switch" : {
183+ "action" : " switch" ,
184+ "cases" : [
185+ {
186+ "label" : " expect-unified" ,
187+ "when" : {
188+ "operator" : " eq" ,
189+ "field" : " inputs.expectedMode" ,
190+ "value" : " unified"
191+ },
192+ "next" : " assert-unified-shape"
193+ },
194+ {
195+ "label" : " expect-standard" ,
196+ "when" : {
197+ "operator" : " eq" ,
198+ "field" : " inputs.expectedMode" ,
199+ "value" : " standard"
200+ },
201+ "next" : " assert-standard-shape"
202+ }
203+ ],
204+ "default" : " done"
205+ },
206+ "assert-unified-shape" : {
207+ "action" : " eval_sync" ,
208+ "expression" : " (function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,spotFold:spotFold,tradeExceedsAvail:trade>=avail,expectedMode:'unified',phase:'{{phaseLabel}}'})})()" ,
209+ "assert" : {
210+ "operator" : " eq" ,
211+ "field" : " tradeExceedsAvail" ,
212+ "value" : true
213+ },
214+ "next" : " done"
215+ },
216+ "assert-standard-shape" : {
217+ "action" : " eval_sync" ,
218+ "expression" : " (function(){var s=(Engine.context.PerpsController.state&&Engine.context.PerpsController.state.accountState)||{};var avail=parseFloat(s.availableBalance||'0');var trade=parseFloat(s.availableToTradeBalance||s.availableBalance||'0');var spotFold=+(trade-avail).toFixed(6);return JSON.stringify({availableBalance:avail,availableToTradeBalance:trade,spotFold:spotFold,foldInvariant:trade>=avail,standardContract:true,expectedMode:'standard',phase:'{{phaseLabel}}'})})()" ,
219+ "assert" : {
220+ "operator" : " eq" ,
221+ "field" : " foldInvariant" ,
222+ "value" : true
223+ },
224+ "next" : " done"
225+ },
226+ "done" : {
227+ "action" : " end" ,
228+ "status" : " pass"
229+ }
230+ }
231+ }
232+ }
233+ }
0 commit comments