@@ -258,6 +258,157 @@ test('buildScreenshotOverlayRefs prefers descendant text over generic android re
258258 ] ) ;
259259} ) ;
260260
261+ test ( 'buildScreenshotOverlayRefs keeps Android pixel rects aligned with screenshots' , ( ) => {
262+ const snapshot = makeSnapshotState (
263+ [
264+ {
265+ index : 0 ,
266+ type : 'android.widget.ScrollView' ,
267+ rect : { x : 0 , y : 0 , width : 1344 , height : 2920 } ,
268+ } ,
269+ {
270+ index : 1 ,
271+ parentIndex : 0 ,
272+ type : 'android.widget.LinearLayout' ,
273+ hittable : true ,
274+ rect : { x : 0 , y : 2697 , width : 1344 , height : 223 } ,
275+ } ,
276+ {
277+ index : 2 ,
278+ parentIndex : 1 ,
279+ type : 'android.widget.TextView' ,
280+ label : 'Storage' ,
281+ rect : { x : 240 , y : 2745 , width : 205 , height : 81 } ,
282+ } ,
283+ ] ,
284+ { backend : 'android' } ,
285+ ) ;
286+
287+ const overlayRefs = buildScreenshotOverlayRefs ( snapshot , 1344 , 2992 ) ;
288+
289+ assert . deepEqual ( overlayRefs , [
290+ {
291+ ref : 'e2' ,
292+ label : 'Storage' ,
293+ rect : { x : 0 , y : 2697 , width : 1344 , height : 223 } ,
294+ overlayRect : { x : 0 , y : 2697 , width : 1344 , height : 223 } ,
295+ center : { x : 672 , y : 2809 } ,
296+ } ,
297+ ] ) ;
298+ } ) ;
299+
300+ test ( 'buildScreenshotOverlayRefs includes unlabeled Android bottom tab controls' , ( ) => {
301+ const snapshot = makeSnapshotState (
302+ [
303+ {
304+ index : 0 ,
305+ type : 'android.widget.FrameLayout' ,
306+ rect : { x : 0 , y : 0 , width : 1344 , height : 2992 } ,
307+ } ,
308+ {
309+ index : 1 ,
310+ parentIndex : 0 ,
311+ type : 'android.widget.ScrollView' ,
312+ hittable : true ,
313+ rect : { x : 0 , y : 159 , width : 1344 , height : 2593 } ,
314+ } ,
315+ {
316+ index : 2 ,
317+ parentIndex : 0 ,
318+ type : 'android.widget.TextView' ,
319+ label : 'Agent Device Tester' ,
320+ rect : { x : 54 , y : 181 , width : 770 , height : 86 } ,
321+ } ,
322+ {
323+ index : 3 ,
324+ parentIndex : 0 ,
325+ type : 'android.view.ViewGroup' ,
326+ hittable : true ,
327+ rect : { x : 72 , y : 2724 , width : 192 , height : 132 } ,
328+ } ,
329+ {
330+ index : 4 ,
331+ parentIndex : 0 ,
332+ type : 'android.view.ViewGroup' ,
333+ hittable : true ,
334+ rect : { x : 436 , y : 2724 , width : 192 , height : 132 } ,
335+ } ,
336+ {
337+ index : 5 ,
338+ parentIndex : 0 ,
339+ type : 'android.view.ViewGroup' ,
340+ hittable : true ,
341+ rect : { x : 800 , y : 2724 , width : 192 , height : 132 } ,
342+ } ,
343+ {
344+ index : 6 ,
345+ parentIndex : 0 ,
346+ type : 'android.view.ViewGroup' ,
347+ hittable : true ,
348+ rect : { x : 1164 , y : 2724 , width : 132 , height : 132 } ,
349+ } ,
350+ ] ,
351+ { backend : 'android' } ,
352+ ) ;
353+
354+ const overlayRefs = buildScreenshotOverlayRefs ( snapshot , 1344 , 2992 ) ;
355+
356+ assert . deepEqual (
357+ overlayRefs . map ( ( overlayRef ) => overlayRef . ref ) ,
358+ [ 'e4' , 'e5' , 'e6' , 'e7' ] ,
359+ ) ;
360+ assert . ok (
361+ overlayRefs . every ( ( overlayRef ) => ! overlayRef . label ) ,
362+ 'unlabeled Android tab controls should still get visual refs' ,
363+ ) ;
364+ } ) ;
365+
366+ test ( 'buildScreenshotOverlayRefs trims Android row spacing from unlabeled action containers' , ( ) => {
367+ const snapshot = makeSnapshotState (
368+ [
369+ {
370+ index : 0 ,
371+ type : 'android.widget.ScrollView' ,
372+ rect : { x : 0 , y : 0 , width : 1344 , height : 2920 } ,
373+ } ,
374+ {
375+ index : 1 ,
376+ parentIndex : 0 ,
377+ type : 'android.widget.LinearLayout' ,
378+ hittable : true ,
379+ rect : { x : 0 , y : 447 , width : 1344 , height : 282 } ,
380+ } ,
381+ {
382+ index : 2 ,
383+ parentIndex : 1 ,
384+ type : 'android.widget.TextView' ,
385+ label : 'Google' ,
386+ rect : { x : 240 , y : 495 , width : 190 , height : 81 } ,
387+ } ,
388+ {
389+ index : 3 ,
390+ parentIndex : 1 ,
391+ type : 'android.widget.TextView' ,
392+ label : 'Services & preferences' ,
393+ rect : { x : 240 , y : 576 , width : 425 , height : 57 } ,
394+ } ,
395+ ] ,
396+ { backend : 'android' } ,
397+ ) ;
398+
399+ const overlayRefs = buildScreenshotOverlayRefs ( snapshot , 1344 , 2992 ) ;
400+
401+ assert . deepEqual ( overlayRefs , [
402+ {
403+ ref : 'e2' ,
404+ label : 'Google' ,
405+ rect : { x : 0 , y : 447 , width : 1344 , height : 234 } ,
406+ overlayRect : { x : 0 , y : 447 , width : 1344 , height : 234 } ,
407+ center : { x : 672 , y : 564 } ,
408+ } ,
409+ ] ) ;
410+ } ) ;
411+
261412test ( 'annotateScreenshotWithRefs draws the overlay onto the saved PNG' , async ( ) => {
262413 const root = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'agent-device-screenshot-overlay-' ) ) ;
263414 const screenshotPath = path . join ( root , 'screen.png' ) ;
0 commit comments