agent-device 0.21.15 → 0.21.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.15.apk → agent-device-android-ime-helper-0.21.17.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.17.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.15.manifest.json → agent-device-android-ime-helper-0.21.17.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.15.apk → agent-device-android-snapshot-helper-0.21.17.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.17.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.15.manifest.json → agent-device-android-snapshot-helper-0.21.17.manifest.json} +6 -6
  7. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +1 -4
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.h +9 -9
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedTextEntry.m +13 -22
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +0 -9
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandDispatch.swift +31 -0
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +1 -1
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -1
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +9 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +40 -6
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +202 -73
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +17 -2
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +109 -128
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +4 -0
  20. package/dist/src/adapter.js +1 -1
  21. package/dist/src/adb-executor.js +1 -1
  22. package/dist/src/adb-failure.js +1 -1
  23. package/dist/src/adb-transport.js +1 -1
  24. package/dist/src/agent-browser-process-record.js +1 -1
  25. package/dist/src/agent-browser-provider.js +1 -1
  26. package/dist/src/agent-browser-tool.js +1 -1
  27. package/dist/src/agent-device-client.js +2 -2
  28. package/dist/src/ai-sdk.js +1 -1
  29. package/dist/src/alert-runtime.js +1 -1
  30. package/dist/src/android-input-ownership.js +1 -1
  31. package/dist/src/app-catalog.js +1 -1
  32. package/dist/src/app-deployment-resolution.js +1 -1
  33. package/dist/src/app-inventory.js +1 -1
  34. package/dist/src/app-launch.js +1 -1
  35. package/dist/src/app-lifecycle.js +1 -1
  36. package/dist/src/app-lifecycle2.js +1 -1
  37. package/dist/src/app-log-files.js +1 -1
  38. package/dist/src/app-log-runtime2.js +1 -1
  39. package/dist/src/app-resolution.js +2 -2
  40. package/dist/src/apple-runner-platform.js +1 -1
  41. package/dist/src/application-lifecycle-interaction.js +1 -1
  42. package/dist/src/archive.js +1 -1
  43. package/dist/src/artifact-download.js +1 -1
  44. package/dist/src/auth-session.js +1 -1
  45. package/dist/src/auth.js +1 -1
  46. package/dist/src/back-mode.js +1 -1
  47. package/dist/src/backend-snapshot-options.js +1 -1
  48. package/dist/src/boot-diagnostics.js +1 -1
  49. package/dist/src/cache.js +1 -0
  50. package/dist/src/capture.js +1 -1
  51. package/dist/src/cli-config.js +1 -1
  52. package/dist/src/cli-help.js +9 -7
  53. package/dist/src/cli-runner.js +1 -1
  54. package/dist/src/cli.js +4 -4
  55. package/dist/src/click-button.js +1 -1
  56. package/dist/src/client-metro-companion.js +1 -1
  57. package/dist/src/client-metro.js +1 -1
  58. package/dist/src/client-types.d.ts +6 -6
  59. package/dist/src/clipboard-shell-response.js +1 -1
  60. package/dist/src/code-signature-cache.js +1 -1
  61. package/dist/src/command-schema.js +3 -3
  62. package/dist/src/command-tools.js +3 -3
  63. package/dist/src/connection-runtime.js +1 -1
  64. package/dist/src/connection.js +5 -5
  65. package/dist/src/contracts.js +1 -1
  66. package/dist/src/daemon-client-lease-beat.js +1 -0
  67. package/dist/src/daemon-client-lifecycle.js +1 -1
  68. package/dist/src/daemon-client-transport.js +1 -0
  69. package/dist/src/daemon-policy-file.js +1 -0
  70. package/dist/src/daemon-process.js +1 -1
  71. package/dist/src/daemon-registration.js +1 -1
  72. package/dist/src/daemon-shutdown-report.js +1 -1
  73. package/dist/src/daemon.js +1 -1
  74. package/dist/src/debug-symbols-facade.js +1 -1
  75. package/dist/src/device-claim-inspection.js +1 -1
  76. package/dist/src/device-claim-rule.js +1 -2
  77. package/dist/src/device-inventory-context.js +1 -1
  78. package/dist/src/{stop-sequence.js → device-readiness-runtime.js} +1 -1
  79. package/dist/src/device-rotation.js +1 -1
  80. package/dist/src/device-selection-resolver.js +1 -1
  81. package/dist/src/device-session.js +1 -1
  82. package/dist/src/device-shell.js +1 -1
  83. package/dist/src/device.js +1 -1
  84. package/dist/src/device2.js +1 -1
  85. package/dist/src/devicectl.js +1 -1
  86. package/dist/src/diagnostics.js +1 -1
  87. package/dist/src/dispatch-resolve.js +1 -1
  88. package/dist/src/doctor-facade.js +1 -1
  89. package/dist/src/doctor2.js +1 -1
  90. package/dist/src/durable-resource.js +1 -1
  91. package/dist/src/engine-eval-script.js +1 -1
  92. package/dist/src/error.js +1 -1
  93. package/dist/src/exec.js +1 -1
  94. package/dist/src/file.js +1 -1
  95. package/dist/src/find.js +1 -1
  96. package/dist/src/format.js +1 -2
  97. package/dist/src/gesture-admission.js +1 -1
  98. package/dist/src/gesture-plan.js +1 -1
  99. package/dist/src/gesture-runtime.js +1 -1
  100. package/dist/src/harmonyos.js +1 -1
  101. package/dist/src/helper.js +2 -2
  102. package/dist/src/host.js +1 -1
  103. package/dist/src/human-control-contract.js +1 -1
  104. package/dist/src/human-control.js +1 -1
  105. package/dist/src/index.d.ts +1 -1
  106. package/dist/src/index.js +1 -1
  107. package/dist/src/input-actions.js +1 -1
  108. package/dist/src/input-actions2.js +1 -1
  109. package/dist/src/install-artifact-facade.js +1 -1
  110. package/dist/src/install-source-network-transport.js +1 -1
  111. package/dist/src/install-source2.js +1 -1
  112. package/dist/src/interaction-positionals.js +1 -1
  113. package/dist/src/interaction-ref-policy.js +1 -1
  114. package/dist/src/interaction2.js +1 -1
  115. package/dist/src/interactor-operation-binding.js +1 -1
  116. package/dist/src/interactor.js +5 -4
  117. package/dist/src/interactor2.js +1 -1
  118. package/dist/src/interactors.js +1 -1
  119. package/dist/src/internal/bin.js +1 -1
  120. package/dist/src/internal/daemon.js +2 -2
  121. package/dist/src/internal/png-worker.js +1 -1
  122. package/dist/src/internal/run-script-http-child.js +1 -1
  123. package/dist/src/inventory.js +1 -1
  124. package/dist/src/inventory2.js +1 -1
  125. package/dist/src/inventory3.js +1 -1
  126. package/dist/src/inventory5.js +1 -1
  127. package/dist/src/ios-snapshot-engine.js +1 -1
  128. package/dist/src/ios-snapshot-runtime.js +1 -1
  129. package/dist/src/lease-context.js +1 -1
  130. package/dist/src/lease-scope.js +1 -1
  131. package/dist/src/lease.js +1 -1
  132. package/dist/src/legacy-xctest-device-set.js +1 -0
  133. package/dist/src/limrun-download-file.js +1 -1
  134. package/dist/src/limrun-runtime-dependencies.js +1 -1
  135. package/dist/src/limrun.d.ts +1 -1
  136. package/dist/src/linux.js +1 -1
  137. package/dist/src/location-coordinates.js +1 -1
  138. package/dist/src/managed-device-scope.js +1 -1
  139. package/dist/src/mechanics.js +16 -16
  140. package/dist/src/multitouch-support.js +1 -1
  141. package/dist/src/observation.js +1 -1
  142. package/dist/src/overlay.js +3 -3
  143. package/dist/src/owner-identity.js +3 -3
  144. package/dist/src/parameterized-recorded-fill.js +1 -1
  145. package/dist/src/perf-facade.js +1 -1
  146. package/dist/src/perf-runtime-operation-builder.js +1 -1
  147. package/dist/src/perf-runtime-plan.js +1 -1
  148. package/dist/src/perf-target.js +1 -1
  149. package/dist/src/perf.js +1 -1
  150. package/dist/src/physical-device-control.js +1 -1
  151. package/dist/src/platform-request-scope.js +1 -1
  152. package/dist/src/platform-runtime-app-log-process.js +1 -1
  153. package/dist/src/platform-runtime-managed-owner.js +1 -1
  154. package/dist/src/platform-runtime-open-target.js +1 -1
  155. package/dist/src/platform-runtime-operation-host.js +2 -2
  156. package/dist/src/platform-runtime-runtime-hints.js +1 -1
  157. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  158. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  159. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  160. package/dist/src/platform-runtime-unavailable.js +1 -1
  161. package/dist/src/platform-runtime.js +1 -1
  162. package/dist/src/platform-runtime2.js +1 -1
  163. package/dist/src/png-crop-bytes.js +1 -1
  164. package/dist/src/png-size.js +1 -1
  165. package/dist/src/png-worker-client.js +1 -1
  166. package/dist/src/png.js +1 -1
  167. package/dist/src/prepare-kind.js +1 -1
  168. package/dist/src/process.js +1 -1
  169. package/dist/src/provider-session-ownership.js +1 -1
  170. package/dist/src/provider-webdriver.js +1 -1
  171. package/dist/src/proxy.js +2 -2
  172. package/dist/src/quality-warnings.js +1 -1
  173. package/dist/src/react-native.js +1 -1
  174. package/dist/src/record-runtime.js +1 -1
  175. package/dist/src/recording.js +1 -1
  176. package/dist/src/rect-center.js +1 -1
  177. package/dist/src/register-builtins.js +1 -1
  178. package/dist/src/registry.js +36 -36
  179. package/dist/src/remote-config2.js +1 -1
  180. package/dist/src/replay-divergence.js +1 -1
  181. package/dist/src/replay.js +1 -1
  182. package/dist/src/reporting.js +1 -1
  183. package/dist/src/request-admission.js +1 -1
  184. package/dist/src/request-cancel.js +1 -1
  185. package/dist/src/request-runtime-binding.js +1 -1
  186. package/dist/src/result-serialization.js +1 -1
  187. package/dist/src/retry.js +1 -1
  188. package/dist/src/runner-artifact-manifest.js +1 -0
  189. package/dist/src/runner-cache-metadata.js +3 -2
  190. package/dist/src/runner-client.js +1 -1
  191. package/dist/src/runner-command-traits.js +1 -0
  192. package/dist/src/runner-dev-tools-security.js +1 -1
  193. package/dist/src/runner-device-readiness.js +1 -1
  194. package/dist/src/runner-disposal.js +1 -1
  195. package/dist/src/runner-error-classification.js +3 -3
  196. package/dist/src/runner-exchange.js +1 -0
  197. package/dist/src/runner-operations-facade.js +1 -1
  198. package/dist/src/runner-owner-facade.js +1 -1
  199. package/dist/src/runner-session-types.js +1 -0
  200. package/dist/src/runner-start-budget.js +1 -0
  201. package/dist/src/runner-startup-transport.js +1 -0
  202. package/dist/src/runner-xctestrun.js +1 -0
  203. package/dist/src/runner.js +1 -1
  204. package/dist/src/runtime-transport-hints.js +1 -1
  205. package/dist/src/runtime.js +1 -1
  206. package/dist/src/runtime10.js +2 -2
  207. package/dist/src/runtime2.js +3 -3
  208. package/dist/src/runtime3.js +1 -1
  209. package/dist/src/runtime4.js +5 -5
  210. package/dist/src/runtime5.js +1 -1
  211. package/dist/src/runtime6.js +1 -1
  212. package/dist/src/runtime7.js +1 -1
  213. package/dist/src/runtime8.js +1 -1
  214. package/dist/src/runtime9.js +1 -1
  215. package/dist/src/screen-recording-admission-ledger.js +1 -1
  216. package/dist/src/screen-recording-completion.js +1 -1
  217. package/dist/src/screen-recording-options.js +1 -1
  218. package/dist/src/screen-recording-resource-recovery.js +1 -1
  219. package/dist/src/screen-recording-transport.js +1 -1
  220. package/dist/src/screenshot-density.js +1 -1
  221. package/dist/src/screenshot-image.js +1 -1
  222. package/dist/src/screenshot-runtime.js +1 -1
  223. package/dist/src/screenshot.js +1 -1
  224. package/dist/src/screenshot3.js +1 -1
  225. package/dist/src/script.js +1 -1
  226. package/dist/src/scroll-command.d.ts +4 -1
  227. package/dist/src/scroll-command.js +1 -1
  228. package/dist/src/scroll-gesture.js +1 -1
  229. package/dist/src/sdk-batch-runner.js +2 -2
  230. package/dist/src/sdk-batch.js +2 -2
  231. package/dist/src/sdk-contracts.d.ts +10 -0
  232. package/dist/src/sdk-contracts.js +2 -2
  233. package/dist/src/sdk-device.js +1 -1
  234. package/dist/src/sdk-io.js +1 -1
  235. package/dist/src/sdk-remote-config.js +1 -1
  236. package/dist/src/selection.js +0 -0
  237. package/dist/src/selector-runtime.js +1 -1
  238. package/dist/src/server.js +1 -1
  239. package/dist/src/session-allocation.js +1 -1
  240. package/dist/src/session-routing.js +1 -1
  241. package/dist/src/session-runtime-admission.js +1 -1
  242. package/dist/src/session-snapshot.js +1 -1
  243. package/dist/src/session-store.js +3 -3
  244. package/dist/src/session2.js +5 -4
  245. package/dist/src/settings.js +1 -1
  246. package/dist/src/settings2.js +1 -1
  247. package/dist/src/simctl.js +1 -1
  248. package/dist/src/simulator-boot.js +1 -1
  249. package/dist/src/simulator.js +1 -1
  250. package/dist/src/snapshot-runtime-binding.js +1 -1
  251. package/dist/src/snapshot-runtime.js +1 -1
  252. package/dist/src/snapshot-state.js +1 -1
  253. package/dist/src/snapshot2.js +1 -1
  254. package/dist/src/snapshot3.js +1 -1
  255. package/dist/src/snapshot4.js +1 -1
  256. package/dist/src/src.js +1 -1
  257. package/dist/src/src7.js +1 -1
  258. package/dist/src/src8.js +2 -2
  259. package/dist/src/string-enum.js +1 -1
  260. package/dist/src/takeover.js +1 -1
  261. package/dist/src/target-evidence.js +1 -0
  262. package/dist/src/text-entry-focus.js +1 -1
  263. package/dist/src/tool-provider.js +1 -1
  264. package/dist/src/tool-provider2.js +1 -1
  265. package/dist/src/touch-runtime.js +1 -1
  266. package/dist/src/transport.js +2 -2
  267. package/dist/src/tv-remote.js +1 -1
  268. package/dist/src/type-text-runtime.js +1 -1
  269. package/dist/src/vars.js +2 -0
  270. package/dist/src/verified-file.js +1 -1
  271. package/dist/src/video.js +1 -1
  272. package/dist/src/viewport.js +1 -1
  273. package/dist/src/web.js +1 -1
  274. package/dist/src/web2.js +1 -1
  275. package/dist/src/webdriver-source.js +1 -1
  276. package/package.json +2 -2
  277. package/server.json +2 -2
  278. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.15.apk.sha256 +0 -1
  279. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.15.apk.sha256 +0 -1
  280. package/dist/src/capture-disclosure.js +0 -1
  281. package/dist/src/fold-runtime.js +0 -1
  282. package/dist/src/native-build-cache.js +0 -1
