11import assert from 'node:assert/strict' ;
22import { afterEach , test , vi } from 'vitest' ;
3- import { invokeMaestroAssertNotVisible , invokeMaestroAssertVisible } from '../runtime-assertions.ts' ;
3+ import {
4+ invokeMaestroAssertNotVisible ,
5+ invokeMaestroAssertVisible ,
6+ } from '../runtime-assertions.ts' ;
47import type { DaemonRequest , DaemonResponse } from '../../../daemon/types.ts' ;
58
69afterEach ( ( ) => {
710 vi . restoreAllMocks ( ) ;
11+ vi . useRealTimers ( ) ;
812} ) ;
913
1014test ( 'invokeMaestroAssertVisible takes a terminal snapshot when the last miss started before the deadline' , async ( ) => {
@@ -56,6 +60,56 @@ test('invokeMaestroAssertVisible takes a terminal snapshot when the last miss st
5660 }
5761} ) ;
5862
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+
59113test ( 'invokeMaestroAssertNotVisible passes after a slow hidden sample exhausts the timeout' , async ( ) => {
60114 vi . spyOn ( Date , 'now' ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 3500 ) ;
61115
@@ -80,9 +134,10 @@ test('invokeMaestroAssertNotVisible passes after a slow hidden sample exhausts t
80134 } ) ;
81135
82136 assert . equal ( response . ok , true ) ;
83- assert . deepEqual ( calls . map ( ( call ) => [ call . command , call . positionals ] ) , [
84- [ 'snapshot' , [ ] ] ,
85- ] ) ;
137+ assert . deepEqual (
138+ calls . map ( ( call ) => [ call . command , call . positionals ] ) ,
139+ [ [ 'snapshot' , [ ] ] ] ,
140+ ) ;
86141 if ( response . ok ) {
87142 assert . ok ( response . data ) ;
88143 assert . equal ( response . data . stableSamples , 1 ) ;
@@ -125,3 +180,30 @@ test('invokeMaestroAssertNotVisible ignores matched nodes without visible rects'
125180 assert . equal ( response . data . stableSamples , 1 ) ;
126181 }
127182} ) ;
183+
184+ test ( 'invokeMaestroAssertNotVisible accepts timeout overrides for short extended waits' , async ( ) => {
185+ vi . spyOn ( Date , 'now' ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 0 ) . mockReturnValueOnce ( 300 ) ;
186+
187+ const response = await invokeMaestroAssertNotVisible ( {
188+ baseReq : {
189+ token : 't' ,
190+ session : 's' ,
191+ flags : { } ,
192+ } ,
193+ positionals : [ 'id="toast"' , '1' ] ,
194+ invoke : async ( ) : Promise < DaemonResponse > => ( {
195+ ok : true ,
196+ data : {
197+ createdAt : 1 ,
198+ nodes : [ ] ,
199+ } ,
200+ } ) ,
201+ } ) ;
202+
203+ assert . equal ( response . ok , true ) ;
204+ if ( response . ok ) {
205+ assert . ok ( response . data ) ;
206+ assert . equal ( response . data . stableSamples , 1 ) ;
207+ assert . equal ( response . data . timeoutMs , 1 ) ;
208+ }
209+ } ) ;
0 commit comments