@@ -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 ( ) => {
@@ -119,9 +123,93 @@ test('test command --verbose prints all test statuses', async () => {
119123 ) ;
120124
121125 assert . equal ( result . code , 1 ) ;
126+ assert . equal ( result . calls [ 0 ] ?. meta ?. debug , false ) ;
122127 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 / ) ;
128+ assert . match ( result . stdout , / P A S S 0 1 - p a s s \. a d \( 0 \. 0 1 s \) / ) ;
129+ assert . match ( result . stdout , / S K I P 0 3 - s k i p \. a d / ) ;
130+ } ) ;
131+
132+ test ( 'test command --verbose prints step telemetry for passing tests without debug mode' , async ( ) => {
133+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'agent-device-cli-test-verbose-' ) ) ;
134+ const artifactsDir = path . join ( tmpDir , 'auth-flow' ) ;
135+ const attemptDir = path . join ( artifactsDir , 'attempt-1' ) ;
136+ await fs . mkdir ( attemptDir , { recursive : true } ) ;
137+ await fs . writeFile (
138+ path . join ( attemptDir , 'replay-timing.ndjson' ) ,
139+ [
140+ {
141+ type : 'replay_action_start' ,
142+ step : 1 ,
143+ line : 3 ,
144+ command : '__maestroTapOn' ,
145+ positionals : [ 'text="Log in"' ] ,
146+ } ,
147+ {
148+ type : 'replay_action_stop' ,
149+ step : 1 ,
150+ line : 3 ,
151+ command : '__maestroTapOn' ,
152+ ok : true ,
153+ durationMs : 250 ,
154+ } ,
155+ {
156+ type : 'replay_action_start' ,
157+ step : 2 ,
158+ line : 4 ,
159+ command : '__maestroAssertVisible' ,
160+ positionals : [ 'text="Home"' ] ,
161+ } ,
162+ {
163+ type : 'replay_action_stop' ,
164+ step : 2 ,
165+ line : 4 ,
166+ command : '__maestroAssertVisible' ,
167+ ok : true ,
168+ durationMs : 75 ,
169+ } ,
170+ ]
171+ . map ( ( entry ) => JSON . stringify ( entry ) )
172+ . join ( '\n' ) ,
173+ ) ;
174+
175+ try {
176+ const result = await runCliCapture ( [ 'test' , './suite' , '--verbose' ] , async ( ) => ( {
177+ ok : true ,
178+ data : {
179+ total : 1 ,
180+ executed : 1 ,
181+ passed : 1 ,
182+ failed : 0 ,
183+ skipped : 0 ,
184+ notRun : 0 ,
185+ durationMs : 500 ,
186+ failures : [ ] ,
187+ tests : [
188+ {
189+ file : '/tmp/auth-flow.yml' ,
190+ title : 'Authentication flow' ,
191+ session : 'default:test:suite:1' ,
192+ status : 'passed' ,
193+ durationMs : 500 ,
194+ finalAttemptDurationMs : 500 ,
195+ attempts : 1 ,
196+ artifactsDir,
197+ replayed : 2 ,
198+ healed : 0 ,
199+ } ,
200+ ] ,
201+ } ,
202+ } ) ) ;
203+
204+ assert . equal ( result . code , null ) ;
205+ assert . equal ( result . calls [ 0 ] ?. meta ?. debug , false ) ;
206+ assert . match ( result . stdout , / P A S S " A u t h e n t i c a t i o n f l o w " \( 0 \. 5 s \) / ) ;
207+ assert . match ( result . stdout , / s t e p s \( a t t e m p t 1 \) : / ) ;
208+ assert . match ( result . stdout , / \[ o k \] t a p O n " t e x t = \\ " L o g i n \\ " " \( l i n e 3 , 0 \. 2 5 s \) / ) ;
209+ assert . match ( result . stdout , / \[ o k \] a s s e r t V i s i b l e " t e x t = \\ " H o m e \\ " " \( l i n e 4 , 0 \. 0 7 5 s \) / ) ;
210+ } finally {
211+ await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
212+ }
125213} ) ;
126214
127215test ( 'test command reports flaky passed-on-retry cases in the default summary' , async ( ) => {
@@ -138,20 +226,127 @@ test('test command reports flaky passed-on-retry cases in the default summary',
138226 failures : [ ] ,
139227 tests : [
140228 {
141- file : '/tmp/01-flaky.ad' ,
229+ file : '/tmp/auth-flow.yml' ,
230+ title : 'Authentication flow' ,
142231 session : 'default:test:suite:1' ,
143232 status : 'passed' ,
144- durationMs : 10 ,
233+ durationMs : 112151 ,
234+ finalAttemptDurationMs : 17492 ,
145235 attempts : 2 ,
236+ attemptFailures : [
237+ {
238+ attempt : 1 ,
239+ message : 'Replay failed at step 3 (tapOn "Log in"): selector not found' ,
240+ durationMs : 94659 ,
241+ } ,
242+ ] ,
146243 } ,
147244 ] ,
148245 } ,
149246 } ) ) ;
150247
151248 assert . equal ( result . code , null ) ;
152249 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 / ) ;
250+ assert . doesNotMatch ( result . stdout , / F L A K Y / ) ;
251+ 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 / ) ;
252+ assert . match ( result . stdout , / F l a k y t e s t s : / ) ;
253+ assert . match (
254+ result . stdout ,
255+ / 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 \) / ,
256+ ) ;
257+ assert . match (
258+ result . stdout ,
259+ / 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 / ,
260+ ) ;
261+ } ) ;
262+
263+ test ( 'test command prints failed attempt step telemetry when timing trace exists' , async ( ) => {
264+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'agent-device-cli-test-steps-' ) ) ;
265+ const artifactsDir = path . join ( tmpDir , 'checkout-flow' ) ;
266+ const attemptDir = path . join ( artifactsDir , 'attempt-2' ) ;
267+ await fs . mkdir ( attemptDir , { recursive : true } ) ;
268+ await fs . writeFile (
269+ path . join ( attemptDir , 'replay-timing.ndjson' ) ,
270+ [
271+ {
272+ type : 'replay_action_start' ,
273+ step : 1 ,
274+ line : 3 ,
275+ command : 'open' ,
276+ positionals : [ 'Demo' ] ,
277+ } ,
278+ {
279+ type : 'replay_action_stop' ,
280+ step : 1 ,
281+ line : 3 ,
282+ command : 'open' ,
283+ ok : true ,
284+ durationMs : 125 ,
285+ resultTiming : { launchMs : 100 } ,
286+ } ,
287+ {
288+ type : 'replay_action_start' ,
289+ step : 2 ,
290+ line : 4 ,
291+ command : '__maestroTapOn' ,
292+ positionals : [ 'text="Pay"' ] ,
293+ } ,
294+ {
295+ type : 'replay_action_stop' ,
296+ step : 2 ,
297+ line : 4 ,
298+ command : '__maestroTapOn' ,
299+ ok : false ,
300+ durationMs : 1500 ,
301+ errorCode : 'ASSERTION_FAILED' ,
302+ } ,
303+ ]
304+ . map ( ( entry ) => JSON . stringify ( entry ) )
305+ . join ( '\n' ) ,
306+ ) ;
307+
308+ try {
309+ const failedReplayResult = {
310+ file : '/tmp/checkout-flow.yml' ,
311+ title : 'Checkout flow' ,
312+ session : 'default:test:suite:1' ,
313+ status : 'failed' ,
314+ durationMs : 2000 ,
315+ attempts : 2 ,
316+ artifactsDir,
317+ error : {
318+ code : 'ASSERTION_FAILED' ,
319+ message : 'Replay failed at step 2 (click "Pay"): selector not found' ,
320+ } ,
321+ } ;
322+ const result = await runCliCapture ( [ 'test' , './suite' ] , async ( ) => ( {
323+ ok : true ,
324+ data : {
325+ total : 1 ,
326+ executed : 1 ,
327+ passed : 0 ,
328+ failed : 1 ,
329+ skipped : 0 ,
330+ notRun : 0 ,
331+ durationMs : 2000 ,
332+ failures : [ failedReplayResult ] ,
333+ tests : [ failedReplayResult ] ,
334+ } ,
335+ } ) ) ;
336+
337+ assert . equal ( result . code , 1 ) ;
338+ assert . match ( result . stdout , / s t e p s \( a t t e m p t 2 \) : / ) ;
339+ assert . match (
340+ result . stdout ,
341+ / \[ 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 \} \) / ,
342+ ) ;
343+ assert . match (
344+ result . stdout ,
345+ / \[ 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 \) / ,
346+ ) ;
347+ } finally {
348+ await fs . rm ( tmpDir , { recursive : true , force : true } ) ;
349+ }
155350} ) ;
156351
157352test ( 'test --maestro forwards Maestro backend and platform for directory suites' , async ( ) => {
0 commit comments