@@ -42,8 +42,16 @@ extension RunnerTests {
42
42
  #if os(iOS)
43
43
  let postStartedAt = Date()
44
44
  let result = replacingExistingText
45
- ? RunnerSynthesizedTextEntry.replaceText(withApplication: app, text: text)
46
- : RunnerSynthesizedTextEntry.synthesizeText(withApplication: app, text: text)
45
+ ? RunnerSynthesizedTextEntry.replaceText(
46
+ withApplication: app,
47
+ text: text,
48
+ charactersPerSecond: TextEntryTiming.synthesizedCharactersPerSecond
49
+ )
50
+ : RunnerSynthesizedTextEntry.synthesizeText(
51
+ withApplication: app,
52
+ text: text,
53
+ charactersPerSecond: TextEntryTiming.synthesizedCharactersPerSecond
54
+ )
47
55
  NSLog(
48
56
  "[DEBUG-1874] synthesize posted %d chars status=%d tookMs=%.0f",
49
57
  text.count, result.status.rawValue, postStartedAt.timeIntervalSinceNow * -1000
@@ -73,90 +81,206 @@ extension RunnerTests {
73
81
  #endif
74
82
  }
75
83
 
76
- struct SynthesizedReplacementStep: Equatable {
77
- let text: String
78
- let replacesExistingText: Bool
79
- }
80
84
 
81
- static func synthesizedReplacementSteps(
82
- text: String,
83
- delaySeconds: Double
84
- ) -> [SynthesizedReplacementStep] {
85
- guard synthesizedReplacementIsSpaced(characterCount: text.count, delaySeconds: delaySeconds)
86
- else {
87
- return [SynthesizedReplacementStep(text: text, replacesExistingText: true)]
85
+
86
+
87
+
88
+ struct SynthesizedTextPlan: Equatable {
89
+
90
+ struct Step: Equatable {
91
+ let characterCount: Int
92
+
93
+
94
+ var replacesExistingText = false
95
+
96
+
97
+
98
+
99
+ var pauseAfterSeconds: TimeInterval = 0
100
+
101
+ var warmsUpField = false
102
+
103
+
104
+ var synthesizeCallCount: Int {
105
+ replacesExistingText ? 2 : 1
106
+ }
88
107
  }
89
- return Array(text).enumerated().map { index, character in
90
- SynthesizedReplacementStep(
91
- text: String(character),
92
- replacesExistingText: index == 0
93
- )
108
+
109
+ let steps: [Step]
110
+
111
+
112
+
113
+ static func spacedSteps(
114
+ characterCount: Int,
115
+ delaySeconds: Double,
116
+ replacesExistingTextOnFirstPost: Bool = false
117
+ ) -> [Step] {
118
+ (0..<characterCount).map { index in
119
+ Step(
120
+ characterCount: 1,
121
+ replacesExistingText: replacesExistingTextOnFirstPost && index == 0,
122
+ pauseAfterSeconds: index + 1 < characterCount ? delaySeconds : 0
123
+ )
124
+ }
125
+ }
126
+
127
+
128
+
129
+ var seconds: TimeInterval {
130
+ steps.reduce(0) { total, step in
131
+ total
132
+ + Double(step.characterCount) * TextEntryTiming.synthesizedCharacterInterval
133
+ + Double(step.synthesizeCallCount) * TextEntryTiming.synthesizeCallOverhead
134
+ + step.pauseAfterSeconds
135
+ }
136
+ }
137
+
138
+
139
+
140
+
141
+ var pacesEveryCharacter: Bool {
142
+ steps.count > 1 && !steps.contains(where: \.warmsUpField)
94
143
  }
95
144
  }
96
145
 
97
146
 
98
147
 
99
- static func synthesizedReplacementIsSpaced(characterCount: Int, delaySeconds: Double) -> Bool {
148
+ static func synthesizedDeliveryIsSpaced(characterCount: Int, delaySeconds: Double) -> Bool {
100
149
  delaySeconds > 0 && characterCount > 1
101
150
  }
102
151
 
103
152
 
104
153
 
105
154
 
106
- enum SynthesizedDeliveryBudget {
107
155
 
108
- static var characterInterval: TimeInterval {
109
- 1.0 / Double(RunnerSynthesizedTextEntry.typingSpeedCharactersPerSecond())
110
- }
111
156
 
112
157
 
113
158
 
114
159
 
160
+ static func synthesizedTextPlan(
161
+ characterCount: Int,
162
+ delaySeconds: Double,
163
+ selectsExistingText: Bool,
164
+ peelsWarmupCharacter: Bool = false
165
+ ) -> SynthesizedTextPlan {
166
+ if synthesizedDeliveryIsSpaced(characterCount: characterCount, delaySeconds: delaySeconds) {
167
+ return SynthesizedTextPlan(
168
+ steps: SynthesizedTextPlan.spacedSteps(
169
+ characterCount: characterCount,
170
+ delaySeconds: delaySeconds,
171
+ replacesExistingTextOnFirstPost: selectsExistingText
172
+ )
173
+ )
174
+ }
175
+ guard peelsWarmupCharacter && characterCount > 1 else {
176
+ return SynthesizedTextPlan(
177
+ steps: [
178
+ SynthesizedTextPlan.Step(
179
+ characterCount: characterCount,
180
+ replacesExistingText: selectsExistingText
181
+ ),
182
+ ]
183
+ )
184
+ }
185
+ return SynthesizedTextPlan(
186
+ steps: [
187
+ SynthesizedTextPlan.Step(
188
+ characterCount: 1,
189
+ pauseAfterSeconds: TextEntryTiming.pollInterval,
190
+ warmsUpField: true
191
+ ),
192
+ SynthesizedTextPlan.Step(characterCount: characterCount - 1),
193
+ ]
194
+ )
195
+ }
115
196
 
116
197
 
117
- static func projectedSeconds(
118
- textLength: Int,
119
- delaySeconds: TimeInterval,
120
- typeWarmup: Bool = false
121
- ) -> TimeInterval {
122
- let spaced = synthesizedReplacementIsSpaced(characterCount: textLength, delaySeconds: delaySeconds)
123
- let warmupSplit = typeWarmup && textLength > 1 && !spaced
124
- let calls = spaced ? textLength : (warmupSplit ? 2 : 1)
125
- return Double(textLength) * characterInterval
126
- + Double(calls) * TextEntryTiming.synthesizeCallOverhead
127
- + Double(calls - 1) * delaySeconds
128
- + (warmupSplit ? TextEntryTiming.pollInterval : 0)
129
- }
130
198
 
131
- static func exceeds(
132
- textLength: Int,
133
- delaySeconds: TimeInterval,
134
- typeWarmup: Bool = false
135
- ) -> Bool {
136
- projectedSeconds(textLength: textLength, delaySeconds: delaySeconds, typeWarmup: typeWarmup)
137
- > TextEntryTiming.synthesizedDeliveryCeiling
199
+ enum SynthesizedDeliveryBudget {
200
+ static func exceeds(_ plan: SynthesizedTextPlan) -> Bool {
201
+ plan.seconds > TextEntryTiming.synthesizedDeliveryCeiling
138
202
  }
139
203
 
140
204
 
205
+
206
+
141
207
  static func maxTextLength(delaySeconds: TimeInterval) -> Int {
142
208
  var length = 1
143
- while !exceeds(textLength: length + 1, delaySeconds: delaySeconds) {
209
+ while !exceeds(
210
+ synthesizedTextPlan(
211
+ characterCount: length + 1,
212
+ delaySeconds: delaySeconds,
213
+ selectsExistingText: true
214
+ )
215
+ ) {
144
216
  length += 1
145
217
  }
146
218
  return length
147
219
  }
148
220
  }
149
221
 
222
+
223
+
224
+ enum SynthesizedStepDispatch {
225
+ case posted
226
+ case stop
227
+ }
228
+
229
+ struct SynthesizedPlanRun {
230
+ let postedCharacterCount: Int
231
+
232
+ let stoppedEarly: Bool
233
+ }
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+
245
+ @MainActor
246
+ func runSynthesizedTextPlan(
247
+ _ plan: SynthesizedTextPlan,
248
+ text: String,
249
+ post: (_ characters: String, _ step: SynthesizedTextPlan.Step) -> SynthesizedStepDispatch,
250
+ waitAfterWarmupCharacter: (_ characters: String) -> Void,
251
+ didPostStep: (_ step: SynthesizedTextPlan.Step) -> Void = { _ in }
252
+ ) -> SynthesizedPlanRun {
253
+ let characters = Array(text)
254
+ var postedCount = 0
255
+ for step in plan.steps {
256
+ let nextCount = postedCount + step.characterCount
257
+ if post(String(characters[postedCount..<nextCount]), step) == .stop {
258
+
259
+ return SynthesizedPlanRun(postedCharacterCount: postedCount, stoppedEarly: true)
260
+ }
261
+ postedCount = nextCount
262
+ didPostStep(step)
263
+ if step.warmsUpField {
264
+ waitAfterWarmupCharacter(String(characters[postedCount - step.characterCount..<postedCount]))
265
+ } else if step.pauseAfterSeconds > 0 {
266
+ sleepFor(step.pauseAfterSeconds)
267
+ }
268
+ }
269
+ return SynthesizedPlanRun(postedCharacterCount: postedCount, stoppedEarly: false)
270
+ }
271
+
150
272
  @MainActor
151
273
  func runSynthesizedReplacementRoute(
152
274
  _ request: SynthesizedReplacementRequest
153
275
  ) -> SynthesizedReplacementRouteOutcome {
154
276
  #if os(iOS)
155
277
  NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder-replacement")
156
- if SynthesizedDeliveryBudget.exceeds(
157
- textLength: request.text.count,
158
- delaySeconds: request.delaySeconds
159
- ) {
278
+ let plan = Self.synthesizedTextPlan(
279
+ characterCount: request.text.count,
280
+ delaySeconds: request.delaySeconds,
281
+ selectsExistingText: true
282
+ )
283
+ if SynthesizedDeliveryBudget.exceeds(plan) {
160
284
  NSLog(
161
285
  "AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder-replacement "
162
286
  + "reason=delivery-budget-refused chars=%d budgetChars=%d",
@@ -174,33 +298,38 @@ extension RunnerTests {
174
298
  )
175
299
  )
176
300
  }
177
- let steps = Self.synthesizedReplacementSteps(
301
+
302
+
303
+ let synthesisAvailable = runSynthesizedTextPlan(
304
+ plan,
178
305
  text: request.text,
179
- delaySeconds: request.delaySeconds
306
+ post: { slice, step in
307
+ switch request.synthesizer.enterText(
308
+ app: request.app,
309
+ text: slice,
310
+ replacingExistingText: step.replacesExistingText
311
+ ) {
312
+ case .continueTyping:
313
+ return .posted
314
+ case .fallback:
315
+ return .stop
316
+ case .raise(let message):
317
+ NSException(
318
+ name: NSExceptionName.internalInconsistencyException,
319
+ reason: message ?? "private XCTest text synthesis failed"
320
+ ).raise()
321
+ return .stop
322
+ }
323
+ },
324
+
325
+ waitAfterWarmupCharacter: { _ in }
180
326
  )
181
- for (index, step) in steps.enumerated() {
182
- switch request.synthesizer.enterText(
183
- app: request.app,
184
- text: step.text,
185
- replacingExistingText: step.replacesExistingText
186
- ) {
187
- case .fallback:
188
- NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
189
- guard let point = request.target.refreshPoint else { return .notApplicable }
190
- return .fallback(
191
- focusTextInputForTextEntry(app: request.app, x: point.x, y: point.y)
192
- )
193
- case .raise(let message):
194
- NSException(
195
- name: NSExceptionName.internalInconsistencyException,
196
- reason: message ?? "private XCTest text synthesis failed"
197
- ).raise()
198
- case .continueTyping:
199
- break
200
- }
201
- if index + 1 < steps.count {
202
- sleepFor(request.delaySeconds)
203
- }
327
+ if synthesisAvailable.stoppedEarly {
328
+ NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
329
+ guard let point = request.target.refreshPoint else { return .notApplicable }
330
+ return .fallback(
331
+ focusTextInputForTextEntry(app: request.app, x: point.x, y: point.y)
332
+ )
204
333
  }
205
334
 
206
335
 
@@ -36,7 +36,11 @@ extension RunnerTests {
36
36
  let recoveryBudget = SynthesizedDeliveryBudget.maxTextLength(
37
37
  delaySeconds: Double(recoveryDelay) / 1000
38
38
  )
39
- return "Fill at most \(SynthesizedDeliveryBudget.maxTextLength(delaySeconds: 0)) characters at a time without --delay-ms and append the rest with separate type commands, keeping each command inside that budget. --delay-ms lowers the budget, because each character then gets its own synthesize call and each gap between characters pays the delay: \(recoveryBudget) characters at --delay-ms \(recoveryDelay). A longer timeout does not help: this route is chosen when the accessibility channel is already degraded, and the pace is what makes the text long."
39
+
40
+
41
+
42
+
43
+ return "Fill at most \(SynthesizedDeliveryBudget.maxTextLength(delaySeconds: 0)) characters per command without --delay-ms and append the rest with separate type commands. --delay-ms lowers the limit: each character then gets its own synthesize call and each gap pays the delay, so \(recoveryDelay) ms fits \(recoveryBudget). This route is chosen when the accessibility channel is already degraded, so a longer timeout does not help."
40
44
  }
41
45
  }
42
46
  }
@@ -76,7 +80,18 @@ extension RunnerTests {
76
80
 
77
81
 
78
82
 
79
- static let synthesizedAcknowledgeWindowSeconds: TimeInterval = 0.04
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+ static let synthesizedCharactersPerSecond: UInt = 12
93
+
94
+ static let synthesizedCharacterInterval: TimeInterval = 1.0 / Double(synthesizedCharactersPerSecond)
80
95
 
81
96
 
82
97
 
@@ -136,6 +136,39 @@ extension RunnerTests {
136
136
  }
137
137
  }
138
138
 
139
+
140
+
141
+
142
+
143
+
144
+ let synthesizedTypePlan = Self.synthesizedTextPlan(
145
+ characterCount: text.count,
146
+ delaySeconds: delaySeconds,
147
+ selectsExistingText: false,
148
+ peelsWarmupCharacter: repairMode != .none
149
+ )
150
+
151
+
152
+
153
+
154
+ let synthesizedTypeCarriesTheCommand = !SynthesizedDeliveryBudget.exceeds(synthesizedTypePlan)
155
+
156
+
157
+
158
+
159
+
160
+ func typeApplicationWide(_ value: String, reason: String) -> (element: XCUIElement?, failure: TextEntryFailure?) {
161
+ NSLog(
162
+ "AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=xctest-application-fallback reason=%@ chars=%d commandChars=%d",
163
+ reason,
164
+ value.count,
165
+ text.count
166
+ )
167
+ textEntryRoute = "xctest-application-fallback"
168
+ app.typeText(value)
169
+ return (resolveTextEntryElement(app: app, target: activeTarget), nil)
170
+ }
171
+
139
172
  func typeIntoCurrentTarget(_ value: String) -> (element: XCUIElement?, failure: TextEntryFailure?) {
140
173
  #if os(iOS)
141
174
  if shouldUseSynthesizedFirstResponderType {
@@ -167,35 +200,14 @@ extension RunnerTests {
167
200
 
168
201
 
169
202
 
170
-
171
-
172
-
173
-
174
- if SynthesizedDeliveryBudget.exceeds(
175
- textLength: text.count,
176
- delaySeconds: delaySeconds,
177
- typeWarmup: repairMode != .none
178
- ) {
179
- textEntryRoute = "xctest-application-fallback"
180
- NSLog(
181
- "AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=xctest-application-fallback "
182
- + "reason=delivery-budget chars=%d",
183
- value.count
184
- )
185
- app.typeText(value)
186
- return (resolveTextEntryElement(app: app, target: activeTarget), nil)
203
+ guard synthesizedTypeCarriesTheCommand else {
204
+ return typeApplicationWide(value, reason: "delivery-budget")
187
205
  }
188
206
  textEntryRoute = "synthesized-first-responder"
189
207
  NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=synthesized-first-responder")
190
- let action = synthesizer.enterText(
191
- app: app,
192
- text: value,
193
- replacingExistingText: false
194
- )
195
- switch action {
208
+ switch synthesizer.enterText(app: app, text: value, replacingExistingText: false) {
196
209
  case .fallback:
197
- textEntryRoute = "xctest-application-fallback"
198
- app.typeText(value)
210
+ return typeApplicationWide(value, reason: "synthesis-unavailable")
199
211
  case .raise(let message):
200
212
  NSException(
201
213
  name: NSExceptionName.internalInconsistencyException,
@@ -223,131 +235,100 @@ extension RunnerTests {
223
235
  )
224
236
  }
225
237
 
226
- func waitForWarmupValue(_ expectedValue: String?, target: TextEntryTarget) {
227
- guard let expectedValue else {
238
+ func readBackWarmupCharacter(_ peeledCharacter: String) {
239
+
240
+
241
+
242
+ let warmupExpectedText = expectedTextEntryValue(
243
+ typedText: peeledCharacter,
244
+ mode: repairMode,
245
+ initialText: initialText
246
+ )
247
+ guard let warmupExpectedText else {
228
248
  sleepFor(TextEntryTiming.pollInterval)
229
249
  return
230
250
  }
231
251
  let deadline = Date().addingTimeInterval(TextEntryTiming.warmupValueTimeout)
232
252
  while Date() < deadline {
233
- if editableTextValue(for: resolveTextEntryElement(app: app, target: target)) == expectedValue {
253
+ if editableTextValue(for: resolveTextEntryElement(app: app, target: activeTarget)) == warmupExpectedText {
234
254
  return
235
255
  }
236
256
  sleepFor(TextEntryTiming.pollInterval)
237
257
  }
238
258
  }
239
259
 
240
- let characters = Array(text)
241
- if delaySeconds > 0 && characters.count > 1 {
242
- var typedTarget: XCUIElement?
243
- let delayedTypeStartedAt = Date()
244
- for (index, character) in characters.enumerated() {
245
- let dispatch = typeIntoCurrentTarget(String(character))
260
+
261
+
262
+ var typedTarget: XCUIElement?
263
+ var sawWarmupPost = false
264
+ var planFailure: TextEntryResult?
265
+ var stepStartedAt = Date()
266
+ let pacedStartedAt = Date()
267
+ runSynthesizedTextPlan(
268
+ synthesizedTypePlan,
269
+ text: text,
270
+ post: { slice, step in
271
+ stepStartedAt = Date()
272
+ let dispatch = typeIntoCurrentTarget(slice)
246
273
  if let failure = dispatch.failure {
247
- return dispatchFailureResult(failure)
274
+ planFailure = dispatchFailureResult(failure)
275
+ return .stop
248
276
  }
249
277
  typedTarget = dispatch.element ?? typedTarget
250
- if index + 1 < characters.count {
251
- sleepFor(delaySeconds)
278
+ if step.warmsUpField {
279
+
280
+ activeTarget = activeTarget.withElement(typedTarget)
252
281
  }
253
- }
254
- logTextEntryPhase(
255
- commandId: commandId,
256
- phase: "type-delayed",
257
- startedAt: delayedTypeStartedAt,
258
- chars: characters.count,
259
- mode: repairMode
260
- )
261
- if repairMode == .none {
262
- logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
263
- return TextEntryResult(
264
- verified: nil,
265
- repaired: false,
266
- expectedText: nil,
267
- observedText: nil,
268
- textEntryRoute: textEntryRoute
282
+ return .posted
283
+ },
284
+ waitAfterWarmupCharacter: { peeledCharacter in
285
+ let warmupStartedAt = Date()
286
+ readBackWarmupCharacter(peeledCharacter)
287
+ logTextEntryPhase(
288
+ commandId: commandId,
289
+ phase: "warmup",
290
+ startedAt: warmupStartedAt,
291
+ chars: 1,
292
+ mode: repairMode
293
+ )
294
+ },
295
+ didPostStep: { step in
296
+ guard !synthesizedTypePlan.pacesEveryCharacter else {
297
+ return
298
+ }
299
+ let phase: String
300
+ if step.warmsUpField {
301
+ phase = "type-first"
302
+ sawWarmupPost = true
303
+ } else {
304
+ phase = sawWarmupPost ? "type-remaining" : "type-all"
305
+ }
306
+ logTextEntryPhase(
307
+ commandId: commandId,
308
+ phase: phase,
309
+ startedAt: stepStartedAt,
310
+ chars: step.characterCount,
311
+ mode: repairMode
269
312
  )
270
313
  }
271
- let verifyStartedAt = Date()
272
- var result = verifyTextEntryWithRepairIfNeeded(
273
- app: app,
274
- target: activeTarget.withElement(typedTarget),
275
- expectedText: expectedText,
276
- repairMode: repairMode
277
- )
314
+ )
315
+ if synthesizedTypePlan.pacesEveryCharacter {
316
+
317
+
278
318
  logTextEntryPhase(
279
319
  commandId: commandId,
280
- phase: "verify",
281
- startedAt: verifyStartedAt,
282
- chars: characters.count,
320
+ phase: "type-delayed",
321
+ startedAt: pacedStartedAt,
322
+ chars: text.count,
283
323
  mode: repairMode
284
324
  )
285
- logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
286
- result.textEntryRoute = textEntryRoute
287
- return result
288
325
  }
289
326
 
290
- let typedTarget: XCUIElement?
291
- if repairMode != .none && characters.count > 1 {
292
- let firstCharacter = String(characters[0])
293
- let firstStartedAt = Date()
294
- let firstDispatch = typeIntoCurrentTarget(firstCharacter)
295
- if let failure = firstDispatch.failure {
296
- return dispatchFailureResult(failure)
297
- }
298
- var firstTypedTarget = firstDispatch.element
299
- logTextEntryPhase(
300
- commandId: commandId,
301
- phase: "type-first",
302
- startedAt: firstStartedAt,
303
- chars: 1,
304
- mode: repairMode
305
- )
306
- activeTarget = activeTarget.withElement(firstTypedTarget)
307
- let warmupExpectedText = expectedTextEntryValue(
308
- typedText: firstCharacter,
309
- mode: repairMode,
310
- initialText: initialText
311
- )
312
- let warmupStartedAt = Date()
313
- waitForWarmupValue(warmupExpectedText, target: activeTarget)
314
- logTextEntryPhase(
315
- commandId: commandId,
316
- phase: "warmup",
317
- startedAt: warmupStartedAt,
318
- chars: 1,
319
- mode: repairMode
320
- )
321
- let remainingText = String(characters.dropFirst())
322
- let remainingStartedAt = Date()
323
- let remainingDispatch = typeIntoCurrentTarget(remainingText)
324
- if let failure = remainingDispatch.failure {
325
- return dispatchFailureResult(failure)
326
- }
327
- firstTypedTarget = remainingDispatch.element ?? firstTypedTarget
328
- logTextEntryPhase(
329
- commandId: commandId,
330
- phase: "type-remaining",
331
- startedAt: remainingStartedAt,
332
- chars: characters.count - 1,
333
- mode: repairMode
334
- )
335
- typedTarget = firstTypedTarget
336
- } else {
337
- let typeStartedAt = Date()
338
- let dispatch = typeIntoCurrentTarget(text)
339
- if let failure = dispatch.failure {
340
- return dispatchFailureResult(failure)
341
- }
342
- typedTarget = dispatch.element
343
- logTextEntryPhase(
344
- commandId: commandId,
345
- phase: "type-all",
346
- startedAt: typeStartedAt,
347
- chars: characters.count,
348
- mode: repairMode
349
- )
327
+
328
+ if let failure = planFailure {
329
+ return failure
350
330
  }
331
+ activeTarget = activeTarget.withElement(typedTarget)
351
332
  if repairMode == .none {
352
333
  logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
353
334
  return TextEntryResult(
@@ -361,7 +342,7 @@ extension RunnerTests {
361
342
  let verifyStartedAt = Date()
362
343
  var result = verifyTextEntryWithRepairIfNeeded(
363
344
  app: app,
364
- target: activeTarget.withElement(typedTarget),
345
+ target: activeTarget,
365
346
  expectedText: expectedText,
366
347
  repairMode: repairMode
367
348
  )
@@ -369,7 +350,7 @@ extension RunnerTests {
369
350
  commandId: commandId,
370
351
  phase: "verify",
371
352
  startedAt: verifyStartedAt,
372
- chars: characters.count,
353
+ chars: text.count,
373
354
  mode: repairMode
374
355
  )
375
356
  logTextEntryPhase(commandId: commandId, phase: "total", startedAt: totalStartedAt, chars: text.count, mode: repairMode)
@@ -5,6 +5,7 @@
5
5
 
6
6
 
7
7
 
8
+ import AgentDeviceSnapshotPresentation
8
9
  import XCTest
9
10
  import Network
10
11
  #if canImport(UIKit)
@@ -211,6 +212,9 @@ final class RunnerTests: XCTestCase {
211
212
 
212
213
 
213
214
 
215
+
216
+
217
+
214
218
 
215
219
 
216
220