@@ -224,6 +224,38 @@ test('resolveReplayAction walks runtime hints', () => {
224224 assert . equal ( resolved . runtime ?. metroHost , '10.0.0.1' ) ;
225225} ) ;
226226
227+ test ( 'resolveReplayAction resolves replay control conditions without pre-resolving nested actions' , ( ) => {
228+ const action : SessionAction = {
229+ ts : 0 ,
230+ command : 'runFlow.when' ,
231+ positionals : [ 'visible' , '${VISIBLE}' ] ,
232+ flags : { } ,
233+ replayControl : {
234+ kind : 'maestroRunFlowWhen' ,
235+ mode : 'visible' ,
236+ selector : '${VISIBLE}' ,
237+ actions : [
238+ {
239+ ts : 0 ,
240+ command : 'tap' ,
241+ positionals : [ '${TARGET}' ] ,
242+ flags : { } ,
243+ } ,
244+ ] ,
245+ } ,
246+ } ;
247+ const scope = buildReplayVarScope ( {
248+ fileEnv : { VISIBLE : 'Feed' , TARGET : '${NEXT}' , NEXT : 'Done' } ,
249+ } ) ;
250+ const resolved = resolveReplayAction ( action , scope , LOC ) ;
251+ assert . equal ( resolved . replayControl ?. kind , 'maestroRunFlowWhen' ) ;
252+ if ( resolved . replayControl ?. kind !== 'maestroRunFlowWhen' ) {
253+ throw new Error ( 'expected runFlow.when control' ) ;
254+ }
255+ assert . equal ( resolved . replayControl . selector , 'Feed' ) ;
256+ assert . deepEqual ( resolved . replayControl . actions [ 0 ] ?. positionals , [ '${TARGET}' ] ) ;
257+ } ) ;
258+
227259test ( 'parseReplayScriptDetailed tracks line numbers' , ( ) => {
228260 const script = [
229261 '# comment' ,
@@ -1824,6 +1856,72 @@ test('runReplayScriptFile runs nested Maestro runtime commands inside runFlow.wh
18241856 ) ;
18251857} ) ;
18261858
1859+ test ( 'runReplayScriptFile resolves nested Maestro runFlow.when command variables once at execution' , async ( ) => {
1860+ const calls : CapturedInvocation [ ] = [ ] ;
1861+ const { response } = await runReplayFixture ( {
1862+ label : 'maestro-run-flow-when-nested-vars' ,
1863+ script : [
1864+ 'appId: demo.app' ,
1865+ 'env:' ,
1866+ ' TARGET_LABEL: ${NEXT_LABEL}' ,
1867+ ' NEXT_LABEL: ${FINAL_LABEL}' ,
1868+ ' FINAL_LABEL: Done' ,
1869+ '---' ,
1870+ '- runFlow:' ,
1871+ ' when:' ,
1872+ ' visible: Feed' ,
1873+ ' commands:' ,
1874+ ' - tapOn: ${TARGET_LABEL}' ,
1875+ '' ,
1876+ ] . join ( '\n' ) ,
1877+ flags : { replayBackend : 'maestro' } ,
1878+ invoke : async ( req ) => {
1879+ calls . push ( { command : req . command , positionals : req . positionals , flags : req . flags } ) ;
1880+ if ( req . command === 'snapshot' ) {
1881+ return {
1882+ ok : true ,
1883+ data : {
1884+ nodes : [
1885+ {
1886+ index : 0 ,
1887+ type : 'application' ,
1888+ rect : { x : 0 , y : 0 , width : 390 , height : 844 } ,
1889+ } ,
1890+ {
1891+ index : 1 ,
1892+ depth : 1 ,
1893+ parentIndex : 0 ,
1894+ type : 'statictext' ,
1895+ label : 'Feed' ,
1896+ rect : { x : 16 , y : 100 , width : 120 , height : 24 } ,
1897+ } ,
1898+ {
1899+ index : 2 ,
1900+ depth : 1 ,
1901+ parentIndex : 0 ,
1902+ type : 'button' ,
1903+ label : '${FINAL_LABEL}' ,
1904+ rect : { x : 100 , y : 300 , width : 80 , height : 40 } ,
1905+ } ,
1906+ ] ,
1907+ } ,
1908+ } ;
1909+ }
1910+ return { ok : true , data : { } } ;
1911+ } ,
1912+ } ) ;
1913+
1914+ assert . equal ( response . ok , true ) ;
1915+ assert . deepEqual (
1916+ calls . map ( ( call ) => [ call . command , call . positionals ] ) ,
1917+ [
1918+ [ 'snapshot' , [ ] ] ,
1919+ [ 'snapshot' , [ ] ] ,
1920+ [ 'click' , [ '140' , '320' ] ] ,
1921+ ] ,
1922+ ) ;
1923+ } ) ;
1924+
18271925test ( 'runReplayScriptFile reads shell env from request (client-collected), not daemon process.env' , async ( ) => {
18281926 // Ensure the daemon's own process.env does NOT contain AD_VAR_APP.
18291927 assert . equal ( process . env . AD_VAR_APP , undefined ) ;
0 commit comments