@@ -81,9 +81,7 @@ final class RunnerTests: XCTestCase {
8181 return
8282 }
8383 NSLog ( " AGENT_DEVICE_RUNNER_WAITING " )
84- let timeout = resolveRunnerTimeout ( )
85- let effectiveTimeout = timeout > 0 ? timeout : 24 * 60 * 60
86- let result = XCTWaiter . wait ( for: [ expectation] , timeout: effectiveTimeout)
84+ let result = XCTWaiter . wait ( for: [ expectation] , timeout: 24 * 60 * 60 )
8785 NSLog ( " AGENT_DEVICE_RUNNER_WAIT_RESULT=%@ " , String ( describing: result) )
8886 if result != . completed {
8987 XCTFail ( " runner wait ended with \( result) " )
@@ -238,7 +236,19 @@ final class RunnerTests: XCTestCase {
238236 guard let text = command. text else {
239237 return Response ( ok: false , error: ErrorPayload ( message: " type requires text " ) )
240238 }
241- activeApp. typeText ( text)
239+ if command. clearFirst == true {
240+ guard let focused = focusedTextInput ( app: activeApp) else {
241+ return Response ( ok: false , error: ErrorPayload ( message: " no focused text input to clear " ) )
242+ }
243+ clearTextInput ( focused)
244+ focused. typeText ( text)
245+ return Response ( ok: true , data: DataPayload ( message: " typed " ) )
246+ }
247+ if let focused = focusedTextInput ( app: activeApp) {
248+ focused. typeText ( text)
249+ } else {
250+ activeApp. typeText ( text)
251+ }
242252 return Response ( ok: true , data: DataPayload ( message: " typed " ) )
243253 case . swipe:
244254 guard let direction = command. direction else {
@@ -343,6 +353,48 @@ final class RunnerTests: XCTestCase {
343353 return element. exists ? element : nil
344354 }
345355
356+ private func clearTextInput( _ element: XCUIElement ) {
357+ moveCaretToEnd ( element: element)
358+ let count = estimatedDeleteCount ( for: element)
359+ let deletes = String ( repeating: XCUIKeyboardKey . delete. rawValue, count: count)
360+ element. typeText ( deletes)
361+ }
362+
363+ private func focusedTextInput( app: XCUIApplication ) -> XCUIElement ? {
364+ let focused = app
365+ . descendants ( matching: . any)
366+ . matching ( NSPredicate ( format: " hasKeyboardFocus == 1 " ) )
367+ . firstMatch
368+ guard focused. exists else { return nil }
369+
370+ switch focused. elementType {
371+ case . textField, . secureTextField, . searchField, . textView:
372+ return focused
373+ default :
374+ return nil
375+ }
376+ }
377+
378+ private func moveCaretToEnd( element: XCUIElement ) {
379+ let frame = element. frame
380+ guard !frame. isEmpty else {
381+ element. tap ( )
382+ return
383+ }
384+ let origin = element. coordinate ( withNormalizedOffset: CGVector ( dx: 0 , dy: 0 ) )
385+ let target = origin. withOffset (
386+ CGVector ( dx: max ( 2 , frame. width - 4 ) , dy: max ( 2 , frame. height / 2 ) )
387+ )
388+ target. tap ( )
389+ }
390+
391+ private func estimatedDeleteCount( for element: XCUIElement ) -> Int {
392+ let valueText = String ( describing: element. value ?? " " )
393+ . trimmingCharacters ( in: . whitespacesAndNewlines)
394+ let base = valueText. isEmpty ? 24 : ( valueText. count + 8 )
395+ return max ( 24 , min ( 120 , base) )
396+ }
397+
346398 private func findScopeElement( app: XCUIApplication , scope: String ) -> XCUIElement ? {
347399 let predicate = NSPredicate (
348400 format: " label CONTAINS[c] %@ OR identifier CONTAINS[c] %@ " ,
@@ -738,14 +790,6 @@ private func resolveRunnerPort() -> UInt16 {
738790 return 0
739791}
740792
741- private func resolveRunnerTimeout( ) -> TimeInterval {
742- if let env = ProcessInfo . processInfo. environment [ " AGENT_DEVICE_RUNNER_TIMEOUT " ] ,
743- let parsed = Double ( env) {
744- return parsed
745- }
746- return 0
747- }
748-
749793enum CommandType : String , Codable {
750794 case tap
751795 case type
@@ -772,6 +816,7 @@ struct Command: Codable {
772816 let command : CommandType
773817 let appBundleId : String ?
774818 let text : String ?
819+ let clearFirst : Bool ?
775820 let action : String ?
776821 let x : Double ?
777822 let y : Double ?
0 commit comments