agent-device 0.21.24 → 0.21.25

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 (379) hide show
  1. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.25.apk +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.25.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.24.manifest.json → agent-device-android-ime-helper-0.21.25.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.25.apk +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.25.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.25.manifest.json +16 -0
  7. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +159 -4
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerHttpRequestFraming.swift +139 -0
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerListenerPeer.swift +41 -0
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerListenerToken.swift +25 -0
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +18 -11
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +25 -1
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +0 -3
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +1 -3
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift +59 -26
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +47 -103
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +142 -54
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +4 -1
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +21 -19
  20. package/dist/src/absence-observation-errors.js +1 -0
  21. package/dist/src/adapter.js +1 -1
  22. package/dist/src/adb-executor.js +2 -2
  23. package/dist/src/adb-failure.js +2 -2
  24. package/dist/src/agent-browser-lifecycle.js +1 -1
  25. package/dist/src/agent-browser-process-record.js +1 -1
  26. package/dist/src/agent-browser-provider.js +1 -1
  27. package/dist/src/agent-browser-tool.js +1 -1
  28. package/dist/src/agent-device-client.js +2 -2
  29. package/dist/src/android-adb.js +1 -1
  30. package/dist/src/android-input-ownership.js +1 -1
  31. package/dist/src/android-system-surface-disclosure.js +1 -0
  32. package/dist/src/android-text-entry.js +1 -0
  33. package/dist/src/android.js +1 -1
  34. package/dist/src/app-backend.js +1 -1
  35. package/dist/src/app-helpers.js +1 -1
  36. package/dist/src/app-launch.js +1 -1
  37. package/dist/src/app-lifecycle.js +4 -3
  38. package/dist/src/app-lifecycle2.js +2 -2
  39. package/dist/src/app-log-files.js +1 -1
  40. package/dist/src/app-log-reconnect.js +1 -1
  41. package/dist/src/app-log-runtime2.js +3 -3
  42. package/dist/src/app-resolution-facade.js +1 -1
  43. package/dist/src/app-resolution.js +2 -2
  44. package/dist/src/apple-runner-platform.js +1 -1
  45. package/dist/src/artifact-download.js +1 -1
  46. package/dist/src/auth.js +1 -1
  47. package/dist/src/aws-device-farm-connection.js +1 -1
  48. package/dist/src/back-mode.js +1 -1
  49. package/dist/src/boot-diagnostics.js +1 -1
  50. package/dist/src/browserstack-connection.js +1 -1
  51. package/dist/src/capture-disclosure.js +1 -0
  52. package/dist/src/cli-config.js +1 -1
  53. package/dist/src/cli-help.js +17 -14
  54. package/dist/src/cli-output.js +1 -1
  55. package/dist/src/cli-runner.js +3 -1
  56. package/dist/src/cli.js +3 -3
  57. package/dist/src/client-companion-tunnel.js +1 -1
  58. package/dist/src/client-metro.js +1 -1
  59. package/dist/src/client-types.d.ts +8 -2
  60. package/dist/src/code-signature-cache.js +1 -1
  61. package/dist/src/code-signature.js +1 -1
  62. package/dist/src/command-metadata.js +73 -1
  63. package/dist/src/command-policy.js +1 -0
  64. package/dist/src/command-schema.js +3 -3
  65. package/dist/src/command-surface.js +1 -1
  66. package/dist/src/command-tools.js +2 -2
  67. package/dist/src/connection-runtime.js +1 -1
  68. package/dist/src/connection-verification.js +1 -1
  69. package/dist/src/connection.js +3 -3
  70. package/dist/src/contracts.js +1 -1
  71. package/dist/src/daemon-client-lease-beat.js +1 -1
  72. package/dist/src/daemon-client-lifecycle.js +1 -1
  73. package/dist/src/daemon-client-liveness-probe.js +1 -1
  74. package/dist/src/daemon-policy-file.js +1 -1
  75. package/dist/src/daemon-registration-owner.js +1 -1
  76. package/dist/src/daemon-registration.js +1 -1
  77. package/dist/src/daemon-resolution.js +3 -1
  78. package/dist/src/debug-symbols-facade.js +1 -1
  79. package/dist/src/device-claim-inspection.js +1 -1
  80. package/dist/src/device-input-state.d.ts +5 -3
  81. package/dist/src/device-inventory-context.js +1 -1
  82. package/dist/src/device-readiness-runtime.js +1 -1
  83. package/dist/src/device-release.js +1 -1
  84. package/dist/src/device-shell.js +1 -1
  85. package/dist/src/device.js +3 -1
  86. package/dist/src/devicectl.js +1 -1
  87. package/dist/src/diagnostics.js +1 -1
  88. package/dist/src/doctor-facade.js +1 -1
  89. package/dist/src/durable-capture-resource.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/expo-dev-client-launch.js +1 -0
  95. package/dist/src/file.js +3 -1
  96. package/dist/src/find.js +1 -1
  97. package/dist/src/finders.js +1 -1
  98. package/dist/src/generic-settle.js +1 -1
  99. package/dist/src/generic.js +1 -1
  100. package/dist/src/gesture-plan.js +1 -1
  101. package/dist/src/gesture-runtime.js +1 -1
  102. package/dist/src/harmonyos.js +1 -1
  103. package/dist/src/hdc.js +1 -1
  104. package/dist/src/helper.js +2 -2
  105. package/dist/src/human-control.js +1 -1
  106. package/dist/src/ime-rebind.js +1 -1
  107. package/dist/src/ime-state.js +1 -1
  108. package/dist/src/index.d.ts +1 -1
  109. package/dist/src/index.js +1 -1
  110. package/dist/src/input-actions.js +1 -1
  111. package/dist/src/input-actions2.js +2 -2
  112. package/dist/src/install-source-network-transport.js +1 -1
  113. package/dist/src/install-source2.js +1 -1
  114. package/dist/src/interaction-error.js +1 -0
  115. package/dist/src/interaction-positionals.js +1 -1
  116. package/dist/src/interaction.js +1 -1
  117. package/dist/src/interaction2.js +1 -1
  118. package/dist/src/interactor-operation-catalog.js +1 -1
  119. package/dist/src/interactor.js +5 -5
  120. package/dist/src/interactor2.js +1 -1
  121. package/dist/src/internal/bin.js +1 -1
  122. package/dist/src/internal/daemon.js +5 -1
  123. package/dist/src/internal/png-worker.js +1 -1
  124. package/dist/src/inventory.js +2 -2
  125. package/dist/src/inventory6.js +1 -1
  126. package/dist/src/io.d.ts +2 -2
  127. package/dist/src/ios-client.js +1 -1
  128. package/dist/src/ios-settings.js +1 -1
  129. package/dist/src/ios-snapshot-acquisition.js +1 -1
  130. package/dist/src/ios-snapshot-adapter.js +1 -1
  131. package/dist/src/ios-snapshot-engine.js +1 -1
  132. package/dist/src/ios-snapshot-runtime.js +1 -1
  133. package/dist/src/keyed-lock.js +1 -1
  134. package/dist/src/launch-confirmation.js +1 -1
  135. package/dist/src/lease.js +1 -1
  136. package/dist/src/limrun-runtime-dependencies.js +1 -1
  137. package/dist/src/limrun.d.ts +2 -10
  138. package/dist/src/limrun.js +1 -1
  139. package/dist/src/linux.js +1 -1
  140. package/dist/src/load.js +1 -1
  141. package/dist/src/local-interactor-operation-set.js +1 -1
  142. package/dist/src/log-stream.js +9 -9
  143. package/dist/src/mechanics.js +16 -16
  144. package/dist/src/metro.js +1 -1
  145. package/dist/src/metro2.js +1 -1
  146. package/dist/src/mobile-snapshot-semantics.js +1 -1
  147. package/dist/src/observability.js +1 -1
  148. package/dist/src/open-target-resolution.js +1 -1
  149. package/dist/src/output-path.js +1 -1
  150. package/dist/src/overlay.js +1 -3
  151. package/dist/src/perf-facade.js +1 -1
  152. package/dist/src/perf-runtime-operation-builder.js +1 -1
  153. package/dist/src/perf-runtime.js +1 -1
  154. package/dist/src/physical-device-control.js +1 -1
  155. package/dist/src/platform-request-scope.js +1 -1
  156. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  157. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  158. package/dist/src/platform-runtime-apple-resources.js +1 -0
  159. package/dist/src/platform-runtime-claim-recovery.js +1 -0
  160. package/dist/src/platform-runtime-daemon-services.js +1 -0
  161. package/dist/src/platform-runtime-host.js +1 -1
  162. package/dist/src/platform-runtime-network-web-transport.js +1 -1
  163. package/dist/src/platform-runtime-open-target.js +1 -1
  164. package/dist/src/platform-runtime-operation-host.js +4 -4
  165. package/dist/src/platform-runtime-runtime-hints.js +2 -2
  166. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  167. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  168. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  169. package/dist/src/platform-runtime-screen-recording-harmony-host.js +1 -1
  170. package/dist/src/platform-runtime-screen-recording-web-host.js +1 -1
  171. package/dist/src/platform-runtime-unavailable.js +1 -1
  172. package/dist/src/platform-runtime.js +1 -1
  173. package/dist/src/platform-runtime2.js +1 -1
  174. package/dist/src/plugin.js +1 -1
  175. package/dist/src/png-crop-bytes.js +1 -1
  176. package/dist/src/png-resize.js +1 -0
  177. package/dist/src/png-worker-client.js +1 -1
  178. package/dist/src/png-worker-contract.js +1 -1
  179. package/dist/src/process-entry.js +1 -1
  180. package/dist/src/process.js +1 -1
  181. package/dist/src/provider-credential-fingerprint.js +1 -1
  182. package/dist/src/provider-device-admission.js +1 -1
  183. package/dist/src/provider-policy.js +1 -1
  184. package/dist/src/provider-webdriver.js +1 -1
  185. package/dist/src/proxy.js +2 -2
  186. package/dist/src/quality-warnings.js +1 -1
  187. package/dist/src/react-native-overlay2.js +3 -0
  188. package/dist/src/react-native.js +1 -1
  189. package/dist/src/record-runtime.js +1 -1
  190. package/dist/src/record-trace.js +1 -1
  191. package/dist/src/recording.js +2 -2
  192. package/dist/src/register-builtins.js +1 -1
  193. package/dist/src/remote-config-core.js +1 -1
  194. package/dist/src/replay-backend-registry.js +1 -0
  195. package/dist/src/replay-divergence.js +1 -1
  196. package/dist/src/replay-maestro-backend.js +117 -0
  197. package/dist/src/replay-script-selection.js +1 -0
  198. package/dist/src/replay.js +1 -3
  199. package/dist/src/replay2.js +1 -0
  200. package/dist/src/reporting.js +3 -3
  201. package/dist/src/request-cancel.js +1 -1
  202. package/dist/src/request-providers.js +1 -1
  203. package/dist/src/request-runtime-binding.js +1 -1
  204. package/dist/src/result-serialization.js +1 -1
  205. package/dist/src/retry.js +1 -1
  206. package/dist/src/runner-cache-retention.js +1 -1
  207. package/dist/src/runner-cache-trim.js +1 -1
  208. package/dist/src/runner-client.js +1 -1
  209. package/dist/src/runner-dev-tools-security.js +1 -1
  210. package/dist/src/runner-device-readiness.js +1 -1
  211. package/dist/src/runner-device-set.js +1 -1
  212. package/dist/src/runner-error-classification.js +3 -3
  213. package/dist/src/runner-exchange.js +1 -1
  214. package/dist/src/runner-lease.js +1 -1
  215. package/dist/src/runner-operations-facade.js +1 -1
  216. package/dist/src/runner-retention.js +1 -0
  217. package/dist/src/runner-start-budget.js +1 -1
  218. package/dist/src/runner-startup-transport.js +1 -0
  219. package/dist/src/runner-toolchain-probe.js +1 -1
  220. package/dist/src/runner.js +2 -1
  221. package/dist/src/runtime-command-surface.js +1 -0
  222. package/dist/src/runtime-deployment.js +1 -1
  223. package/dist/src/runtime-factory.js +1 -0
  224. package/dist/src/runtime-session.js +1 -0
  225. package/dist/src/runtime-transport-hints.js +1 -1
  226. package/dist/src/runtime.js +2 -1
  227. package/dist/src/runtime10.js +2 -1
  228. package/dist/src/runtime2.js +5 -2
  229. package/dist/src/runtime3.js +1 -5
  230. package/dist/src/runtime4.js +5 -1
  231. package/dist/src/runtime5.js +1 -5
  232. package/dist/src/runtime6.js +1 -1
  233. package/dist/src/runtime7.js +1 -1
  234. package/dist/src/runtime8.js +1 -1
  235. package/dist/src/runtime9.js +1 -1
  236. package/dist/src/screenshot-image.js +1 -1
  237. package/dist/src/screenshot-runtime.js +1 -0
  238. package/dist/src/screenshot3.js +5 -5
  239. package/dist/src/sdk-batch-runner.js +2 -2
  240. package/dist/src/sdk-batch.js +1 -1
  241. package/dist/src/sdk-contracts.js +2 -2
  242. package/dist/src/sdk-finders.js +1 -1
  243. package/dist/src/sdk-io.d.ts +4 -2
  244. package/dist/src/sdk-selectors.d.ts +4 -5
  245. package/dist/src/sdk-selectors.js +1 -1
  246. package/dist/src/selection.js +0 -0
  247. package/dist/src/selector-pipeline-policy.js +1 -1
  248. package/dist/src/selector-runtime.js +1 -1
  249. package/dist/src/selectors.js +1 -1
  250. package/dist/src/server.js +2 -2
  251. package/dist/src/session-allocation.js +1 -1
  252. package/dist/src/session-replay-runtime-failure-response.js +1 -0
  253. package/dist/src/session-runtime-admission.js +1 -0
  254. package/dist/src/session.js +1 -1
  255. package/dist/src/session2.js +5 -5
  256. package/dist/src/settings.js +1 -1
  257. package/dist/src/settings2.js +1 -1
  258. package/dist/src/simctl-settings.js +1 -1
  259. package/dist/src/simulator.js +1 -1
  260. package/dist/src/snapshot-chrome.js +1 -0
  261. package/dist/src/snapshot-desktop-projection.js +1 -1
  262. package/dist/src/snapshot-evidence.js +1 -0
  263. package/dist/src/snapshot-node-lookup.js +1 -0
  264. package/dist/src/snapshot-occlusion.js +1 -0
  265. package/dist/src/snapshot-quality-verdict.js +1 -0
  266. package/dist/src/snapshot-runtime-binding.js +1 -1
  267. package/dist/src/snapshot-runtime.js +1 -1
  268. package/dist/src/snapshot-state.js +1 -1
  269. package/dist/src/snapshot.js +1 -1
  270. package/dist/src/snapshot2.js +1 -1
  271. package/dist/src/snapshot3.js +1 -1
  272. package/dist/src/src.js +5 -1
  273. package/dist/src/src2.js +1 -1
  274. package/dist/src/src3.js +2 -1
  275. package/dist/src/store.js +1 -1
  276. package/dist/src/surface-snapshot2.js +1 -1
  277. package/dist/src/target-evidence-tree.js +1 -0
  278. package/dist/src/target-evidence.js +1 -1
  279. package/dist/src/text-entry-focus.js +1 -1
  280. package/dist/src/tool-provider2.js +1 -1
  281. package/dist/src/touch-runtime.js +1 -1
  282. package/dist/src/touch-runtime2.js +1 -1
  283. package/dist/src/tree.js +1 -1
  284. package/dist/src/tv-remote.js +1 -1
  285. package/dist/src/type-text-runtime.js +1 -1
  286. package/dist/src/undici.js +8 -8
  287. package/dist/src/update-check.js +1 -1
  288. package/dist/src/vars.js +2 -0
  289. package/dist/src/version.js +1 -1
  290. package/dist/src/video.js +2 -2
  291. package/dist/src/wait.js +1 -0
  292. package/dist/src/web.js +1 -1
  293. package/dist/src/web2.js +1 -1
  294. package/package.json +9 -11
  295. package/server.json +2 -2
  296. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.24.apk +0 -0
  297. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.24.apk.sha256 +0 -1
  298. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.24.apk +0 -0
  299. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.24.apk.sha256 +0 -1
  300. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.24.manifest.json +0 -16
  301. package/dist/src/adb-transport.js +0 -1
  302. package/dist/src/alert-runtime.js +0 -1
  303. package/dist/src/app-deployment-resolution.js +0 -2
  304. package/dist/src/app-inventory-contract.js +0 -1
  305. package/dist/src/app-log-session-resource.js +0 -1
  306. package/dist/src/application-lifecycle-runtime.js +0 -1
  307. package/dist/src/auth-session.js +0 -1
  308. package/dist/src/browserstack-device-features.js +0 -1
  309. package/dist/src/browserstack.js +0 -1
  310. package/dist/src/cache.js +0 -1
  311. package/dist/src/capture.js +0 -1
  312. package/dist/src/catalog.js +0 -1
  313. package/dist/src/click-button.js +0 -1
  314. package/dist/src/client-metro-companion.js +0 -1
  315. package/dist/src/client-options.js +0 -3
  316. package/dist/src/daemon-client-transport.js +0 -1
  317. package/dist/src/daemon-http.js +0 -1
  318. package/dist/src/device-rotation.js +0 -1
  319. package/dist/src/device-session.js +0 -3
  320. package/dist/src/device2.js +0 -1
  321. package/dist/src/device3.js +0 -3
  322. package/dist/src/dispatch-resolve.js +0 -4
  323. package/dist/src/format.js +0 -2
  324. package/dist/src/gesture-plan-types.js +0 -1
  325. package/dist/src/host.js +0 -1
  326. package/dist/src/ime-settings-record.js +0 -1
  327. package/dist/src/interaction-ref-policy.js +0 -1
  328. package/dist/src/interactor-operation-binding.js +0 -1
  329. package/dist/src/keyboard-actions.js +0 -1
  330. package/dist/src/lease-context.js +0 -1
  331. package/dist/src/lease-registry-scope.js +0 -1
  332. package/dist/src/lease-scope.js +0 -1
  333. package/dist/src/managed-device-scope.js +0 -1
  334. package/dist/src/manifest.js +0 -1
  335. package/dist/src/observe-until.js +0 -1
  336. package/dist/src/output-common.js +0 -5
  337. package/dist/src/owner-identity.js +0 -3
  338. package/dist/src/path-resolution.js +0 -1
  339. package/dist/src/perf-runtime-plan.js +0 -1
  340. package/dist/src/platform-runtime-app-log-process.js +0 -1
  341. package/dist/src/platform-runtime-managed-owner.js +0 -1
  342. package/dist/src/platform-runtime-screen-recording-process-host.js +0 -1
  343. package/dist/src/png.js +0 -1
  344. package/dist/src/provider-session-ownership.js +0 -2
  345. package/dist/src/providers.js +0 -1
  346. package/dist/src/registry.js +0 -68
  347. package/dist/src/remote-connection-state.js +0 -1
  348. package/dist/src/request-admission.js +0 -1
  349. package/dist/src/request-cancellation.js +0 -1
  350. package/dist/src/request-progress-protocol.js +0 -1
  351. package/dist/src/runner-cache-metadata.js +0 -2
  352. package/dist/src/runner-disposal.js +0 -1
  353. package/dist/src/runner-session-types.js +0 -1
  354. package/dist/src/runtime-admission.js +0 -1
  355. package/dist/src/runtime11.js +0 -2
  356. package/dist/src/screen-recording-resource-recovery.js +0 -1
  357. package/dist/src/screenshot-result.js +0 -1
  358. package/dist/src/script.js +0 -1
  359. package/dist/src/scroll-command.js +0 -1
  360. package/dist/src/scroll-gesture.js +0 -1
  361. package/dist/src/scroll-indicator.js +0 -1
  362. package/dist/src/session-artifact-paths.js +0 -1
  363. package/dist/src/session-paths.js +0 -1
  364. package/dist/src/session-repair-tombstone.js +0 -1
  365. package/dist/src/session-snapshot.js +0 -1
  366. package/dist/src/snapshot-private-evidence.js +0 -1
  367. package/dist/src/snapshot4.js +0 -1
  368. package/dist/src/src4.js +0 -1
  369. package/dist/src/src5.js +0 -1
  370. package/dist/src/src6.js +0 -1
  371. package/dist/src/src7.js +0 -2
  372. package/dist/src/src8.js +0 -115
  373. package/dist/src/string-enum.js +0 -1
  374. package/dist/src/success-text.js +0 -1
  375. package/dist/src/transport.js +0 -3
  376. package/dist/src/ttl-memo.js +0 -1
  377. package/dist/src/verified-file.js +0 -1
  378. package/dist/src/window-state.js +0 -1
  379. /package/dist/src/{doctor-output.js → doctor-lines.js} +0 -0
