@@ -6,28 +6,37 @@ export type AndroidSnapshotAnalysis = {
66 maxDepth : number ;
77} ;
88
9- export function findBounds ( xml : string , query : string ) : { x : number ; y : number } | null {
10- const q = query . toLowerCase ( ) ;
11- const nodeRegex = / < n o d e [ ^ > ] + > / g;
9+ export type AndroidUiNodeMetadata = {
10+ text : string | null ;
11+ desc : string | null ;
12+ resourceId : string | null ;
13+ packageName : string | null ;
14+ className : string | null ;
15+ bounds : string | null ;
16+ rect ?: Rect ;
17+ clickable ?: boolean ;
18+ enabled ?: boolean ;
19+ focusable ?: boolean ;
20+ focused ?: boolean ;
21+ password ?: boolean ;
22+ } ;
23+
24+ export function * androidUiNodes ( xml : string ) : IterableIterator < AndroidUiNodeMetadata > {
25+ const nodeRegex = / < n o d e \b [ ^ > ] * > / g;
1226 let match = nodeRegex . exec ( xml ) ;
1327 while ( match ) {
14- const node = match [ 0 ] ;
15- const attrs = parseXmlNodeAttributes ( node ) ;
16- const textVal = ( readXmlAttr ( attrs , 'text' ) ?? '' ) . toLowerCase ( ) ;
17- const descVal = ( readXmlAttr ( attrs , 'content-desc' ) ?? '' ) . toLowerCase ( ) ;
18- if ( textVal . includes ( q ) || descVal . includes ( q ) ) {
19- const rect = parseBounds ( readXmlAttr ( attrs , 'bounds' ) ) ;
20- if ( rect ) {
21- return {
22- x : Math . floor ( rect . x + rect . width / 2 ) ,
23- y : Math . floor ( rect . y + rect . height / 2 ) ,
24- } ;
25- }
26- return { x : 0 , y : 0 } ;
27- }
28+ yield readAndroidUiNodeMetadata ( match [ 0 ] ) ;
2829 match = nodeRegex . exec ( xml ) ;
2930 }
30- return null ;
31+ }
32+
33+ function readAndroidUiNodeMetadata ( node : string ) : AndroidUiNodeMetadata {
34+ const attrs = readNodeAttributes ( node ) ;
35+ const rect = parseBounds ( attrs . bounds ) ;
36+ return {
37+ ...attrs ,
38+ ...( rect ? { rect } : { } ) ,
39+ } ;
3140}
3241
3342export function parseUiHierarchy (
@@ -172,19 +181,7 @@ function hasInteractiveDescendant(state: AndroidSnapshotBuildState, node: Androi
172181 return false ;
173182}
174183
175- export function readNodeAttributes ( node : string ) : {
176- text : string | null ;
177- desc : string | null ;
178- resourceId : string | null ;
179- packageName : string | null ;
180- className : string | null ;
181- bounds : string | null ;
182- clickable ?: boolean ;
183- enabled ?: boolean ;
184- focusable ?: boolean ;
185- focused ?: boolean ;
186- password ?: boolean ;
187- } {
184+ function readNodeAttributes ( node : string ) : Omit < AndroidUiNodeMetadata , 'rect' > {
188185 const attrs = parseXmlNodeAttributes ( node ) ;
189186 const getAttr = ( name : string ) : string | null => readXmlAttr ( attrs , name ) ;
190187 const boolAttr = ( name : string ) : boolean | undefined => {
@@ -332,7 +329,7 @@ function readXmlAttr(attrs: Map<string, string>, name: string): string | null {
332329 return attrs . get ( name ) ?? null ;
333330}
334331
335- export function parseBounds ( bounds : string | null ) : Rect | undefined {
332+ function parseBounds ( bounds : string | null ) : Rect | undefined {
336333 if ( ! bounds ) return undefined ;
337334 const match = / \[ ( \d + ) , ( \d + ) \] \[ ( \d + ) , ( \d + ) \] / . exec ( bounds ) ;
338335 if ( ! match ) return undefined ;
@@ -387,15 +384,14 @@ export function parseUiHierarchyTree(xml: string): AndroidUiHierarchy {
387384 match = tokenRegex . exec ( xml ) ;
388385 continue ;
389386 }
390- const attrs = readNodeAttributes ( token ) ;
391- const rect = parseBounds ( attrs . bounds ) ;
387+ const attrs = readAndroidUiNodeMetadata ( token ) ;
392388 const parent = stack [ stack . length - 1 ] ;
393389 const node : AndroidUiHierarchy = {
394390 type : attrs . className ,
395391 label : attrs . text || attrs . desc ,
396392 value : attrs . text ,
397393 identifier : attrs . resourceId ,
398- rect,
394+ rect : attrs . rect ,
399395 enabled : attrs . enabled ,
400396 hittable : attrs . clickable ?? attrs . focusable ,
401397 depth : parent . depth + 1 ,
0 commit comments