agent-device 0.21.15 → 0.21.16

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 (248) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.15.apk → agent-device-android-ime-helper-0.21.16.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.16.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.16.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.16.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.16.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.16.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+SynthesizedTextEntry.swift +202 -73
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +17 -2
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +109 -128
  14. package/dist/src/adapter.js +1 -1
  15. package/dist/src/adb-executor.js +1 -1
  16. package/dist/src/adb-failure.js +1 -1
  17. package/dist/src/adb-transport.js +1 -1
  18. package/dist/src/agent-browser-provider.js +1 -1
  19. package/dist/src/agent-browser-tool.js +1 -1
  20. package/dist/src/agent-device-client.js +2 -2
  21. package/dist/src/ai-sdk.js +1 -1
  22. package/dist/src/alert-runtime.js +1 -1
  23. package/dist/src/android-input-ownership.js +1 -1
  24. package/dist/src/app-catalog.js +1 -1
  25. package/dist/src/app-deployment-resolution.js +1 -1
  26. package/dist/src/app-inventory.js +1 -1
  27. package/dist/src/app-launch.js +1 -1
  28. package/dist/src/app-lifecycle.js +1 -1
  29. package/dist/src/app-lifecycle2.js +1 -1
  30. package/dist/src/app-log-files.js +1 -1
  31. package/dist/src/app-log-runtime2.js +1 -1
  32. package/dist/src/app-resolution.js +1 -1
  33. package/dist/src/apple-runner-platform.js +1 -1
  34. package/dist/src/application-lifecycle-interaction.js +1 -1
  35. package/dist/src/archive.js +1 -1
  36. package/dist/src/artifact-download.js +1 -1
  37. package/dist/src/auth-session.js +1 -1
  38. package/dist/src/auth.js +1 -1
  39. package/dist/src/backend-snapshot-options.js +1 -1
  40. package/dist/src/boot-diagnostics.js +1 -1
  41. package/dist/src/capture-disclosure.js +1 -1
  42. package/dist/src/capture.js +1 -1
  43. package/dist/src/cli-config.js +1 -1
  44. package/dist/src/cli-help.js +3 -2
  45. package/dist/src/cli.js +4 -4
  46. package/dist/src/click-button.js +1 -1
  47. package/dist/src/client-metro.js +1 -1
  48. package/dist/src/client-types.d.ts +1 -1
  49. package/dist/src/clipboard-shell-response.js +1 -1
  50. package/dist/src/command-schema.js +1 -1
  51. package/dist/src/command-tools.js +2 -2
  52. package/dist/src/connection-runtime.js +1 -1
  53. package/dist/src/connection.js +5 -5
  54. package/dist/src/contracts.js +1 -1
  55. package/dist/src/daemon-client-lease-beat.js +1 -0
  56. package/dist/src/daemon-client-lifecycle.js +1 -1
  57. package/dist/src/daemon-client-transport.js +1 -0
  58. package/dist/src/daemon-shutdown-report.js +1 -1
  59. package/dist/src/daemon.js +1 -1
  60. package/dist/src/debug-symbols-facade.js +1 -1
  61. package/dist/src/device-claim-inspection.js +1 -1
  62. package/dist/src/device-claim-rule.js +2 -2
  63. package/dist/src/device-inventory-context.js +1 -1
  64. package/dist/src/device-rotation.js +1 -1
  65. package/dist/src/device-selection-resolver.js +1 -1
  66. package/dist/src/device-session.js +1 -1
  67. package/dist/src/device-shell.js +1 -1
  68. package/dist/src/device.js +1 -1
  69. package/dist/src/device2.js +1 -1
  70. package/dist/src/devicectl.js +1 -1
  71. package/dist/src/diagnostics.js +1 -1
  72. package/dist/src/dispatch-resolve.js +1 -1
  73. package/dist/src/doctor-facade.js +1 -1
  74. package/dist/src/doctor2.js +1 -1
  75. package/dist/src/durable-resource.js +1 -1
  76. package/dist/src/engine-eval-script.js +1 -1
  77. package/dist/src/error.js +1 -1
  78. package/dist/src/exec.js +1 -1
  79. package/dist/src/file.js +1 -1
  80. package/dist/src/find.js +1 -1
  81. package/dist/src/format.js +1 -1
  82. package/dist/src/gesture-admission.js +1 -1
  83. package/dist/src/gesture-plan.js +1 -1
  84. package/dist/src/gesture-runtime.js +1 -1
  85. package/dist/src/harmonyos.js +1 -1
  86. package/dist/src/helper.js +1 -1
  87. package/dist/src/host.js +1 -1
  88. package/dist/src/human-control-contract.js +1 -1
  89. package/dist/src/human-control.js +1 -1
  90. package/dist/src/index.js +1 -1
  91. package/dist/src/input-actions.js +1 -1
  92. package/dist/src/input-actions2.js +1 -1
  93. package/dist/src/install-artifact-facade.js +1 -1
  94. package/dist/src/install-source-network-transport.js +1 -1
  95. package/dist/src/install-source2.js +1 -1
  96. package/dist/src/interaction-positionals.js +1 -1
  97. package/dist/src/interaction-ref-policy.js +1 -1
  98. package/dist/src/interaction2.js +1 -1
  99. package/dist/src/interactor-operation-binding.js +1 -1
  100. package/dist/src/interactor.js +5 -4
  101. package/dist/src/interactor2.js +1 -1
  102. package/dist/src/interactors.js +1 -1
  103. package/dist/src/internal/bin.js +1 -1
  104. package/dist/src/internal/daemon.js +2 -2
  105. package/dist/src/internal/png-worker.js +1 -1
  106. package/dist/src/internal/run-script-http-child.js +1 -1
  107. package/dist/src/inventory.js +1 -1
  108. package/dist/src/inventory2.js +1 -1
  109. package/dist/src/inventory3.js +1 -1
  110. package/dist/src/inventory5.js +1 -1
  111. package/dist/src/ios-snapshot-engine.js +1 -1
  112. package/dist/src/ios-snapshot-runtime.js +1 -1
  113. package/dist/src/lease-context.js +1 -1
  114. package/dist/src/lease-scope.js +1 -1
  115. package/dist/src/lease.js +1 -1
  116. package/dist/src/limrun-download-file.js +1 -1
  117. package/dist/src/limrun-runtime-dependencies.js +1 -1
  118. package/dist/src/linux.js +1 -1
  119. package/dist/src/location-coordinates.js +1 -1
  120. package/dist/src/managed-device-scope.js +1 -1
  121. package/dist/src/mechanics.js +15 -15
  122. package/dist/src/multitouch-support.js +1 -1
  123. package/dist/src/native-build-cache.js +1 -1
  124. package/dist/src/observation.js +1 -1
  125. package/dist/src/overlay.js +2 -2
  126. package/dist/src/parameterized-recorded-fill.js +1 -1
  127. package/dist/src/perf-facade.js +1 -1
  128. package/dist/src/perf-runtime-operation-builder.js +1 -1
  129. package/dist/src/perf-runtime-plan.js +1 -1
  130. package/dist/src/perf-target.js +1 -1
  131. package/dist/src/perf.js +1 -1
  132. package/dist/src/physical-device-control.js +1 -1
  133. package/dist/src/platform-request-scope.js +1 -1
  134. package/dist/src/platform-runtime-managed-owner.js +1 -1
  135. package/dist/src/platform-runtime-open-target.js +1 -1
  136. package/dist/src/platform-runtime-operation-host.js +2 -2
  137. package/dist/src/platform-runtime-runtime-hints.js +1 -1
  138. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  139. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  140. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  141. package/dist/src/platform-runtime-unavailable.js +1 -1
  142. package/dist/src/platform-runtime.js +1 -1
  143. package/dist/src/platform-runtime2.js +1 -1
  144. package/dist/src/png-crop-bytes.js +1 -1
  145. package/dist/src/png-size.js +1 -1
  146. package/dist/src/png-worker-client.js +1 -1
  147. package/dist/src/png.js +1 -1
  148. package/dist/src/prepare-kind.js +1 -1
  149. package/dist/src/provider-session-ownership.js +1 -1
  150. package/dist/src/provider-webdriver.js +1 -1
  151. package/dist/src/proxy.js +1 -1
  152. package/dist/src/quality-warnings.js +1 -1
  153. package/dist/src/react-native.js +1 -1
  154. package/dist/src/record-runtime.js +1 -1
  155. package/dist/src/recording.js +1 -1
  156. package/dist/src/rect-center.js +1 -1
  157. package/dist/src/register-builtins.js +1 -1
  158. package/dist/src/registry.js +13 -13
  159. package/dist/src/remote-config2.js +1 -1
  160. package/dist/src/replay-divergence.js +1 -1
  161. package/dist/src/replay.js +1 -1
  162. package/dist/src/reporting.js +1 -1
  163. package/dist/src/request-admission.js +1 -1
  164. package/dist/src/request-cancel.js +1 -1
  165. package/dist/src/request-runtime-binding.js +1 -1
  166. package/dist/src/result-serialization.js +1 -1
  167. package/dist/src/retry.js +1 -1
  168. package/dist/src/runner-artifact-manifest.js +1 -0
  169. package/dist/src/runner-cache-metadata.js +3 -2
  170. package/dist/src/runner-client.js +1 -1
  171. package/dist/src/runner-dev-tools-security.js +1 -1
  172. package/dist/src/runner-device-readiness.js +1 -1
  173. package/dist/src/runner-disposal.js +1 -1
  174. package/dist/src/runner-error-classification.js +3 -3
  175. package/dist/src/runner-operations-facade.js +1 -1
  176. package/dist/src/runner.js +1 -1
  177. package/dist/src/runtime-transport-hints.js +1 -1
  178. package/dist/src/runtime.js +1 -1
  179. package/dist/src/runtime10.js +2 -2
  180. package/dist/src/runtime2.js +3 -3
  181. package/dist/src/runtime3.js +1 -1
  182. package/dist/src/runtime4.js +4 -4
  183. package/dist/src/runtime5.js +1 -1
  184. package/dist/src/runtime6.js +1 -1
  185. package/dist/src/runtime7.js +1 -1
  186. package/dist/src/runtime8.js +1 -1
  187. package/dist/src/runtime9.js +1 -1
  188. package/dist/src/screen-recording-admission-ledger.js +1 -1
  189. package/dist/src/screen-recording-completion.js +1 -1
  190. package/dist/src/screen-recording-options.js +1 -1
  191. package/dist/src/screen-recording-resource-recovery.js +1 -1
  192. package/dist/src/screen-recording-transport.js +1 -1
  193. package/dist/src/screenshot-density.js +1 -1
  194. package/dist/src/screenshot-image.js +1 -1
  195. package/dist/src/screenshot-runtime.js +1 -1
  196. package/dist/src/screenshot.js +1 -1
  197. package/dist/src/screenshot3.js +1 -1
  198. package/dist/src/script.js +1 -1
  199. package/dist/src/scroll-command.js +1 -1
  200. package/dist/src/scroll-gesture.js +1 -1
  201. package/dist/src/sdk-batch-runner.js +2 -2
  202. package/dist/src/sdk-batch.js +1 -1
  203. package/dist/src/sdk-contracts.js +2 -2
  204. package/dist/src/sdk-device.js +1 -1
  205. package/dist/src/sdk-io.js +1 -1
  206. package/dist/src/sdk-remote-config.js +1 -1
  207. package/dist/src/selector-runtime.js +1 -1
  208. package/dist/src/server.js +1 -1
  209. package/dist/src/session-allocation.js +1 -1
  210. package/dist/src/session-routing.js +1 -1
  211. package/dist/src/session-runtime-admission.js +1 -1
  212. package/dist/src/session-snapshot.js +1 -1
  213. package/dist/src/session-store.js +3 -3
  214. package/dist/src/session2.js +3 -3
  215. package/dist/src/settings.js +1 -1
  216. package/dist/src/settings2.js +1 -1
  217. package/dist/src/simctl.js +1 -1
  218. package/dist/src/simulator-boot.js +1 -1
  219. package/dist/src/simulator.js +1 -1
  220. package/dist/src/snapshot-runtime-binding.js +1 -1
  221. package/dist/src/snapshot-runtime.js +1 -1
  222. package/dist/src/snapshot-state.js +1 -1
  223. package/dist/src/snapshot2.js +1 -1
  224. package/dist/src/snapshot3.js +1 -1
  225. package/dist/src/snapshot4.js +1 -1
  226. package/dist/src/src.js +1 -1
  227. package/dist/src/src7.js +1 -1
  228. package/dist/src/src8.js +1 -1
  229. package/dist/src/stop-sequence.js +1 -1
  230. package/dist/src/string-enum.js +1 -1
  231. package/dist/src/takeover.js +1 -1
  232. package/dist/src/text-entry-focus.js +1 -1
  233. package/dist/src/tool-provider.js +1 -1
  234. package/dist/src/tool-provider2.js +1 -1
  235. package/dist/src/touch-runtime.js +1 -1
  236. package/dist/src/transport.js +1 -1
  237. package/dist/src/tv-remote.js +1 -1
  238. package/dist/src/type-text-runtime.js +1 -1
  239. package/dist/src/verified-file.js +1 -1
  240. package/dist/src/video.js +1 -1
  241. package/dist/src/viewport.js +1 -1
  242. package/dist/src/web.js +1 -1
  243. package/dist/src/web2.js +1 -1
  244. package/dist/src/webdriver-source.js +1 -1
  245. package/package.json +2 -2
  246. package/server.json +2 -2
  247. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.15.apk.sha256 +0 -1
  248. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.15.apk.sha256 +0 -1
