@@ -283,6 +283,13 @@ extension RunnerTests {
283283
284284 func readTextAt( app: XCUIApplication , x: Double , y: Double ) -> String ? {
285285 let point = CGPoint ( x: x, y: y)
286+ let textInputCandidates = textInputCandidatesAt ( app: app, point: point)
287+ for element in textInputCandidates where prefersExpandedTextRead ( element) {
288+ if let text = readableText ( for: element) {
289+ return text
290+ }
291+ }
292+
286293 let candidates = app. descendants ( matching: . any) . allElementsBoundByIndex
287294 . filter { element in
288295 element. exists && !element. frame. isEmpty && element. frame. contains ( point)
@@ -337,15 +344,18 @@ extension RunnerTests {
337344 }
338345
339346 func textInputAt( app: XCUIApplication , x: Double , y: Double ) -> XCUIElement ? {
340- let point = CGPoint ( x: x, y: y)
341- var matched : XCUIElement ?
347+ return textInputCandidatesAt ( app: app, point: CGPoint ( x: x, y: y) ) . first
348+ }
349+
350+ private func textInputCandidatesAt( app: XCUIApplication , point: CGPoint ) -> [ XCUIElement ] {
351+ var candidates : [ XCUIElement ] = [ ]
342352 let exceptionMessage = RunnerObjCExceptionCatcher . catchException ( {
343353 // Query the text-input element types directly instead of enumerating the entire tree
344354 // (app.descendants(.any).allElementsBoundByIndex snapshots every element and is ~10x
345355 // slower — it dominated fill latency because resolveTextEntryElement re-runs this on
346356 // each verify/repair poll once the focused field reference goes stale).
347357 // Prefer the smallest matching field so nested editable controls win over large containers.
348- let candidates = [
358+ candidates = [
349359 app. textFields,
350360 app. secureTextFields,
351361 app. searchFields,
@@ -371,16 +381,15 @@ extension RunnerTests {
371381 }
372382 return left. elementType. rawValue < right. elementType. rawValue
373383 }
374- matched = candidates. first
375384 } )
376385 if let exceptionMessage {
377386 NSLog (
378387 " AGENT_DEVICE_RUNNER_TEXT_INPUT_AT_POINT_IGNORED_EXCEPTION=%@ " ,
379388 exceptionMessage
380389 )
381- return nil
390+ return [ ]
382391 }
383- return matched
392+ return candidates
384393 }
385394
386395 private func frameContainsPoint( _ frame: CGRect , _ point: CGPoint , tolerance: CGFloat ) -> Bool {
@@ -1019,6 +1028,14 @@ extension RunnerTests {
10191028 return ( wasVisible: true , dismissed: !visible, visible: visible)
10201029 }
10211030
1031+ if tapKeyboardReturnControl ( app: app, allowCoordinateFallback: true ) {
1032+ sleepFor ( 0.2 )
1033+ let visible = isKeyboardVisible ( app: app)
1034+ if !visible {
1035+ return ( wasVisible: true , dismissed: true , visible: false )
1036+ }
1037+ }
1038+
10221039 return ( wasVisible: true , dismissed: false , visible: isKeyboardVisible ( app: app) )
10231040#endif
10241041 }
@@ -1139,7 +1156,10 @@ extension RunnerTests {
11391156#endif
11401157 }
11411158
1142- private func tapKeyboardReturnControl( app: XCUIApplication ) -> Bool {
1159+ private func tapKeyboardReturnControl(
1160+ app: XCUIApplication ,
1161+ allowCoordinateFallback: Bool = false
1162+ ) -> Bool {
11431163#if os(iOS)
11441164 for label in [ " return " , " Return " , " Enter " , " Go " , " Search " , " Next " , " Done " , " Send " , " Join " ] {
11451165 let candidates = [
@@ -1150,6 +1170,21 @@ extension RunnerTests {
11501170 hittable. tap ( )
11511171 return true
11521172 }
1173+ if allowCoordinateFallback,
1174+ let keyboardFrame = visibleKeyboardFrame ( app: app) ,
1175+ let framed = candidates. first ( where: {
1176+ guard $0. exists else { return false }
1177+ let frame = $0. frame
1178+ return !frame. isEmpty && keyboardFrame. contains ( CGPoint ( x: frame. midX, y: frame. midY) )
1179+ } ) {
1180+ let frame = framed. frame
1181+ switch tapAt ( app: app, x: frame. midX, y: frame. midY) {
1182+ case . performed:
1183+ return true
1184+ case . unsupported:
1185+ return false
1186+ }
1187+ }
11531188 }
11541189#endif
11551190 return false
0 commit comments