@@ -16,6 +16,7 @@ extension RunnerTests {
1616 . scrollView,
1717 . table
1818 ]
19+ private static let flatInteractiveFallbackBudget : TimeInterval = 1.0
1920
2021 private struct SnapshotTraversalContext {
2122 let queryRoot : XCUIElement
@@ -85,6 +86,9 @@ extension RunnerTests {
8586 }
8687
8788 func snapshotFast( app: XCUIApplication , options: SnapshotOptions ) throws -> DataPayload {
89+ if options. interactiveOnly && options. compact {
90+ return snapshotFlatInteractive ( app: app, options: options)
91+ }
8892 if let blocking = blockingSystemAlertSnapshot ( ) {
8993 return blocking
9094 }
@@ -257,58 +261,50 @@ extension RunnerTests {
257261 }
258262
259263 private func snapshotFlatInteractive( app: XCUIApplication , options: SnapshotOptions ) -> DataPayload {
260- let viewport = safeSnapshotViewport ( app: app)
261264 var nodes : [ SnapshotNode ] = [
262- SnapshotNode (
263- index: 0 ,
264- type: " Application " ,
265- label: nonEmptyElementText { app. label } ,
266- identifier: nonEmptyElementText { app. identifier } ,
267- value: nil ,
268- rect: snapshotRect ( from: safely ( " SNAPSHOT_FLAT_APP_FRAME " , CGRect . zero) { app. frame } ) ,
269- enabled: true ,
270- focused: nil ,
271- selected: nil ,
272- hittable: false ,
273- depth: 0 ,
274- parentIndex: nil ,
275- hiddenContentAbove: nil ,
276- hiddenContentBelow: nil
277- )
265+ compactInteractiveRootNode ( rect: . zero)
278266 ]
279267 if options. depth == 0 {
280268 return DataPayload ( nodes: nodes, truncated: false )
281269 }
282270
271+ let deadline = options. interactiveOnly
272+ ? Date ( ) . addingTimeInterval ( Self . flatInteractiveFallbackBudget)
273+ : Date . distantFuture
283274 var seen = Set < String > ( )
284- let candidates = flatFallbackElements ( app: app)
285- . compactMap { element in
286- flatSnapshotNode (
287- element: element,
288- index: 0 ,
289- parentIndex: 0 ,
290- viewport: viewport,
291- options: options
292- )
275+ var candidates : [ SnapshotNode ] = [ ]
276+ for element in flatFallbackElements ( app: app, options: options) {
277+ if Date ( ) >= deadline {
278+ NSLog ( " AGENT_DEVICE_RUNNER_SNAPSHOT_FLAT_FALLBACK_DEADLINE " )
279+ break
293280 }
294- . filter { node in
295- let key = " \( node. type) - \( node. label ?? " " ) - \( node. identifier ?? " " ) - \( node. value ?? " " ) - \( node. rect. x) - \( node. rect. y) - \( node. rect. width) - \( node. rect. height) "
296- if seen. contains ( key) { return false }
297- seen. insert ( key)
298- return true
281+ guard let node = flatSnapshotNode (
282+ element: element,
283+ index: 0 ,
284+ parentIndex: 0 ,
285+ viewport: . infinite,
286+ options: options
287+ ) else {
288+ continue
299289 }
300- . sorted { left, right in
301- if left. rect. y != right. rect. y {
302- return left. rect. y < right. rect. y
303- }
304- if left. rect. x != right. rect. x {
305- return left. rect. x < right. rect. x
306- }
307- return left. type < right. type
290+ let key = " \( node. type) - \( node. label ?? " " ) - \( node. identifier ?? " " ) - \( node. value ?? " " ) - \( node. rect. x) - \( node. rect. y) - \( node. rect. width) - \( node. rect. height) "
291+ if seen. contains ( key) { continue }
292+ seen. insert ( key)
293+ candidates. append ( node)
294+ }
295+ candidates. sort { left, right in
296+ if left. rect. y != right. rect. y {
297+ return left. rect. y < right. rect. y
298+ }
299+ if left. rect. x != right. rect. x {
300+ return left. rect. x < right. rect. x
308301 }
302+ return left. type < right. type
303+ }
309304
310305 let remaining = max ( 0 , fastSnapshotLimit - nodes. count)
311306 let truncated = candidates. count > remaining
307+ nodes [ 0 ] = compactInteractiveRootNode ( rect: compactInteractiveRootFrame ( for: candidates) )
312308 for candidate in candidates. prefix ( remaining) {
313309 nodes. append (
314310 SnapshotNode (
@@ -332,6 +328,34 @@ extension RunnerTests {
332328 return DataPayload ( nodes: nodes, truncated: truncated)
333329 }
334330
331+ private func compactInteractiveRootNode( rect: CGRect ) -> SnapshotNode {
332+ SnapshotNode (
333+ index: 0 ,
334+ type: " Application " ,
335+ label: nil ,
336+ identifier: nil ,
337+ value: nil ,
338+ rect: snapshotRect ( from: rect) ,
339+ enabled: true ,
340+ focused: nil ,
341+ selected: nil ,
342+ hittable: false ,
343+ depth: 0 ,
344+ parentIndex: nil ,
345+ hiddenContentAbove: nil ,
346+ hiddenContentBelow: nil
347+ )
348+ }
349+
350+ private func compactInteractiveRootFrame( for candidates: [ SnapshotNode ] ) -> CGRect {
351+ guard !candidates. isEmpty else {
352+ return . zero
353+ }
354+ let maxX = candidates. map { CGFloat ( $0. rect. x + $0. rect. width) } . max ( ) ?? 0
355+ let maxY = candidates. map { CGFloat ( $0. rect. y + $0. rect. height) } . max ( ) ?? 0
356+ return CGRect ( x: 0 , y: 0 , width: max ( 1 , maxX) , height: max ( 1 , maxY) )
357+ }
358+
335359 func snapshotRect( from frame: CGRect ) -> SnapshotRect {
336360 return SnapshotRect (
337361 x: Double ( frame. origin. x) ,
@@ -798,8 +822,26 @@ extension RunnerTests {
798822 safely ( " SNAPSHOT_QUERY " , [ ] , fetch)
799823 }
800824
801- private func flatFallbackElements( app: XCUIApplication ) -> [ XCUIElement ] {
802- let queries : [ XCUIElementQuery ] = [
825+ private func flatFallbackElements( app: XCUIApplication , options: SnapshotOptions ) -> [ XCUIElement ] {
826+ let queries : [ XCUIElementQuery ] = options. interactiveOnly ? [
827+ app. buttons,
828+ app. links,
829+ app. textFields,
830+ app. secureTextFields,
831+ app. searchFields,
832+ app. textViews,
833+ app. switches,
834+ app. sliders,
835+ app. segmentedControls,
836+ app. cells,
837+ app. collectionViews,
838+ app. tables,
839+ app. scrollViews,
840+ app. pickers,
841+ app. steppers,
842+ app. tabBars,
843+ app. menuItems
844+ ] : [
803845 app. buttons,
804846 app. cells,
805847 app. collectionViews,
@@ -816,11 +858,26 @@ extension RunnerTests {
816858 app. textFields,
817859 app. textViews
818860 ]
819- return queries. flatMap { query in
820- safeSnapshotElementsQuery {
821- query. allElementsBoundByIndex
861+ guard options. interactiveOnly else {
862+ return queries. flatMap { query in
863+ safeSnapshotElementsQuery {
864+ query. allElementsBoundByIndex
865+ }
866+ }
867+ }
868+
869+ let deadline = Date ( ) . addingTimeInterval ( Self . flatInteractiveFallbackBudget)
870+ var elements : [ XCUIElement ] = [ ]
871+ for query in queries {
872+ if Date ( ) >= deadline {
873+ NSLog ( " AGENT_DEVICE_RUNNER_SNAPSHOT_FLAT_FALLBACK_DEADLINE " )
874+ break
822875 }
876+ elements. append ( contentsOf: safeSnapshotElementsQuery {
877+ query. allElementsBoundByIndex
878+ } )
823879 }
880+ return elements
824881 }
825882
826883 private func flatSnapshotNode(
0 commit comments