@@ -0,0 +1 @@
1
+ 2b9f418df9ee3ddce97502fbb39dd34d06562d2747cf45e41393725a928d1007 agent-device-android-ime-helper-0.21.16.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.15",
4
- "assetName": "agent-device-android-ime-helper-0.21.15.apk",
5
- "sha256": "2f8d38e6705542b327057b059c77cf3a6358fa2fc9a53e4bf5ecfb6279c441bf",
3
+ "version": "0.21.16",
4
+ "assetName": "agent-device-android-ime-helper-0.21.16.apk",
5
+ "sha256": "2b9f418df9ee3ddce97502fbb39dd34d06562d2747cf45e41393725a928d1007",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21015,
7
+ "versionCode": 21016,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ a7db56d04eaee4009721cc4bf0947a12de4eb491b4a16e6b2bfa3b987a43012d agent-device-android-snapshot-helper-0.21.16.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.21.15",
4
- "releaseTag": "v0.21.15",
5
- "assetName": "agent-device-android-snapshot-helper-0.21.15.apk",
3
+ "version": "0.21.16",
4
+ "releaseTag": "v0.21.16",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.16.apk",
6
6
  "apkUrl": null,
7
- "sha256": "af733a540d518ede88f0336564d6d4d03b05191732130b65a3094228fa1bed54",
8
- "checksumName": "agent-device-android-snapshot-helper-0.21.15.apk.sha256",
7
+ "sha256": "a7db56d04eaee4009721cc4bf0947a12de4eb491b4a16e6b2bfa3b987a43012d",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.16.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 21015,
10
+ "versionCode": 21016,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -66,7 +66,6 @@ int main(int argc, const char *argv[]) {
66
66
  @property(nonatomic, assign) NSUInteger firstAlertActions;
67
67
  @property(nonatomic, assign) NSUInteger replacementAlertActions;
68
68
  @property(nonatomic, strong) UILabel *textEntryWriteBackStatus;
69
- @property(nonatomic, assign) NSUInteger textEntryRenderedEdits;
70
69
  @property(nonatomic, assign) NSUInteger textEntryWriteBacks;
71
70
  @property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
72
71
  @property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
@@ -187,8 +186,7 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
187
186
  - (void)updateTextEntryWriteBackStatus {
188
187
  NSTimeInterval burstSpan = self.textEntryLastEditTime - self.textEntryBurstStartTime;
189
188
  self.textEntryWriteBackStatus.text = [NSString
190
- stringWithFormat:@"edits=%lu write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
191
- (unsigned long)self.textEntryRenderedEdits,
189
+ stringWithFormat:@"write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
192
190
  (unsigned long)self.textEntryWriteBacks,
193
191
  (unsigned long)self.textEntryBurstEdits,
194
192
  (unsigned long)llround(burstSpan * 1000),
@@ -292,7 +290,6 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
292
290
  }
293
291
  } else {
294
292
  self.textEntryRenderedValue = [textField.text copy];
295
- self.textEntryRenderedEdits += 1;
296
293
  }
297
294
  [self updateTextEntryWriteBackStatus];
298
295
  }
