@@ -155,6 +155,161 @@ test('press @ref resolves snapshot node and records press action', async () => {
155155 assert . ok ( Array . isArray ( result . selectorChain ) ) ;
156156} ) ;
157157
158+ test ( 'press @ref refreshes snapshot when stored ref bounds are invalid' , async ( ) => {
159+ const sessionStore = makeSessionStore ( ) ;
160+ const sessionName = 'default' ;
161+ const session = makeSession ( sessionName ) ;
162+ session . device = {
163+ platform : 'android' ,
164+ id : 'emulator-5554' ,
165+ name : 'Pixel 8 Pro' ,
166+ kind : 'emulator' ,
167+ booted : true ,
168+ } ;
169+ session . snapshot = {
170+ nodes : attachRefs ( [
171+ {
172+ index : 0 ,
173+ type : 'android.widget.TextView' ,
174+ label : 'My App' ,
175+ // Simulate malformed persisted bounds from older/stale snapshot state.
176+ rect : { x : 20 , y : 40 , width : Number . NaN , height : 40 } ,
177+ enabled : true ,
178+ hittable : true ,
179+ } ,
180+ ] ) ,
181+ createdAt : Date . now ( ) ,
182+ backend : 'android' ,
183+ } ;
184+ sessionStore . set ( sessionName , session ) ;
185+
186+ const pressCalls : Array < { command : string ; positionals : string [ ] } > = [ ] ;
187+ let snapshotCalls = 0 ;
188+ const response = await handleInteractionCommands ( {
189+ req : {
190+ token : 't' ,
191+ session : sessionName ,
192+ command : 'press' ,
193+ positionals : [ '@e1' ] ,
194+ flags : { } ,
195+ } ,
196+ sessionName,
197+ sessionStore,
198+ contextFromFlags,
199+ dispatch : async ( _device , command , positionals ) => {
200+ if ( command === 'snapshot' ) {
201+ snapshotCalls += 1 ;
202+ return {
203+ nodes : [
204+ {
205+ index : 0 ,
206+ type : 'android.widget.TextView' ,
207+ label : 'My App' ,
208+ rect : { x : 20 , y : 40 , width : 100 , height : 40 } ,
209+ enabled : true ,
210+ hittable : true ,
211+ } ,
212+ ] ,
213+ backend : 'android' ,
214+ } ;
215+ }
216+ pressCalls . push ( { command, positionals } ) ;
217+ return { pressed : true } ;
218+ } ,
219+ } ) ;
220+
221+ assert . ok ( response ) ;
222+ assert . equal ( response . ok , true ) ;
223+ assert . equal ( snapshotCalls , 1 ) ;
224+ assert . equal ( pressCalls . length , 1 ) ;
225+ assert . equal ( pressCalls [ 0 ] ?. command , 'press' ) ;
226+ assert . deepEqual ( pressCalls [ 0 ] ?. positionals , [ '70' , '60' ] ) ;
227+ if ( response . ok ) {
228+ assert . equal ( response . data ?. x , 70 ) ;
229+ assert . equal ( response . data ?. y , 60 ) ;
230+ assert . equal ( response . data ?. ref , 'e1' ) ;
231+ }
232+ } ) ;
233+
234+ test ( 'press @ref fallback label is used after refresh when ref bounds remain invalid' , async ( ) => {
235+ const sessionStore = makeSessionStore ( ) ;
236+ const sessionName = 'default' ;
237+ const session = makeSession ( sessionName ) ;
238+ session . device = {
239+ platform : 'android' ,
240+ id : 'emulator-5554' ,
241+ name : 'Pixel 8 Pro' ,
242+ kind : 'emulator' ,
243+ booted : true ,
244+ } ;
245+ session . snapshot = {
246+ nodes : attachRefs ( [
247+ {
248+ index : 0 ,
249+ type : 'android.widget.TextView' ,
250+ label : 'My App' ,
251+ rect : { x : 20 , y : 40 , width : Number . NaN , height : 40 } ,
252+ enabled : true ,
253+ hittable : true ,
254+ } ,
255+ ] ) ,
256+ createdAt : Date . now ( ) ,
257+ backend : 'android' ,
258+ } ;
259+ sessionStore . set ( sessionName , session ) ;
260+
261+ const pressCalls : Array < { command : string ; positionals : string [ ] } > = [ ] ;
262+ const response = await handleInteractionCommands ( {
263+ req : {
264+ token : 't' ,
265+ session : sessionName ,
266+ command : 'press' ,
267+ positionals : [ '@e1' , 'My App' ] ,
268+ flags : { } ,
269+ } ,
270+ sessionName,
271+ sessionStore,
272+ contextFromFlags,
273+ dispatch : async ( _device , command , positionals ) => {
274+ if ( command === 'snapshot' ) {
275+ return {
276+ nodes : [
277+ {
278+ index : 0 ,
279+ type : 'android.widget.TextView' ,
280+ label : 'Different' ,
281+ rect : { x : 20 , y : 40 , width : Number . NaN , height : 40 } ,
282+ enabled : true ,
283+ hittable : true ,
284+ } ,
285+ {
286+ index : 1 ,
287+ type : 'android.widget.TextView' ,
288+ label : 'My App' ,
289+ rect : { x : 100 , y : 200 , width : 80 , height : 40 } ,
290+ enabled : true ,
291+ hittable : true ,
292+ } ,
293+ ] ,
294+ backend : 'android' ,
295+ } ;
296+ }
297+ pressCalls . push ( { command, positionals } ) ;
298+ return { pressed : true } ;
299+ } ,
300+ } ) ;
301+
302+ assert . ok ( response ) ;
303+ assert . equal ( response . ok , true ) ;
304+ assert . equal ( pressCalls . length , 1 ) ;
305+ assert . equal ( pressCalls [ 0 ] ?. command , 'press' ) ;
306+ assert . deepEqual ( pressCalls [ 0 ] ?. positionals , [ '140' , '220' ] ) ;
307+ if ( response . ok ) {
308+ assert . equal ( response . data ?. x , 140 ) ;
309+ assert . equal ( response . data ?. y , 220 ) ;
310+ }
311+ } ) ;
312+
158313test ( 'press coordinates does not treat extra trailing args as selector' , async ( ) => {
159314 const sessionStore = makeSessionStore ( ) ;
160315 const sessionName = 'default' ;
0 commit comments