@@ -34,19 +34,24 @@ test('android settings commands', () => {
3434 detail : 'expected snapshot to include a nodes array' ,
3535 } ) ;
3636
37- const nodes = Array . isArray ( snapshot . json ?. data ?. nodes ) ? ( snapshot . json . data . nodes as SnapshotNode [ ] ) : [ ] ;
38- const clickTarget = selectSettingsClickSelector ( nodes ) ;
37+ const settingsSectionSelector = [
38+ 'label=Apps' ,
39+ 'label="Apps & notifications"' ,
40+ 'label="Network & internet"' ,
41+ 'label="Connected devices"' ,
42+ 'label=Display' ,
43+ 'label=Battery' ,
44+ 'label=Notifications' ,
45+ ] . join ( ' || ' ) ;
46+ const clickArgs = [ 'click' , settingsSectionSelector , '--json' , ...session ] ;
47+ const openSection = integration . runStep ( 'open settings section' , clickArgs ) ;
3948 integration . assertResult (
40- Boolean ( clickTarget ) ,
41- 'select click target ' ,
42- snapshotArgs ,
43- snapshot ,
44- { detail : 'expected at least one bounded, labeled node in snapshot output ' } ,
49+ openSection . json ?. success ,
50+ 'open settings section success ' ,
51+ clickArgs ,
52+ openSection ,
53+ { detail : 'expected selector-based click to return success=true ' } ,
4554 ) ;
46- if ( ! clickTarget ) return ;
47-
48- const clickArgs = [ 'click' , clickTarget . selector , '--json' , ...session ] ;
49- integration . runStep ( `click ${ clickTarget . label } ` , clickArgs ) ;
5055
5156 const snapshotAppsArgs = [ 'snapshot' , '-i' , '--json' , ...session ] ;
5257 const snapshotApps = integration . runStep ( 'snapshot apps' , snapshotAppsArgs ) ;
@@ -75,109 +80,3 @@ test('android settings commands', () => {
7580 const backArgs = [ 'back' , '--json' , ...session ] ;
7681 integration . runStep ( 'back' , backArgs ) ;
7782} ) ;
78-
79- type SnapshotNode = {
80- type ?: string ;
81- label ?: string ;
82- rect ?: { width ?: number ; height ?: number } ;
83- } ;
84-
85- type SelectorTarget = {
86- label : string ;
87- selector : string ;
88- } ;
89-
90- function selectSettingsClickSelector ( nodes : SnapshotNode [ ] ) : SelectorTarget | null {
91- const clickableNodes = nodes . filter ( ( node ) => {
92- const label = typeof node . label === 'string' ? node . label . trim ( ) : '' ;
93- const width = node . rect ?. width ;
94- const height = node . rect ?. height ;
95- return (
96- label . length > 0 &&
97- label !== '(no-label)' &&
98- typeof width === 'number' &&
99- width > 0 &&
100- typeof height === 'number' &&
101- height > 0
102- ) ;
103- } ) ;
104- if ( clickableNodes . length === 0 ) return null ;
105-
106- const labelCounts = new Map < string , number > ( ) ;
107- const roleLabelCounts = new Map < string , number > ( ) ;
108- for ( const node of clickableNodes ) {
109- const labelKey = normalizeSelectorText ( node . label ) ;
110- if ( ! labelKey ) continue ;
111- labelCounts . set ( labelKey , ( labelCounts . get ( labelKey ) ?? 0 ) + 1 ) ;
112- const roleKey = normalizeSelectorText ( node . type ) ?? '' ;
113- const roleLabelKey = `${ roleKey } ::${ labelKey } ` ;
114- roleLabelCounts . set ( roleLabelKey , ( roleLabelCounts . get ( roleLabelKey ) ?? 0 ) + 1 ) ;
115- }
116-
117- const preferredLabels = [
118- 'Apps' ,
119- 'Apps & notifications' ,
120- 'Network & internet' ,
121- 'Connected devices' ,
122- 'Display' ,
123- 'Battery' ,
124- 'Notifications' ,
125- ] ;
126- const preferredTargets = clickableNodes . filter ( ( node ) => {
127- const label = normalizeSelectorText ( node . label ) ;
128- if ( ! label ) return false ;
129- return preferredLabels . some ( ( candidate ) => label . includes ( candidate . toLowerCase ( ) ) ) ;
130- } ) ;
131- const candidates = preferredTargets . length > 0 ? preferredTargets : clickableNodes ;
132- const selected = candidates . find ( ( node ) => hasUniqueRoleAndLabel ( node , labelCounts , roleLabelCounts ) )
133- ?? candidates . find ( ( node ) => hasUniqueLabel ( node , labelCounts ) )
134- ?? candidates [ 0 ] ;
135- if ( ! selected ) return null ;
136-
137- const selector = toSelectorExpression ( selected , labelCounts , roleLabelCounts ) ;
138- if ( ! selector ) return null ;
139- const label = String ( selected . label ?? '' ) . trim ( ) ;
140- return { label : label . length > 0 ? label : selector , selector } ;
141- }
142-
143- function hasUniqueLabel ( node : SnapshotNode , labelCounts : Map < string , number > ) : boolean {
144- const labelKey = normalizeSelectorText ( node . label ) ;
145- if ( ! labelKey ) return false ;
146- return ( labelCounts . get ( labelKey ) ?? 0 ) === 1 ;
147- }
148-
149- function hasUniqueRoleAndLabel (
150- node : SnapshotNode ,
151- labelCounts : Map < string , number > ,
152- roleLabelCounts : Map < string , number > ,
153- ) : boolean {
154- const labelKey = normalizeSelectorText ( node . label ) ;
155- if ( ! labelKey ) return false ;
156- const roleKey = normalizeSelectorText ( node . type ) ?? '' ;
157- const roleLabelKey = `${ roleKey } ::${ labelKey } ` ;
158- if ( roleKey . length === 0 ) return hasUniqueLabel ( node , labelCounts ) ;
159- return ( roleLabelCounts . get ( roleLabelKey ) ?? 0 ) === 1 ;
160- }
161-
162- function toSelectorExpression (
163- node : SnapshotNode ,
164- labelCounts : Map < string , number > ,
165- roleLabelCounts : Map < string , number > ,
166- ) : string | null {
167- const label = normalizeSelectorText ( node . label ) ;
168- if ( ! label ) return null ;
169- const role = normalizeSelectorText ( node . type ) ;
170- if ( role ) {
171- const roleLabelKey = `${ role } ::${ label } ` ;
172- if ( ( roleLabelCounts . get ( roleLabelKey ) ?? 0 ) === 1 || ( labelCounts . get ( label ) ?? 0 ) > 1 ) {
173- return `role=${ JSON . stringify ( role ) } label=${ JSON . stringify ( label ) } ` ;
174- }
175- }
176- return `label=${ JSON . stringify ( label ) } ` ;
177- }
178-
179- function normalizeSelectorText ( value : string | undefined ) : string | null {
180- if ( ! value ) return null ;
181- const normalized = value . trim ( ) . toLowerCase ( ) . replace ( / \s + / g, ' ' ) ;
182- return normalized . length > 0 ? normalized : null ;
183- }
0 commit comments