@@ -8,6 +8,7 @@ import type { DaemonRequest, DaemonResponse } from '../../../daemon/types.ts';
88
99afterEach ( ( ) => {
1010 vi . restoreAllMocks ( ) ;
11+ vi . useRealTimers ( ) ;
1112} ) ;
1213
1314test ( 'invokeMaestroAssertVisible takes a terminal snapshot when the last miss started before the deadline' , async ( ) => {
@@ -59,6 +60,56 @@ test('invokeMaestroAssertVisible takes a terminal snapshot when the last miss st
5960 }
6061} ) ;
6162
63+ test ( 'invokeMaestroAssertVisible retries transient snapshot failures until a later match' , async ( ) => {
64+ vi . useFakeTimers ( ) ;
65+
66+ let snapshots = 0 ;
67+ const responsePromise = invokeMaestroAssertVisible ( {
68+ baseReq : {
69+ token : 't' ,
70+ session : 's' ,
71+ flags : { platform : 'android' } ,
72+ } ,
73+ positionals : [ 'label="Ready"' , '1000' ] ,
74+ invoke : async ( ) : Promise < DaemonResponse > => {
75+ snapshots += 1 ;
76+ if ( snapshots === 1 ) {
77+ return {
78+ ok : false ,
79+ error : { code : 'SNAPSHOT_FAILED' , message : 'Snapshot temporarily unavailable.' } ,
80+ } ;
81+ }
82+ return {
83+ ok : true ,
84+ data : {
85+ createdAt : 2 ,
86+ nodes : [
87+ {
88+ index : 1 ,
89+ ref : 'e1' ,
90+ type : 'android.widget.TextView' ,
91+ label : 'Ready' ,
92+ rect : { x : 10 , y : 20 , width : 120 , height : 40 } ,
93+ depth : 8 ,
94+ } ,
95+ ] ,
96+ } ,
97+ } ;
98+ } ,
99+ } ) ;
100+
101+ await vi . advanceTimersByTimeAsync ( 250 ) ;
102+ const response = await responsePromise ;
103+
104+ assert . equal ( response . ok , true ) ;
105+ assert . equal ( snapshots , 2 ) ;
106+ if ( response . ok ) {
107+ assert . ok ( response . data ) ;
108+ assert . equal ( response . data . nodeLabel , 'Ready' ) ;
109+ assert . equal ( response . data . waitedMs , 250 ) ;
110+ }
111+ } ) ;
112+
62113test ( 'invokeMaestroAssertNotVisible passes after a slow hidden sample exhausts the timeout' , async ( ) => {
63114 vi . spyOn ( Date , 'now' ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 3500 ) ;
64115
0 commit comments