@@ -128,13 +128,44 @@ test('runner session executes read-only commands without uptime preflight', asyn
128128 assert . deepEqual ( result , { nodes : [ ] , truncated : false } ) ;
129129 assert . equal ( session . ready , true ) ;
130130 assert . equal ( mockWaitForRunner . mock . calls . length , 1 ) ;
131- assert . deepEqual ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , {
131+ assertRunnerCommand ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , {
132132 command : 'snapshot' ,
133133 appBundleId : 'com.example.demo' ,
134134 } ) ;
135135 assert . equal ( mockSendRunnerCommandOnce . mock . calls . length , 0 ) ;
136136} ) ;
137137
138+ test ( 'runner session executes status command as read-only lifecycle command' , async ( ) => {
139+ const session = makeRunnerSession ( { ready : true } ) ;
140+ mockWaitForRunner . mockResolvedValueOnce (
141+ runnerResponse ( {
142+ commandId : 'runner-command-1' ,
143+ lifecycleState : 'completed' ,
144+ lifecycleResponseOk : true ,
145+ } ) ,
146+ ) ;
147+
148+ const result = await executeRunnerCommandWithSession (
149+ IOS_SIMULATOR ,
150+ session ,
151+ { command : 'status' , statusCommandId : 'runner-command-1' } ,
152+ '/tmp/runner.log' ,
153+ 30_000 ,
154+ ) ;
155+
156+ assert . deepEqual ( result , {
157+ commandId : 'runner-command-1' ,
158+ lifecycleState : 'completed' ,
159+ lifecycleResponseOk : true ,
160+ } ) ;
161+ assert . equal ( mockWaitForRunner . mock . calls . length , 1 ) ;
162+ assertRunnerCommand ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , {
163+ command : 'status' ,
164+ statusCommandId : 'runner-command-1' ,
165+ } ) ;
166+ assert . equal ( mockSendRunnerCommandOnce . mock . calls . length , 0 ) ;
167+ } ) ;
168+
138169test ( 'runner session probes readiness before mutating commands' , async ( ) => {
139170 const session = makeRunnerSession ( { ready : false } ) ;
140171 mockWaitForRunner . mockResolvedValueOnce ( runnerResponse ( { uptimeMs : 42 } ) ) ;
@@ -151,9 +182,9 @@ test('runner session probes readiness before mutating commands', async () => {
151182 assert . deepEqual ( result , { tapped : true } ) ;
152183 assert . equal ( session . ready , true ) ;
153184 assert . equal ( mockWaitForRunner . mock . calls . length , 1 ) ;
154- assert . deepEqual ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
185+ assertRunnerCommand ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
155186 assert . equal ( mockSendRunnerCommandOnce . mock . calls . length , 1 ) ;
156- assert . deepEqual ( mockSendRunnerCommandOnce . mock . calls [ 0 ] ?. [ 2 ] , {
187+ assertRunnerCommand ( mockSendRunnerCommandOnce . mock . calls [ 0 ] ?. [ 2 ] , {
157188 command : 'tap' ,
158189 x : 120 ,
159190 y : 240 ,
@@ -239,7 +270,7 @@ test('runner session keeps readiness preflight for tap commands when ready but n
239270
240271 assert . deepEqual ( result , { tapped : true } ) ;
241272 assert . equal ( mockWaitForRunner . mock . calls . length , 1 ) ;
242- assert . deepEqual ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
273+ assertRunnerCommand ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
243274 assert . equal ( mockSendRunnerCommandOnce . mock . calls . length , 1 ) ;
244275} ) ;
245276
@@ -261,7 +292,7 @@ test('runner session keeps readiness preflight for tap commands when marked read
261292
262293 assert . deepEqual ( result , { tapped : true } ) ;
263294 assert . equal ( mockWaitForRunner . mock . calls . length , 1 ) ;
264- assert . deepEqual ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
295+ assertRunnerCommand ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
265296 assert . equal ( mockSendRunnerCommandOnce . mock . calls . length , 1 ) ;
266297} ) ;
267298
@@ -280,7 +311,7 @@ test('runner session keeps readiness preflight for non-tap mutating commands whe
280311
281312 assert . deepEqual ( result , { pressed : true } ) ;
282313 assert . equal ( mockWaitForRunner . mock . calls . length , 1 ) ;
283- assert . deepEqual ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
314+ assertRunnerCommand ( mockWaitForRunner . mock . calls [ 0 ] ?. [ 2 ] , { command : 'uptime' } ) ;
284315 assert . equal ( mockSendRunnerCommandOnce . mock . calls . length , 1 ) ;
285316} ) ;
286317
@@ -371,7 +402,7 @@ test('runner session stop sends shutdown, cleans temporary runner files, and rel
371402 mockIsProcessAlive . mockReturnValue ( false ) ;
372403 await stopRunnerSession ( session ) ;
373404
374- assert . deepEqual ( mockWaitForRunner . mock . calls . at ( - 1 ) ?. [ 2 ] , { command : 'shutdown' } ) ;
405+ assertRunnerCommand ( mockWaitForRunner . mock . calls . at ( - 1 ) ?. [ 2 ] , { command : 'shutdown' } ) ;
375406 assert . deepEqual ( mockCleanupTempFile . mock . calls , [
376407 [ '/tmp/session-runner.xctestrun' ] ,
377408 [ '/tmp/session-runner.json' ] ,
@@ -453,3 +484,18 @@ function runnerResponse(data: Record<string, unknown>): Response {
453484function runnerError ( error : { code : string ; message : string } ) : Response {
454485 return new Response ( JSON . stringify ( { ok : false , error } ) ) ;
455486}
487+
488+ function assertRunnerCommand (
489+ actual : unknown ,
490+ expected : Record < string , unknown > ,
491+ ) : asserts actual is Record < string , unknown > {
492+ assert . equal ( typeof actual , 'object' ) ;
493+ assert . notEqual ( actual , null ) ;
494+ const command = actual as Record < string , unknown > ;
495+ const commandId = command . commandId ;
496+ if ( typeof commandId !== 'string' ) {
497+ assert . fail ( 'expected runner commandId' ) ;
498+ }
499+ assert . match ( commandId , / ^ r u n n e r - / ) ;
500+ assert . deepEqual ( { ...command , commandId : undefined } , { ...expected , commandId : undefined } ) ;
501+ }
0 commit comments