@@ -353,16 +353,21 @@ extension XcodeAppInstanceInspector {
353353
354354 for window in windows {
355355 let workspaceIdentifier = workspaceIdentifier ( window)
356+ var traverseCount = 0
356357
357358 let tabs = {
358359 guard let editArea = window. firstChild ( where: { $0. description == " editor area " } )
359360 else { return Set < String > ( ) }
360361 var allTabs = Set < String > ( )
361- let tabBars = editArea. children { $0 . description == " tab bar " }
362+ let tabBars = editArea. tabBars
362363 for tabBar in tabBars {
363- let tabs = tabBar. children { $0. roleDescription == " tab " }
364- for tab in tabs {
365- allTabs. insert ( tab. title)
364+ tabBar. traverse { element, _ in
365+ traverseCount += 1
366+ if element. roleDescription == " tab " {
367+ allTabs. insert ( element. title)
368+ return . skipDescendants
369+ }
370+ return . continueSearching
366371 }
367372 }
368373 return allTabs
@@ -416,3 +421,54 @@ private func isCompletionPanel(_ element: AXUIElement) -> Bool {
416421 return matchXcode16CompletionPanel
417422}
418423
424+ public extension AXUIElement {
425+ var tabBars : [ AXUIElement ] {
426+ // Searching by traversing with AXUIElement is (Xcode) resource consuming, we should skip
427+ // as much as possible!
428+
429+ guard let editArea: AXUIElement = {
430+ if description == " editor area " { return self }
431+ return firstChild ( where: { $0. description == " editor area " } )
432+ } ( ) else { return [ ] }
433+
434+ var tabBars = [ AXUIElement] ( )
435+ editArea. traverse { element, _ in
436+ let description = element. description
437+ if description == " Tab Bar " {
438+ element. traverse { element, _ in
439+ if element. description == " tab bar " {
440+ tabBars. append ( element)
441+ return . stopSearching
442+ }
443+ return . continueSearching
444+ }
445+
446+ return . skipDescendantsAndSiblings
447+ }
448+
449+ if element. identifier == " editor context " {
450+ return . skipDescendantsAndSiblings
451+ }
452+
453+ if element. isSourceEditor {
454+ return . skipDescendantsAndSiblings
455+ }
456+
457+ if description == " Code Coverage Ribbon " {
458+ return . skipDescendants
459+ }
460+
461+ if description == " Debug Area " {
462+ return . skipDescendants
463+ }
464+
465+ if description == " debug bar " {
466+ return . skipDescendants
467+ }
468+
469+ return . continueSearching
470+ }
471+
472+ return tabBars
473+ }
474+ }
0 commit comments