@@ -0,0 +1 @@
1
+ 2b1c6a028b28e426c4bed80449b6dd1eff69985f1741a58d033e30f8b9fa5bf1 agent-device-android-ime-helper-0.21.25.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.24",
4
- "assetName": "agent-device-android-ime-helper-0.21.24.apk",
5
- "sha256": "ca5c3b48c9da40f1fcddd785f1129906125e4880549cfacb1b63ff70848aed1e",
3
+ "version": "0.21.25",
4
+ "assetName": "agent-device-android-ime-helper-0.21.25.apk",
5
+ "sha256": "2b1c6a028b28e426c4bed80449b6dd1eff69985f1741a58d033e30f8b9fa5bf1",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21024,
7
+ "versionCode": 21025,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ 79175080eb71f444224c844a599e75e839d6456790eb96d175fc887f85237a12 agent-device-android-snapshot-helper-0.21.25.apk
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "android-snapshot-helper",
3
+ "version": "0.21.25",
4
+ "releaseTag": "v0.21.25",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.25.apk",
6
+ "apkUrl": "https://github.com/callstack/agent-device/releases/download/v0.21.25/agent-device-android-snapshot-helper-0.21.25.apk",
7
+ "sha256": "79175080eb71f444224c844a599e75e839d6456790eb96d175fc887f85237a12",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.25.apk.sha256",
9
+ "packageName": "com.callstack.agentdevice.snapshothelper",
10
+ "versionCode": 21025,
11
+ "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
+ "minSdk": 23,
13
+ "targetSdk": 36,
14
+ "outputFormat": "uiautomator-xml",
15
+ "statusProtocol": "android-snapshot-helper-v1"
16
+ }
@@ -71,6 +71,7 @@ int main(int argc, const char *argv[]) {
71
71
  // took focus from an input that has stopped answering the accessibility query that found it.
72
72
  @property(nonatomic, strong) UILabel *textEntryFocusWitness;
73
73
  @property(nonatomic, assign) NSUInteger textEntryWriteBacks;
74
+ @property(nonatomic, assign) NSUInteger textEntryTotalEdits;
74
75
  @property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
75
76
  @property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
76
77
  @property(nonatomic, assign) NSTimeInterval textEntryBurstStartTime;
@@ -97,6 +98,122 @@ int main(int argc, const char *argv[]) {
97
98
  return [NSString stringWithFormat:@"%lu of 6 digits", (unsigned long)self.text.length];
98
99
  }
99
100
  @end
101
+
102
+ // A field that reformats its own text on every edit into `## ### ## ##` digit groups, the way a
103
+ // digit-grouping formatter in a Flutter or React Native input does. The accessibility value is
104
+ // what the user sees — the formatted string — so it never equals the digits a `fill` sent (#2634).
105
+ // The field owns the delegate it installs, keeping the formatter out of the view controller's
106
+ // own delegate callbacks.
107
+ @interface AgentDeviceDigitGroupingTextField : UITextField <UITextFieldDelegate>
108
+ @end
109
+
110
+ @interface AgentDeviceCurrencyTextField : UITextField <UITextFieldDelegate>
111
+ @property(nonatomic, strong) UILabel *entryStatus;
112
+ @property(nonatomic, assign) NSUInteger insertedDigits;
113
+ @end
114
+
115
+ @interface AgentDeviceSubmitClearingTextField : UITextField <UITextFieldDelegate>
116
+ @property(nonatomic, strong) UILabel *entryStatus;
117
+ @property(nonatomic, assign) NSUInteger submits;
118
+ @end
119
+
120
+ static NSString *AgentDeviceFormatGroupedDigits(NSString *raw) {
121
+ NSMutableString *digits = [NSMutableString string];
122
+ NSCharacterSet *nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
123
+ for (NSString *chunk in [raw componentsSeparatedByCharactersInSet:nonDigits]) {
124
+ [digits appendString:chunk];
125
+ }
126
+ NSMutableArray<NSNumber *> *groupWidths = [NSMutableArray arrayWithArray:@[@2, @3, @2, @2]];
127
+ NSMutableString *formatted = [NSMutableString string];
128
+ NSUInteger cursor = 0;
129
+ for (NSNumber *width in groupWidths) {
130
+ if (cursor >= digits.length) {
131
+ break;
132
+ }
133
+ if (formatted.length > 0) {
134
+ [formatted appendString:@" "];
135
+ }
136
+ NSUInteger end = MIN(cursor + width.unsignedIntegerValue, digits.length);
137
+ [formatted appendString:[digits substringWithRange:NSMakeRange(cursor, end - cursor)]];
138
+ cursor = end;
139
+ }
140
+ // Digits past the last group keep grouping by threes rather than vanishing from the field.
141
+ while (cursor < digits.length) {
142
+ NSUInteger end = MIN(cursor + 3, digits.length);
143
+ [formatted appendString:[NSString stringWithFormat:@" %@", [digits substringWithRange:NSMakeRange(cursor, end - cursor)]]];
144
+ cursor = end;
145
+ }
146
+ return formatted;
147
+ }
148
+
149
+ static NSUInteger AgentDeviceDigitCount(NSString *text, NSUInteger limit) {
150
+ NSUInteger count = 0;
151
+ for (NSUInteger index = 0; index < limit && index < text.length; index++) {
152
+ if ([NSCharacterSet.decimalDigitCharacterSet characterIsMember:[text characterAtIndex:index]]) {
153
+ count += 1;
154
+ }
155
+ }
156
+ return count;
157
+ }
158
+
159
+ @implementation AgentDeviceDigitGroupingTextField
160
+ - (BOOL)textField:(UITextField *)textField
161
+ shouldChangeCharactersInRange:(NSRange)range
162
+ replacementString:(NSString *)string {
163
+ NSString *combined = [textField.text stringByReplacingCharactersInRange:range withString:string];
164
+ NSString *formatted = AgentDeviceFormatGroupedDigits(combined);
165
+ textField.text = formatted;
166
+ // Assigning `text` drops the caret at the end, which a real grouping formatter does not do:
167
+ // a mid-string edit must keep editing where the user put the caret. The digits before the
168
+ // caret survive the reformat unchanged, so the caret belongs before the digit that follows
169
+ // them — past the separators the grouping inserted behind them — and at the end when it
170
+ // followed all of them. Typed digits count as before the caret; a delete does not move it.
171
+ NSUInteger digitsBeforeCaret =
172
+ AgentDeviceDigitCount(textField.text, range.location) + AgentDeviceDigitCount(string, string.length);
173
+ NSUInteger caretOffset = formatted.length;
174
+ NSUInteger seen = 0;
175
+ for (NSUInteger index = 0; index < formatted.length; index++) {
176
+ if (![NSCharacterSet.decimalDigitCharacterSet characterIsMember:[formatted characterAtIndex:index]]) {
177
+ continue;
178
+ }
179
+ seen += 1;
180
+ if (seen == digitsBeforeCaret + 1) {
181
+ caretOffset = index;
182
+ break;
183
+ }
184
+ }
185
+ textField.selectedTextRange = [textField textRangeFromPosition:[textField positionFromPosition:textField.beginningOfDocument offset:caretOffset]
186
+ toPosition:[textField positionFromPosition:textField.beginningOfDocument offset:caretOffset]];
187
+ return NO;
188
+ }
189
+ @end
190
+
191
+ @implementation AgentDeviceCurrencyTextField
192
+ - (BOOL)textField:(UITextField *)textField
193
+ shouldChangeCharactersInRange:(NSRange)range
194
+ replacementString:(NSString *)string {
195
+ NSString *combined = [textField.text stringByReplacingCharactersInRange:range withString:string];
196
+ NSMutableString *digits = [NSMutableString string];
197
+ for (NSString *part in [combined componentsSeparatedByCharactersInSet:NSCharacterSet.decimalDigitCharacterSet.invertedSet]) {
198
+ [digits appendString:part];
199
+ }
200
+ unsigned long long cents = digits.longLongValue;
201
+ textField.text = [NSString stringWithFormat:@"$%llu.%02llu", cents / 100, cents % 100];
202
+ self.insertedDigits += AgentDeviceDigitCount(string, string.length);
203
+ self.entryStatus.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.insertedDigits];
204
+ textField.selectedTextRange = [textField textRangeFromPosition:textField.endOfDocument toPosition:textField.endOfDocument];
205
+ return NO;
206
+ }
207
+ @end
208
+
209
+ @implementation AgentDeviceSubmitClearingTextField
210
+ - (BOOL)textFieldShouldReturn:(UITextField *)textField {
211
+ self.submits += 1;
212
+ self.entryStatus.text = [NSString stringWithFormat:@"%lu", (unsigned long)self.submits];
213
+ textField.text = @"";
214
+ return YES;
215
+ }
216
+ @end
100
217
  #endif
