@@ -744,3 +744,93 @@ func TestUiNodeDecodesEditableFromAgentJson(t *testing.T) {
744744 t .Errorf ("expected editable to be decoded" )
745745 }
746746}
747+
748+ // A Compose Button is a clickable android.view.View whose text lives on a child,
749+ // so clickable is the only hint that it is a button; it must surface Clickable=true.
750+ func TestCollectElementsMarksClickableNode (t * testing.T ) {
751+ d := & AndroidDevice {}
752+ tree := uiAutomatorXmlNode {
753+ Class : "android.view.View" ,
754+ Clickable : "true" ,
755+ Bounds : "[0,0][140,140]" ,
756+ }
757+
758+ output := d .collectElements (tree )
759+
760+ if len (output ) != 1 {
761+ t .Fatalf ("expected 1 element, got %d: %+v" , len (output ), output )
762+ }
763+ if output [0 ].Clickable == nil || * output [0 ].Clickable != true {
764+ t .Errorf ("expected Clickable to be true, got %+v" , output [0 ].Clickable )
765+ }
766+ }
767+
768+ // A node that isn't clickable must leave Clickable unset (nil), matching omitempty.
769+ func TestCollectElementsLeavesNonClickableNodeUnset (t * testing.T ) {
770+ d := & AndroidDevice {}
771+ tree := uiAutomatorXmlNode {
772+ Class : "android.widget.TextView" ,
773+ Text : "New Order" ,
774+ Bounds : "[0,0][140,140]" ,
775+ }
776+
777+ output := d .collectElements (tree )
778+
779+ if len (output ) != 1 {
780+ t .Fatalf ("expected 1 element, got %d: %+v" , len (output ), output )
781+ }
782+ if output [0 ].Clickable != nil {
783+ t .Errorf ("expected Clickable to be nil for a non-clickable node, got %+v" , * output [0 ].Clickable )
784+ }
785+ }
786+
787+ // A Material3 AlertDialog puts its pane title on a plain View that has no text of its own.
788+ // That node must be kept, with its pane title, so the dialog's content stays under it.
789+ func TestCollectUiNodeElementsKeepsPaneTitleNode (t * testing.T ) {
790+ nodes := []uiNode {
791+ {
792+ Class : "android.view.View" ,
793+ PaneTitle : "Dialog" ,
794+ Rect : uiRect {X : 60 , Y : 900 , Width : 960 , Height : 500 },
795+ Children : []uiNode {
796+ {
797+ Class : "android.widget.TextView" ,
798+ Text : "You must be 18 or older to order a Dark Matter Martini" ,
799+ Rect : uiRect {X : 120 , Y : 960 , Width : 840 , Height : 120 },
800+ },
801+ },
802+ },
803+ }
804+
805+ output := collectUiNodeElements (nodes )
806+
807+ if len (output ) != 1 {
808+ t .Fatalf ("expected 1 element, got %d: %+v" , len (output ), output )
809+ }
810+ if output [0 ].PaneTitle == nil || * output [0 ].PaneTitle != "Dialog" {
811+ t .Errorf ("expected PaneTitle to be Dialog, got %+v" , output [0 ].PaneTitle )
812+ }
813+ if len (output [0 ].Children ) != 1 {
814+ t .Errorf ("expected the dialog text under the pane, got %+v" , output [0 ].Children )
815+ }
816+ }
817+
818+ // A node without a pane title must leave PaneTitle unset (nil), matching omitempty.
819+ func TestCollectUiNodeElementsLeavesPaneTitleUnset (t * testing.T ) {
820+ nodes := []uiNode {
821+ {
822+ Class : "android.widget.TextView" ,
823+ Text : "New Order" ,
824+ Rect : uiRect {X : 0 , Y : 0 , Width : 140 , Height : 140 },
825+ },
826+ }
827+
828+ output := collectUiNodeElements (nodes )
829+
830+ if len (output ) != 1 {
831+ t .Fatalf ("expected 1 element, got %d: %+v" , len (output ), output )
832+ }
833+ if output [0 ].PaneTitle != nil {
834+ t .Errorf ("expected PaneTitle to be nil, got %+v" , * output [0 ].PaneTitle )
835+ }
836+ }
0 commit comments