@@ -8,6 +8,7 @@ import { runCliCapture } from './cli-capture.ts';
88function makeFailedReplayResult ( ) {
99 return {
1010 file : '/tmp/02-fail.ad' ,
11+ title : 'Checkout failure' ,
1112 session : 'default:test:suite:2' ,
1213 status : 'failed' ,
1314 durationMs : 5 ,
@@ -106,11 +107,14 @@ test('test command prints suite summary and exits non-zero on failures', async (
106107 assert . equal ( result . calls [ 0 ] ?. meta ?. requestProgress , 'replay-test' ) ;
107108 assert . match ( result . stderr , / R u n n i n g r e p l a y s u i t e \. \. \. / ) ;
108109 assert . doesNotMatch ( result . stdout , / P A S S \/ t m p \/ 0 1 - p a s s \. a d / ) ;
109- assert . match ( result . stdout , / F A I L \/ t m p \/ 0 2 - f a i l \. a d a f t e r 2 a t t e m p t s \( 5 m s \) / ) ;
110+ assert . match (
111+ result . stdout ,
112+ / F A I L " C h e c k o u t f a i l u r e " i n 0 2 - f a i l \. a d a f t e r 2 a t t e m p t s \( t o t a l 0 \. 0 0 5 s \) / ,
113+ ) ;
110114 assert . match ( result . stdout , / R e p l a y f a i l e d a t s t e p 1 \( o p e n D e m o \) : b o o m / ) ;
111115 assert . match ( result . stdout , / a r t i f a c t s : \/ t m p \/ t e s t - a r t i f a c t s \/ 0 2 - f a i l / ) ;
112116 assert . doesNotMatch ( result . stdout , / S K I P \/ t m p \/ 0 3 - s k i p \. a d / ) ;
113- assert . match ( result . stdout , / T e s t s u m m a r y : 1 p a s s e d , 1 f a i l e d i n 2 5 m s / ) ;
117+ assert . match ( result . stdout , / T e s t s u m m a r y : 1 p a s s e d , 1 f a i l e d i n 0 \. 0 2 5 s / ) ;
114118} ) ;
115119
116120test ( 'test command --verbose prints all test statuses' , async ( ) => {
@@ -120,8 +124,8 @@ test('test command --verbose prints all test statuses', async () => {
120124
121125 assert . equal ( result . code , 1 ) ;
122126 assert . match ( result . stderr , / R u n n i n g r e p l a y s u i t e \. \. \. / ) ;
123- assert . match ( result . stdout , / P A S S \/ t m p \/ 0 1 - p a s s \. a d \( 1 0 m s \) / ) ;
124- assert . match ( result . stdout , / S K I P \/ t m p \/ 0 3 - s k i p \. a d / ) ;
127+ assert . match ( result . stdout , / P A S S 0 1 - p a s s \. a d \( 0 \. 0 1 s \) / ) ;
128+ assert . match ( result . stdout , / S K I P 0 3 - s k i p \. a d / ) ;
125129} ) ;
126130
127131test ( 'test command reports flaky passed-on-retry cases in the default summary' , async ( ) => {
@@ -138,20 +142,127 @@ test('test command reports flaky passed-on-retry cases in the default summary',
138142 failures : [ ] ,
139143 tests : [
140144 {
141- file : '/tmp/01-flaky.ad' ,
145+ file : '/tmp/auth-flow.yml' ,
146+ title : 'Authentication flow' ,
142147 session : 'default:test:suite:1' ,
143148 status : 'passed' ,
144- durationMs : 10 ,
149+ durationMs : 112151 ,
150+ finalAttemptDurationMs : 17492 ,
145151 attempts : 2 ,
152+ attemptFailures : [
153+ {
154+ attempt : 1 ,
155+ message : 'Replay failed at step 3 (tapOn "Log in"): selector not found' ,
156+ durationMs : 94659 ,
157+ } ,
158+ ] ,
146159 } ,
147160 ] ,
148161 } ,
149162 } ) ) ;
150163
151164 assert . equal ( result . code , null ) ;
152165 assert . match ( result . stderr , / R u n n i n g r e p l a y s u i t e \. \. \. / ) ;
153- assert . match ( result . stdout , / F L A K Y \/ t m p \/ 0 1 - f l a k y \. a d a f t e r 2 a t t e m p t s \( 1 0 m s \) / ) ;
154- assert . match ( result . stdout , / T e s t s u m m a r y : 1 p a s s e d , 0 f a i l e d , 1 f l a k y i n 2 5 m s / ) ;
166+ assert . doesNotMatch ( result . stdout , / F L A K Y / ) ;
167+ assert . match ( result . stdout , / T e s t s u m m a r y : 1 p a s s e d , 0 f a i l e d , 1 f l a k y i n 0 \. 0 2 5 s / ) ;
168+ assert . match ( result . stdout , / F l a k y t e s t s : / ) ;
169+ assert . match (
170+ result . stdout ,
171+ / P A S S " A u t h e n t i c a t i o n f l o w " a f t e r 2 a t t e m p t s \( p a s s e d a t t e m p t 1 7 \. 5 s , t o t a l 1 1 2 \. 2 s \) / ,
172+ ) ;
173+ assert . match (
174+ result . stdout ,
175+ / a t t e m p t 1 f a i l e d \( 9 4 \. 7 s \) : R e p l a y f a i l e d a t s t e p 3 \( t a p O n " L o g i n " \) : s e l e c t o r n o t f o u n d / ,
176+ ) ;
177+ } ) ;
178+
179+ test ( 'test command prints failed attempt step telemetry when timing trace exists' , async ( ) => {
180+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'agent-device-cli-test-steps-' ) ) ;
181+ const artifactsDir = path . join ( tmpDir , 'checkout-flow' ) ;
182+ const attemptDir = path . join ( artifactsDir , 'attempt-2' ) ;
183+ await fs . mkdir ( attemptDir , { recursive : true } ) ;
184+ await fs . writeFile (
185+ path . join ( attemptDir , 'replay-timing.ndjson' ) ,
186+ [
187+ {
188+ type : 'replay_action_start' ,
189+ step : 1 ,
190+ line : 3 ,
191+ command : 'open' ,
192+ positionals : [ 'Demo' ] ,
193+ } ,
194+ {
195+ type : 'replay_action_stop' ,
196+ step : 1 ,
197+ line : 3 ,
198+ command : 'open' ,
199+ ok : true ,
200+ durationMs : 125 ,
201+ resultTiming : { launchMs : 100 } ,
202+ } ,
203+ {
204+ type : 'replay_action_start' ,
205+ step : 2 ,
206+ line : 4 ,
207+ command : '__maestroTapOn' ,
208+ positionals : [ 'text="Pay"' ] ,
209+ } ,
210+ {
211+ type : 'replay_action_stop' ,
212+ step : 2 ,
213+ line : 4 ,
214+ command : '__maestroTapOn' ,
215+ ok : false ,
216+ durationMs : 1500 ,
217+ errorCode : 'ASSERTION_FAILED' ,
218+ } ,
219+ ]
220+ . map ( ( entry ) => JSON . stringify ( entry ) )
221+ . join ( '\n' ) ,
222+ ) ;
223+
224+ try {
225+ const failedReplayResult = {
226+ file : '/tmp/checkout-flow.yml' ,
227+ title : 'Checkout flow' ,
228+ session : 'default:test:suite:1' ,
229+ status : 'failed' ,
230+ durationMs : 2000 ,
231+ attempts : 2 ,
232+ artifactsDir,
233+ error : {
234+ code : 'ASSERTION_FAILED' ,
235+ message : 'Replay failed at step 2 (click "Pay"): selector not found' ,
236+ } ,
237+ } ;
238+ const result = await runCliCapture ( [ 'test' , './suite' ] , async ( ) => ( {
239+ ok : true ,
240+ data : {
241+ total : 1 ,
242+ executed : 1 ,
243+ passed : 0 ,
244+ failed : 1 ,
245+ skipped : 0 ,
246+ notRun : 0 ,
247+ durationMs : 2000 ,
248+ failures : [ failedReplayResult ] ,
249+ tests : [ failedReplayResult ] ,
250+ } ,
251+ } ) ) ;
252+
253+ assert . equal ( result . code , 1 ) ;
254+ assert . match ( result . stdout , / s t e p s \( a t t e m p t 2 \) : / ) ;
255+ assert . match (
256+ result . stdout ,
257+ / \[ o k \] o p e n " D e m o " \( l i n e 3 , 0 \. 1 2 5 s , t i m i n g \{ " l a u n c h M s " : 1 0 0 \} \) / ,
258+ ) ;
259+ assert . match (
260+ result . stdout ,
261+ / \[ F A I L \] t a p O n " t e x t = \\ " P a y \\ " " \( l i n e 4 , 1 \. 5 0 s , A S S E R T I O N _ F A I L E D \) / ,
262+ ) ;
263+ } finally {
264+ await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
265+ }
155266} ) ;
156267
157268test ( 'test --maestro forwards Maestro backend and platform for directory suites' , async ( ) => {
0 commit comments