101
218
 
102
219
  @implementation AgentDeviceRunnerViewController
@@ -202,11 +319,12 @@ static NSTimeInterval AgentDeviceAlertActivationBusyWindow(void) {
202
319
  - (void)updateTextEntryWriteBackStatus {
203
320
  NSTimeInterval burstSpan = self.textEntryLastEditTime - self.textEntryBurstStartTime;
204
321
  self.textEntryWriteBackStatus.text = [NSString
205
- stringWithFormat:@"write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu",
322
+ stringWithFormat:@"write-backs=%lu burst-edits=%lu burst-ms=%lu min-gap-ms=%lu total-edits=%lu",
206
323
  (unsigned long)self.textEntryWriteBacks,
207
324
  (unsigned long)self.textEntryBurstEdits,
208
325
  (unsigned long)llround(burstSpan * 1000),
209
- (unsigned long)llround(self.textEntryBurstMinGap * 1000)];
326
+ (unsigned long)llround(self.textEntryBurstMinGap * 1000),
327
+ (unsigned long)self.textEntryTotalEdits];
210
328
  }
211
329
 
212
330
  - (void)presentAlertFixtureReplacement:(BOOL)replacement {
@@ -292,6 +410,7 @@ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
292
410
  // typed faster than the app renders loses the characters that arrived while a render was in
293
411
  // flight, and the field settles stable short of the request.
294
412
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-app-owned-value"]) {
413
+ self.textEntryTotalEdits += 1;
295
414
  NSTimeInterval now = NSProcessInfo.processInfo.systemUptime;
296
415
  NSTimeInterval gap = now - self.textEntryLastEditTime;
297
416
  BOOL overtookARender = self.textEntryRenderedValue != nil && gap < self.textEntryAcknowledgeWindowSeconds;
@@ -408,8 +527,28 @@ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
408
527
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-regression"]) {
409
528
  BOOL digitCountValue =
410
529
  [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-digit-count-value"];
411
- UITextField *textField =
412
- digitCountValue ? [[AgentDeviceDigitCountTextField alloc] init] : [[UITextField alloc] init];
530
+ BOOL digitGroupingValue =
531
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-digit-grouping-value"];
532
+ BOOL currencyValue =
533
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-currency-value"];
534
+ BOOL submitClears =
535
+ [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-submit-clears"];
536
+ UITextField *textField;
537
+ if (digitCountValue) {
538
+ textField = [[AgentDeviceDigitCountTextField alloc] init];
539
+ } else if (digitGroupingValue) {
540
+ textField = [[AgentDeviceDigitGroupingTextField alloc] init];
541
+ } else if (currencyValue) {
542
+ textField = [[AgentDeviceCurrencyTextField alloc] init];
543
+ textField.text = @"$0.00";
544
+ } else if (submitClears) {
545
+ textField = [[AgentDeviceSubmitClearingTextField alloc] init];
546
+ } else {
547
+ textField = [[UITextField alloc] init];
548
+ }
549
+ if (digitGroupingValue || currencyValue || submitClears) {
550
+ textField.delegate = (id<UITextFieldDelegate>)textField;
551
+ }
413
552
  if (![NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
414
553
  textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
415
554
  }
@@ -434,6 +573,22 @@ static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
434
573
  [textField.widthAnchor constraintEqualToConstant:240],
435
574
  [textField.heightAnchor constraintEqualToConstant:AgentDeviceTextEntryFieldHeight],
436
575
  ]];
576
+ if (currencyValue || submitClears) {
577
+ UILabel *entryStatus = [[UILabel alloc] init];
578
+ entryStatus.accessibilityIdentifier = @"agent-device-text-entry-events";
579
+ entryStatus.text = @"0";
580
+ entryStatus.translatesAutoresizingMaskIntoConstraints = NO;
581
+ [self.view addSubview:entryStatus];
582
+ [NSLayoutConstraint activateConstraints:@[
583
+ [entryStatus.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
584
+ [entryStatus.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:12],
585
+ ]];
586
+ if (currencyValue) {
587
+ ((AgentDeviceCurrencyTextField *)textField).entryStatus = entryStatus;
588
+ } else {
589
+ ((AgentDeviceSubmitClearingTextField *)textField).entryStatus = entryStatus;
590
+ }
591
+ }
437
592
  if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unqueryable-on-focus"]) {
438
593
  self.textEntryFocusWitness = [[UILabel alloc] init];
439
594
  self.textEntryFocusWitness.accessibilityIdentifier = @"agent-device-text-entry-focus";
@@ -0,0 +1,139 @@
1
+ import Foundation
2
+
3
+
4
+
5
+ enum RunnerHttpRequestRejection: Equatable {
6
+ case malformed(String)
7
+ case headTooLarge
8
+ case unauthorized
9
+ case transferEncodingUnsupported
10
+ case lengthRequired
11
+ case bodyTooLarge
12
+ case methodNotAllowed
13
+ case notFound
14
+ case timedOut
15
+
16
+ var status: Int {
17
+ switch self {
18
+ case .malformed, .transferEncodingUnsupported: return 400
19
+ case .unauthorized: return 401
20
+ case .notFound: return 404
21
+ case .methodNotAllowed: return 405
22
+ case .timedOut: return 408
23
+ case .lengthRequired: return 411
24
+ case .bodyTooLarge: return 413
25
+ case .headTooLarge: return 431
26
+ }
27
+ }
28
+
29
+ var code: String {
30
+ self == .unauthorized ? "UNAUTHORIZED" : "INVALID_ARGS"
31
+ }
32
+
33
+ var message: String {
34
+ switch self {
35
+ case .malformed(let part): return "runner request is malformed: \(part)"
36
+ case .headTooLarge:
37
+ return "runner request head exceeds \(RunnerHttpRequestFraming.maxHeadBytes) bytes"
38
+ case .unauthorized: return "runner request is missing a valid listener token"
39
+ case .transferEncodingUnsupported: return "runner requests must not use Transfer-Encoding"
40
+ case .lengthRequired: return "runner requests must declare one Content-Length"
41
+ case .bodyTooLarge:
42
+ return "runner request body exceeds \(RunnerHttpRequestFraming.maxBodyBytes) bytes"
43
+ case .methodNotAllowed: return "runner accepts only POST"
44
+ case .notFound: return "runner serves only \(RunnerHttpRequestFraming.commandPath)"
45
+ case .timedOut: return "runner request did not arrive before its deadline"
46
+ }
47
+ }
48
+ }
49
+
50
+
51
+ struct RunnerHttpAdmittedHead: Equatable {
52
+ let bodyOffset: Int
53
+ let contentLength: Int
54
+ }
55
+
56
+ enum RunnerHttpHeadVerdict: Equatable {
57
+ case incomplete
58
+ case admitted(RunnerHttpAdmittedHead)
59
+ case rejected(RunnerHttpRequestRejection)
60
+ }
61
+
62
+
63
+
64
+ enum RunnerHttpRequestFraming {
65
+ static let commandPath = "/command"
66
+ static let maxHeadBytes = 16 * 1024
67
+ static let maxBodyBytes = 2 * 1024 * 1024
68
+ private static let headTerminator = Data("\r\n\r\n".utf8)
69
+
70
+
71
+
72
+ static func admitHead(
73
+ _ buffer: Data,
74
+ token: RunnerListenerToken,
75
+ alreadyScanned: Int = 0
76
+ ) -> RunnerHttpHeadVerdict {
77
+ let searchStart = buffer.startIndex + max(0, alreadyScanned - (headTerminator.count - 1))
78
+ guard let terminator = buffer.range(of: headTerminator, in: searchStart..<buffer.endIndex) else {
79
+ return buffer.count > maxHeadBytes ? .rejected(.headTooLarge) : .incomplete
80
+ }
81
+ if terminator.lowerBound - buffer.startIndex > maxHeadBytes { return .rejected(.headTooLarge) }
82
+ guard
83
+ let head = String(data: buffer[buffer.startIndex..<terminator.lowerBound], encoding: .utf8)
84
+ else {
85
+ return .rejected(.malformed("head is not UTF-8"))
86
+ }
87
+ var lines = head.components(separatedBy: "\r\n")
88
+ let requestLine = lines.removeFirst().split(separator: " ", omittingEmptySubsequences: false)
89
+ guard requestLine.count == 3, requestLine[2] == "HTTP/1.1" || requestLine[2] == "HTTP/1.0" else {
90
+ return .rejected(.malformed("request line"))
91
+ }
92
+ var headers: [String: [String]] = [:]
93
+ for line in lines {
94
+ guard let colon = line.firstIndex(of: ":") else {
95
+ return .rejected(.malformed("header line"))
96
+ }
97
+ let name = line[line.startIndex..<colon]
98
+ guard !name.isEmpty, name.allSatisfy(isHeaderNameCharacter) else {
99
+ return .rejected(.malformed("header name"))
100
+ }
101
+ let value = line[line.index(after: colon)...].trimmingCharacters(in: CharacterSet(charactersIn: " \t"))
102
+ headers[name.lowercased(), default: []].append(value)
103
+ }
104
+
105
+ guard let presented = headers[RunnerListenerToken.requestHeaderName], presented.count == 1,
106
+ token.matches(presented[0])
107
+ else {
108
+ return .rejected(.unauthorized)
109
+ }
110
+ guard requestLine[0] == "POST" else { return .rejected(.methodNotAllowed) }
111
+ guard requestLine[1] == Substring(commandPath) else { return .rejected(.notFound) }
112
+ if headers["transfer-encoding"] != nil { return .rejected(.transferEncodingUnsupported) }
113
+ guard let lengths = headers["content-length"], lengths.count == 1 else {
114
+ return .rejected(.lengthRequired)
115
+ }
116
+ let declared = lengths[0]
117
+ guard !declared.isEmpty, declared.utf8.allSatisfy({ $0 >= 0x30 && $0 <= 0x39 }) else {
118
+ return .rejected(.malformed("Content-Length"))
119
+ }
120
+ guard declared.utf8.count <= 10, let contentLength = Int(declared), contentLength <= maxBodyBytes
121
+ else {
122
+ return .rejected(.bodyTooLarge)
123
+ }
124
+ return .admitted(
125
+ RunnerHttpAdmittedHead(
126
+ bodyOffset: terminator.upperBound - buffer.startIndex,
127
+ contentLength: contentLength
128
+ )
129
+ )
130
+ }
131
+
132
+ private static func isHeaderNameCharacter(_ character: Character) -> Bool {
133
+ guard let ascii = character.asciiValue else { return false }
134
+ switch ascii {
135
+ case 0x30...0x39, 0x41...0x5A, 0x61...0x7A: return true
136
+ default: return "!#$%&'*+-.^_`|~".utf8.contains(ascii)
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,41 @@
1
+ import Network
2
+
3
+
4
+
5
+
6
+
7
+
8
+ enum RunnerListenerPeer: Equatable {
9
+
10
+
11
+ case loopback
12
+ case tunnel
13
+ case directNetwork
14
+
15
+ static func of(_ connection: NWConnection) -> RunnerListenerPeer {
16
+ classify(
17
+ isLoopbackAddress: isLoopback(connection.endpoint),
18
+ interfaceTypes: connection.currentPath?.availableInterfaces.map(\.type) ?? []
19
+ )
20
+ }
21
+
22
+ static func classify(
23
+ isLoopbackAddress: Bool,
24
+ interfaceTypes: [NWInterface.InterfaceType]
25
+ ) -> RunnerListenerPeer {
26
+ if isLoopbackAddress { return .loopback }
27
+
28
+
29
+ if !interfaceTypes.isEmpty && interfaceTypes.allSatisfy({ $0 == .other }) { return .tunnel }
30
+ return .directNetwork
31
+ }
32
+
33
+ static func isLoopback(_ endpoint: NWEndpoint) -> Bool {
34
+ guard case .hostPort(let host, _) = endpoint else { return false }
35
+ switch host {
36
+ case .ipv4(let address): return address.isLoopback
37
+ case .ipv6(let address): return address.isLoopback || (address.asIPv4?.isLoopback ?? false)
38
+ default: return false
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,25 @@
1
+ import Foundation
2
+
3
+
4
+ struct RunnerListenerToken {
5
+ static let requestHeaderName = "x-agent-device-runner-token"
6
+ static let minimumByteCount = 32
7
+
8
+ private let expected: [UInt8]
9
+
10
+ init?(_ token: String?) {
11
+ guard let token, token.utf8.count >= Self.minimumByteCount else { return nil }
12
+ expected = Array(token.utf8)
13
+ }
14
+
15
+
16
+ func matches(_ presented: String) -> Bool {
17
+ let candidate = Array(presented.utf8)
18
+ var difference = candidate.count ^ expected.count
19
+ for index in 0..<expected.count {
20
+ let byte = index < candidate.count ? candidate[index] : 0
21
+ difference |= Int(byte ^ expected[index])
22
+ }
23
+ return difference == 0
24
+ }
25
+ }
@@ -1,18 +1,25 @@
1
1
  import Foundation
2
+ import Network
2
3
 
3
4
 
4
5
 
5
- enum RunnerEnv {
6
- static func resolvePort() -> UInt16 {
7
- if let env = ProcessInfo.processInfo.environment["AGENT_DEVICE_RUNNER_PORT"], let port = UInt16(env) {
8
- return port
9
- }
10
- for arg in CommandLine.arguments {
11
- if arg.hasPrefix("AGENT_DEVICE_RUNNER_PORT=") {
12
- let value = arg.replacingOccurrences(of: "AGENT_DEVICE_RUNNER_PORT=", with: "")
13
- if let port = UInt16(value) { return port }
14
- }
6
+
7
+
8
+
9
+ struct RunnerListenerConfiguration {
10
+ let port: NWEndpoint.Port
11
+ let token: RunnerListenerToken
12
+
13
+ static func fromEnvironment(
14
+ _ environment: [String: String] = ProcessInfo.processInfo.environment
15
+ ) -> RunnerListenerConfiguration? {
16
+ guard
17
+ let rawPort = environment["AGENT_DEVICE_RUNNER_PORT"].flatMap({ UInt16($0) }), rawPort > 0,
18
+ let port = NWEndpoint.Port(rawValue: rawPort),
19
+ let token = RunnerListenerToken(environment["AGENT_DEVICE_RUNNER_TOKEN"])
20
+ else {
21
+ return nil
15
22
  }
16
- return 0
23
+ return RunnerListenerConfiguration(port: port, token: token)
17
24
  }
18
25
  }
@@ -3,6 +3,8 @@ import XCTest
3
3
 
4
4
  extension RunnerTests {
5
5
  static let navigationBackKeywords = ["back", "close", "cancel"]
6
+ static let systemBackButtonIdentifier = "BackButton"
7
+ static let floatingBarBackControlMaxYFraction: CGFloat = 0.4
6
8
  static let navigationFallbackVerificationDelay: TimeInterval = 0.25
7
9
 
8
10
 
@@ -88,7 +90,11 @@ extension RunnerTests {
88
90
  let candidates = app.buttons.matching(Self.navigationBackPredicate()).allElementsBoundByIndex.compactMap {
89
91
  element -> (XCUIElement, Int)? in
90
92
  guard element.exists, element.isHittable else { return nil }
91
- guard Self.isTopNavigationControlFrame(element.frame, in: frame) else { return nil }
93
+ guard Self.isTopNavigationBackCandidateFrame(
94
+ element.frame,
95
+ in: frame,
96
+ identifier: element.identifier
97
+ ) else { return nil }
92
98
  guard let rank = Self.navigationBackControlRank(
93
99
  label: element.label,
94
100
  identifier: element.identifier
@@ -132,6 +138,24 @@ extension RunnerTests {
132
138
  return candidate.midY >= window.minY && candidate.midY <= maxY
133
139
  }
134
140
 
141
+
142
+
143
+
144
+ static func isTopNavigationBackCandidateFrame(
145
+ _ candidate: CGRect,
146
+ in window: CGRect,
147
+ identifier: String
148
+ ) -> Bool {
149
+ if isTopNavigationControlFrame(candidate, in: window) { return true }
150
+ guard identifier == systemBackButtonIdentifier,
151
+ SnapshotGeometry.isPositiveFinite(candidate), SnapshotGeometry.isPositiveFinite(window)
152
+ else {
153
+ return false
154
+ }
155
+ let maxY = window.minY + window.height * floatingBarBackControlMaxYFraction
156
+ return candidate.midY >= window.minY && candidate.midY <= maxY
157
+ }
158
+
135
159
  static func topLeadingNavigationFallbackPoint(in frame: CGRect) -> CGPoint? {
136
160
  guard SnapshotGeometry.isPositiveFinite(frame) else {
137
161
  return nil
@@ -524,9 +524,6 @@ extension RunnerTests {
524
524
 
525
525
 
526
526
 
527
-
528
-
529
-
530
527
  static func awaitSynthesizedReplacementCommitOutcome(
531
528
  expectedText: String,
532
529
  placeholder: String?,
@@ -64,9 +64,7 @@ extension RunnerTests {
64
64
  static let warmupValueTimeout: TimeInterval = 0.4
65
65
  static let verificationStabilityWindow: TimeInterval = 0.2
66
66
 
67
-
68
-
69
- static let unconfirmedSettleCeiling: TimeInterval = 1.0
67
+ static let replacementSettleCeiling: TimeInterval = 1.0
70
68
 
71
69
 
72
70
 
@@ -39,41 +39,74 @@ extension RunnerTests {
39
39
  let target: TextEntryElementIdentity
40
40
  }
41
41
 
42
+ struct ReplacementTextEntryConfirmation {
43
+ private let requested: String
44
+ private let baseline: TextEntryObservation?
45
+ private var deadline: Date
46
+ private var latest: TextEntryObservation?
47
+ private var stableSince: Date
48
+
49
+ init(requested: String, baseline: TextEntryObservation?, startedAt: Date) {
50
+ self.requested = requested
51
+ self.baseline = baseline
52
+ deadline = startedAt.addingTimeInterval(TextEntryTiming.replacementSettleCeiling)
53
+ stableSince = startedAt
54
+ }
42
55
 
56
+ mutating func observe(_ observed: TextEntryObservation, at sampledAt: Date) -> TextEntryResult? {
43
57
 
44
-
45
-
46
-
47
- static func textEntryValueEchoes(observed: String, expected: String, baseline: String) -> Bool {
48
- let request = textEntryRequestWithoutSubmitKeys(expected)
49
- let residualAndRequest = baseline + request
50
- return isOrderedSubsequence(observed, of: residualAndRequest)
51
- || (isOrderedSubsequence(request, of: observed) && !isOrderedSubsequence(request, of: baseline))
52
- || isOrderedSubsequence(residualAndRequest, of: observed)
58
+ if latest == nil {
59
+ deadline = max(deadline, sampledAt.addingTimeInterval(TextEntryTiming.verificationStabilityWindow))
60
+ }
61
+ if latest.map({ observed.isSettled(with: $0) }) != true { stableSince = sampledAt }
62
+ latest = observed
63
+ let result = RunnerTests.replacementTextEntryResult(requested: requested, baseline: baseline, observed: observed)
64
+ let settled = sampledAt.timeIntervalSince(stableSince) >= TextEntryTiming.verificationStabilityWindow
65
+ let moved = baseline.map { !observed.isSettled(with: $0) } ?? true
66
+ if settled && (moved || result.verified != false || sampledAt >= deadline) {
67
+ return result
68
+ }
69
+ if sampledAt >= deadline {
70
+ return TextEntryResult(
71
+ verified: nil, repaired: false, expectedText: requested, observedText: observed.value,
72
+ failure: .commitNotObserved
73
+ )
74
+ }
75
+ return nil
76
+ }
53
77
  }
54
78
 
55
79
 
56
-
57
-
58
- static func unconfirmedTextEntryEvidence(
80
+ static func replacementTextEntryResult(
59
81
  requested: String,
60
82
  baseline: TextEntryObservation?,
61
83
  observed: TextEntryObservation?
62
- ) -> TextEntryUnconfirmedEvidence? {
63
- guard !textEntryRequestWithoutSubmitKeys(requested).isEmpty,
64
- let baseline,
65
- let observed,
66
- baseline.identity.isSameElement(as: observed.identity),
67
- observed.value != baseline.value,
68
- !textEntryValueEchoes(observed: observed.value, expected: requested, baseline: baseline.value)
69
- else {
70
- return nil
84
+ ) -> TextEntryResult {
85
+ func result(_ verified: Bool?) -> TextEntryResult {
86
+ TextEntryResult(verified: verified, repaired: false, expectedText: requested, observedText: observed?.value)
87
+ }
88
+ guard let observed else { return result(nil) }
89
+ if let baseline, !baseline.identity.isSameElement(as: observed.identity) {
90
+ return result(false)
71
91
  }
72
- return TextEntryUnconfirmedEvidence(
73
- requested: requested,
74
- before: baseline.value,
75
- after: observed.value,
76
- target: observed.identity
92
+ let request = observed.identity.elementType == elementTypeNamesByRawValue[XCUIElement.ElementType.textView.rawValue]
93
+ ? requested : textEntryRequestWithoutSubmitKeys(requested)
94
+ if observed.value == requested || observed.value == request { return result(true) }
95
+
96
+ if request != requested { return result(nil) }
97
+ guard !request.isEmpty, let baseline else { return result(false) }
98
+ if isOrderedSubsequence(observed.value, of: baseline.value + request) { return result(false) }
99
+ return TextEntryResult(
100
+ verified: nil,
101
+ repaired: false,
102
+ expectedText: requested,
103
+ observedText: observed.value,
104
+ unconfirmed: TextEntryUnconfirmedEvidence(
105
+ requested: requested,
106
+ before: baseline.value,
107
+ after: observed.value,
108
+ target: observed.identity
109
+ )
77
110
  )
78
111
  }
79
112