@@ -8,6 +8,7 @@ import { runCmdBackground, type ExecBackgroundResult } from '../../../utils/exec
88import type { DaemonInvokeFn , DaemonRequest , DaemonResponse , SessionAction } from '../../types.ts' ;
99import type { CommandFlags } from '../../../core/dispatch.ts' ;
1010import { SessionStore } from '../../session-store.ts' ;
11+ import { makeIosSession } from '../../../__tests__/test-utils/index.ts' ;
1112import {
1213 buildReplayVarScope ,
1314 collectReplayShellEnv ,
@@ -475,6 +476,110 @@ test('runReplayScriptFile dispatches resolved literals with file env overridden
475476 }
476477} ) ;
477478
479+ test ( 'runReplayScriptFile reports snapshot diagnostics from per-action session samples' , async ( ) => {
480+ const root = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-replay-snapshot-samples-' ) ) ;
481+ const scriptPath = path . join ( root , 'flow.ad' ) ;
482+ fs . writeFileSync ( scriptPath , [ 'snapshot' , 'snapshot' , '' ] . join ( '\n' ) ) ;
483+ const sessionStore = new SessionStore ( path . join ( root , 'state' ) ) ;
484+ sessionStore . set (
485+ 's' ,
486+ makeIosSession ( 's' , {
487+ snapshotDiagnostics : { samples : [ ] } ,
488+ } ) ,
489+ ) ;
490+ let captures = 0 ;
491+
492+ const response = await runReplayScriptFile ( {
493+ req : {
494+ token : 't' ,
495+ session : 's' ,
496+ command : 'replay' ,
497+ positionals : [ scriptPath ] ,
498+ meta : { cwd : root } ,
499+ } ,
500+ sessionName : 's' ,
501+ logPath : path . join ( root , 'log' ) ,
502+ sessionStore,
503+ invoke : async ( ) : Promise < DaemonResponse > => {
504+ captures += 1 ;
505+ const session = sessionStore . get ( 's' ) ;
506+ session ?. snapshotDiagnostics ?. samples . push ( {
507+ durationMs : captures === 1 ? 400 : 1_900 ,
508+ backend : 'xctest' ,
509+ platform : 'ios' ,
510+ } ) ;
511+ return {
512+ ok : true ,
513+ data : {
514+ snapshotDiagnostics : {
515+ stats : {
516+ count : captures ,
517+ p50Ms : captures === 1 ? 400 : 1_900 ,
518+ p95Ms : captures === 1 ? 400 : 1_900 ,
519+ maxMs : captures === 1 ? 400 : 1_900 ,
520+ slowThresholdMs : 1_500 ,
521+ platform : 'ios' ,
522+ } ,
523+ } ,
524+ } ,
525+ } ;
526+ } ,
527+ } ) ;
528+
529+ assert . equal ( response . ok , true ) ;
530+ const diagnostics = response . data ?. snapshotDiagnostics as
531+ | { stats ?: { count ?: number } ; warning ?: string }
532+ | undefined ;
533+ assert . equal ( diagnostics ?. stats ?. count , 2 ) ;
534+ assert . match ( String ( diagnostics ?. warning ) , / p 9 5 1 9 0 0 m s o v e r 2 c a p t u r e s / ) ;
535+ } ) ;
536+
537+ test ( 'runReplayScriptFile reports snapshot diagnostics on replay failure' , async ( ) => {
538+ const root = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-replay-snapshot-failure-' ) ) ;
539+ const scriptPath = path . join ( root , 'flow.ad' ) ;
540+ fs . writeFileSync ( scriptPath , [ 'snapshot' , 'click "Missing"' , '' ] . join ( '\n' ) ) ;
541+ const sessionStore = new SessionStore ( path . join ( root , 'state' ) ) ;
542+ sessionStore . set (
543+ 's' ,
544+ makeIosSession ( 's' , {
545+ snapshotDiagnostics : { samples : [ ] } ,
546+ } ) ,
547+ ) ;
548+ let captures = 0 ;
549+
550+ const response = await runReplayScriptFile ( {
551+ req : {
552+ token : 't' ,
553+ session : 's' ,
554+ command : 'replay' ,
555+ positionals : [ scriptPath ] ,
556+ meta : { cwd : root } ,
557+ } ,
558+ sessionName : 's' ,
559+ logPath : path . join ( root , 'log' ) ,
560+ sessionStore,
561+ invoke : async ( ) : Promise < DaemonResponse > => {
562+ captures += 1 ;
563+ const session = sessionStore . get ( 's' ) ;
564+ session ?. snapshotDiagnostics ?. samples . push ( {
565+ durationMs : captures === 1 ? 450 : 2_100 ,
566+ backend : 'xctest' ,
567+ platform : 'ios' ,
568+ } ) ;
569+ if ( captures === 1 ) return { ok : true , data : { } } ;
570+ return { ok : false , error : { code : 'COMMAND_FAILED' , message : 'button missing' } } ;
571+ } ,
572+ } ) ;
573+
574+ assert . equal ( response . ok , false ) ;
575+ const diagnostics = response . error . details ?. snapshotDiagnostics as
576+ | { stats ?: { count ?: number ; p95Ms ?: number } ; warning ?: string }
577+ | undefined ;
578+ assert . equal ( diagnostics ?. stats ?. count , 2 ) ;
579+ assert . equal ( diagnostics ?. stats ?. p95Ms , 2_100 ) ;
580+ assert . match ( String ( diagnostics ?. warning ) , / p 9 5 2 1 0 0 m s o v e r 2 c a p t u r e s / ) ;
581+ } ) ;
582+
478583test ( 'runReplayScriptFile applies CLI env overrides before Maestro compat mapping' , async ( ) => {
479584 const { response, calls } = await runReplayFixture ( {
480585 label : 'maestro-env' ,
0 commit comments