@@ -17,21 +17,21 @@ typedef NS_ENUM(NSInteger, RunnerSynthesizedTextEntryStatus) {
17
17
 
18
18
  @interface RunnerSynthesizedTextEntry : NSObject
19
19
 
20
- // Characters per second the synthesized text-input records are typed at. Declared here, where the
21
- // typing happens, so the delivery budget that bounds a burst is charged the same pace the app sees.
22
- // The edit-acknowledge window that pace is sized for is a separate assumption about the app
23
- // (TextEntryTiming.synthesizedAcknowledgeWindowSeconds), not a value derived from this one.
24
- + (NSUInteger)typingSpeedCharactersPerSecond;
25
-
26
20
  // Synthesizes keyboard input for the current first responder without resolving an
27
21
  // XCUIElement or serializing the application's accessibility tree.
22
+ //
23
+ // `charactersPerSecond` is XCTest's `typingSpeed:` argument. The caller declares it because the
24
+ // same number is what the caller's delivery budget charges a burst (#2955): a pace owned here and
25
+ // read back from Swift would let a call site type at a speed its own budget never charged.
28
26
  + (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
29
- text:(NSString *)text;
27
+ text:(NSString *)text
28
+ charactersPerSecond:(NSUInteger)charactersPerSecond;
30
29
 
31
30
  // Replaces the current first responder's contents with one synthesized Command-A record
32
- // followed by a text-input record, typed at the bounded pace declared in the implementation.
31
+ // followed by a text-input record, typed at the pace the caller declares.
33
32
  + (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
34
- text:(NSString *)text;
33
+ text:(NSString *)text
34
+ charactersPerSecond:(NSUInteger)charactersPerSecond;
35
35
 
36
36
  @end
37
37
 
@@ -5,19 +5,10 @@
5
5
 
6
6
  static NSString *const RunnerTextSynthesisSurface = @"text";
7
7
 
8
- // XCTest's `typingSpeed:` argument is characters per second. At 60 the 11 characters of a `fill`
9
- // arrived at a fixture field across 131 ms (~13 ms per gap), which is faster than an app that owns
10
- // its field's value and re-applies it after the edit (a controlled React Native `TextInput`, an
11
- // async validator) can acknowledge: such a write lands between two characters of the burst and
12
- // erases what was typed while it was in flight, leaving a value that is stable short of the
13
- // request. 12 characters/second spaces them ~83 ms apart on average, which reduces that loss but
14
- // does not remove it: XCTest does not space the characters evenly, and two of them can reach the
15
- // app a few milliseconds apart. Against a fixture app that acknowledges each edit within 40 ms, 60
16
- // characters/second left 1 of 11 characters in 20 of 20 bursts, and this pace left 10 or 11. The
17
- // command refuses a field left short; back-pressure from the field (#2906) is what would prevent
18
- // it. The app-owned-value lane test pins the average spacing the app sees, and the delivery budget
19
- // in TextEntryTiming bounds what the pace costs a long text.
20
- static const NSUInteger RunnerTextEntryTypingSpeedCharactersPerSecond = 12;
8
+ // The pace (`typingSpeed:`) is a caller argument, not a constant here: TextEntryTiming owns it so
9
+ // the synthesized delivery budget charges the pace the app actually sees (#2955). XCTest's
10
+ // `typingSpeed:` is characters per second, and it spaces them unevenly — two characters of a paced
11
+ // burst can reach the app a few milliseconds apart at any pace.
21
12
 
22
13
  typedef id (*RunnerTextMsgSendInit)(id, SEL, NSString *);
23
14
  typedef id (*RunnerTextMsgSendInitPath)(id, SEL);
@@ -49,6 +40,7 @@ static RunnerSynthesizedTextEntryResult *RunnerTextEntryFailed(NSString *message
49
40
  static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
50
41
  id application,
51
42
  NSString *text,
43
+ NSUInteger charactersPerSecond,
52
44
  BOOL replace
53
45
  );
54
46
 
@@ -65,18 +57,16 @@ static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
65
57
 
66
58
  @implementation RunnerSynthesizedTextEntry
67
59
 
68
- + (NSUInteger)typingSpeedCharactersPerSecond {
69
- return RunnerTextEntryTypingSpeedCharactersPerSecond;
70
- }
71
-
72
60
  + (RunnerSynthesizedTextEntryResult *)synthesizeTextWithApplication:(id)application
73
- text:(NSString *)text {
74
- return RunnerSynthesizeTextWithMode(application, text, NO);
61
+ text:(NSString *)text
62
+ charactersPerSecond:(NSUInteger)charactersPerSecond {
63
+ return RunnerSynthesizeTextWithMode(application, text, charactersPerSecond, NO);
75
64
  }
76
65
 
77
66
  + (RunnerSynthesizedTextEntryResult *)replaceTextWithApplication:(id)application
78
- text:(NSString *)text {
79
- return RunnerSynthesizeTextWithMode(application, text, YES);
67
+ text:(NSString *)text
68
+ charactersPerSecond:(NSUInteger)charactersPerSecond {
69
+ return RunnerSynthesizeTextWithMode(application, text, charactersPerSecond, YES);
80
70
  }
81
71
 
82
72
  @end
@@ -84,6 +74,7 @@ static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
84
74
  static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
85
75
  id application,
86
76
  NSString *text,
77
+ NSUInteger charactersPerSecond,
87
78
  BOOL replace
88
79
  ) {
89
80
  @try {
@@ -151,7 +142,7 @@ static RunnerSynthesizedTextEntryResult *RunnerSynthesizeTextWithMode(
151
142
  bridge.typeTextSelector,
152
143
  text,
153
144
  0.0,
154
- RunnerTextEntryTypingSpeedCharactersPerSecond,
145
+ charactersPerSecond,
155
146
  YES
156
147
  );
157
148
  ((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
@@ -20,13 +20,4 @@ enum SystemSurfaceHostRegistry {
20
20
  SystemSurfaceHost(bundleId: "com.apple.SafariViewService", kind: .webAuth),
21
21
  SystemSurfaceHost(bundleId: "com.apple.PassbookUIService", kind: .payment),
22
22
  ]
23
-
24
- static func host(forBundleId bundleId: String?) -> SystemSurfaceHost? {
25
- guard let bundleId else { return nil }
26
- return hosts.first { $0.bundleId == bundleId }
27
- }
28
-
29
- static func isSystemSurfaceHost(_ bundleId: String?) -> Bool {
30
- host(forBundleId: bundleId) != nil
31
- }
32
23
  }
@@ -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