agent-device 0.21.1 → 0.21.2

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 (171) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.1.apk → agent-device-android-ime-helper-0.21.2.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.2.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.1.manifest.json → agent-device-android-ime-helper-0.21.2.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.2.apk +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.2.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.1.manifest.json → agent-device-android-snapshot-helper-0.21.2.manifest.json} +6 -6
  7. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerScrollViewportPolicy.swift +413 -0
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +34 -0
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +56 -31
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +1 -13
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +4 -0
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +9 -1
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +1 -0
  14. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +0 -4
  15. package/dist/src/absence-observation-errors.js +1 -0
  16. package/dist/src/adapter.js +1 -1
  17. package/dist/src/agent-browser-lifecycle.js +1 -1
  18. package/dist/src/agent-browser-process-record.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.d.ts +1 -1
  22. package/dist/src/ai-sdk.js +1 -1
  23. package/dist/src/alert-runtime.js +1 -1
  24. package/dist/src/android-input-ownership.js +1 -1
  25. package/dist/src/app-helpers.js +1 -1
  26. package/dist/src/app-inventory-contract.js +1 -1
  27. package/dist/src/app-inventory.js +1 -1
  28. package/dist/src/app-log-runtime2.js +2 -2
  29. package/dist/src/auth-session.js +1 -1
  30. package/dist/src/back-mode.js +1 -0
  31. package/dist/src/browser-launch.js +1 -0
  32. package/dist/src/catalog.js +1 -1
  33. package/dist/src/cli-config.js +1 -1
  34. package/dist/src/cli-help.js +12 -12
  35. package/dist/src/cli-runner.js +1 -1
  36. package/dist/src/cli.js +3 -3
  37. package/dist/src/client-metro-companion.js +1 -1
  38. package/dist/src/client-metro.js +1 -1
  39. package/dist/src/client-types.d.ts +2 -1
  40. package/dist/src/code-signature-cache.js +1 -1
  41. package/dist/src/code-signature.js +1 -1
  42. package/dist/src/command-metadata.js +1 -1
  43. package/dist/src/command-schema.js +3 -3
  44. package/dist/src/command-tools.js +2 -2
  45. package/dist/src/config.js +1 -1
  46. package/dist/src/connection-runtime.js +1 -1
  47. package/dist/src/contracts.d.ts +1 -1
  48. package/dist/src/daemon-client-lifecycle.js +1 -1
  49. package/dist/src/daemon-process.js +1 -1
  50. package/dist/src/daemon-shutdown-report.js +1 -1
  51. package/dist/src/device-boot.js +1 -0
  52. package/dist/src/device-claim-inspection.js +1 -1
  53. package/dist/src/device-claim-rule.js +1 -1
  54. package/dist/src/device-input-state.d.ts +1 -1
  55. package/dist/src/device-session.js +1 -1
  56. package/dist/src/durable-resource.js +1 -1
  57. package/dist/src/engine-eval-script.js +1 -0
  58. package/dist/src/file.js +1 -1
  59. package/dist/src/find.js +1 -1
  60. package/dist/src/finders.js +1 -1
  61. package/dist/src/format.js +1 -0
  62. package/dist/src/generic-settle.js +1 -1
  63. package/dist/src/gesture-plan.js +1 -1
  64. package/dist/src/gesture-runtime.js +1 -1
  65. package/dist/src/helper.js +1 -1
  66. package/dist/src/index.d.ts +1 -1
  67. package/dist/src/input-actions.js +1 -1
  68. package/dist/src/interaction-positionals.js +1 -1
  69. package/dist/src/interaction-ref-policy.js +1 -1
  70. package/dist/src/interaction.js +1 -1
  71. package/dist/src/interaction2.js +1 -1
  72. package/dist/src/interactor.js +5 -5
  73. package/dist/src/internal/bin.js +1 -1
  74. package/dist/src/internal/daemon.js +2 -2
  75. package/dist/src/internal/png-worker.d.ts +3 -0
  76. package/dist/src/internal/png-worker.js +1 -1
  77. package/dist/src/ios-snapshot-acquisition.js +1 -1
  78. package/dist/src/ios-snapshot-planning.js +1 -1
  79. package/dist/src/ios-snapshot-runtime.js +1 -1
  80. package/dist/src/lease.js +1 -1
  81. package/dist/src/limrun.d.ts +1 -1
  82. package/dist/src/local-interactor-operation-set.js +1 -1
  83. package/dist/src/mechanics.js +16 -16
  84. package/dist/src/mobile-snapshot-semantics.js +1 -1
  85. package/dist/src/mp4-atoms.js +1 -0
  86. package/dist/src/owner-identity.js +3 -3
  87. package/dist/src/parameterized-recorded-fill.js +1 -1
  88. package/dist/src/perf-facade.js +1 -1
  89. package/dist/src/perf-runtime-plan.js +1 -1
  90. package/dist/src/platform-request-scope.js +1 -1
  91. package/dist/src/platform-runtime-app-log-process.js +1 -1
  92. package/dist/src/platform-runtime-managed-owner.js +1 -1
  93. package/dist/src/platform-runtime-operation-host.js +1 -1
  94. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  95. package/dist/src/platform-runtime-screen-recording-apple-runner-host.js +1 -1
  96. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  97. package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
  98. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  99. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  100. package/dist/src/platform-runtime.js +1 -1
  101. package/dist/src/platform-runtime2.js +1 -1
  102. package/dist/src/png-crop-bytes.js +1 -0
  103. package/dist/src/png-worker-contract.js +1 -1
  104. package/dist/src/png.js +1 -0
  105. package/dist/src/process.js +1 -1
  106. package/dist/src/provider-webdriver.js +1 -1
  107. package/dist/src/quality-warnings.js +1 -0
  108. package/dist/src/react-native.js +1 -1
  109. package/dist/src/record-runtime.js +1 -1
  110. package/dist/src/registry.js +36 -37
  111. package/dist/src/replay.js +1 -1
  112. package/dist/src/request-admission.js +1 -1
  113. package/dist/src/request-cancellation.js +1 -1
  114. package/dist/src/request-runtime-binding.js +1 -1
  115. package/dist/src/runner-client.js +1 -1
  116. package/dist/src/runner-disposal.js +1 -1
  117. package/dist/src/runner-presentation.js +1 -1
  118. package/dist/src/runtime.js +3 -3
  119. package/dist/src/runtime10.js +1 -0
  120. package/dist/src/runtime2.js +4 -4
  121. package/dist/src/runtime4.js +3 -3
  122. package/dist/src/runtime6.js +1 -1
  123. package/dist/src/runtime7.js +1 -1
  124. package/dist/src/runtime8.js +1 -1
  125. package/dist/src/runtime9.js +1 -1
  126. package/dist/src/screen-recording-live-handle.js +1 -1
  127. package/dist/src/screen-recording-resource-recovery.js +1 -1
  128. package/dist/src/screenshot-runtime.js +1 -1
  129. package/dist/src/screenshot3.js +2 -2
  130. package/dist/src/script.js +1 -1
  131. package/dist/src/scroll-gesture.js +1 -1
  132. package/dist/src/sdk-batch.js +1 -1
  133. package/dist/src/sdk-contracts.d.ts +26 -26
  134. package/dist/src/sdk-finders.d.ts +1 -1
  135. package/dist/src/sdk-finders.js +1 -1
  136. package/dist/src/sdk-remote-config.d.ts +1 -1
  137. package/dist/src/sdk-selectors.d.ts +12 -4
  138. package/dist/src/sdk-selectors.js +1 -1
  139. package/dist/src/selection.js +0 -0
  140. package/dist/src/selector-runtime.js +1 -1
  141. package/dist/src/selectors.js +1 -1
  142. package/dist/src/semantic-index.js +1 -1
  143. package/dist/src/server.js +1 -1
  144. package/dist/src/session-snapshot.js +1 -1
  145. package/dist/src/session-store.js +2 -2
  146. package/dist/src/session-target-evidence.js +1 -1
  147. package/dist/src/session2.js +5 -5
  148. package/dist/src/simulator-boot.js +3 -0
  149. package/dist/src/snapshot-runtime-binding.js +1 -1
  150. package/dist/src/snapshot-runtime.js +1 -1
  151. package/dist/src/snapshot-state.js +1 -1
  152. package/dist/src/snapshot4.js +1 -1
  153. package/dist/src/src.js +1 -1
  154. package/dist/src/src7.js +1 -1
  155. package/dist/src/src8.js +2 -2
  156. package/dist/src/telemetry.js +1 -1
  157. package/dist/src/touch-reference-frame.js +1 -1
  158. package/dist/src/tree.js +1 -1
  159. package/dist/src/type-text-runtime.js +1 -1
  160. package/dist/src/vars.js +2 -0
  161. package/dist/src/version.js +1 -1
  162. package/dist/src/video.js +2 -2
  163. package/dist/src/viewport.js +1 -0
  164. package/dist/src/webdriver-ios-snapshot.js +1 -1
  165. package/package.json +2 -1
  166. package/server.json +2 -2
  167. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.1.apk.sha256 +0 -1
  168. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.1.apk +0 -0
  169. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.1.apk.sha256 +0 -1
  170. package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -93
  171. package/dist/src/snapshot-quality-verdict.js +0 -1
@@ -0,0 +1 @@
1
+ 17a3645b2baca245adaa37e025a89eb35da7c5c6d53ae2d2d84862474f402cf0 agent-device-android-ime-helper-0.21.2.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.1",
4
- "assetName": "agent-device-android-ime-helper-0.21.1.apk",
5
- "sha256": "65454a4663596adfc7fccd9a30e42f02b5ecde17eb1514fa115ef3036171b995",
3
+ "version": "0.21.2",
4
+ "assetName": "agent-device-android-ime-helper-0.21.2.apk",
5
+ "sha256": "17a3645b2baca245adaa37e025a89eb35da7c5c6d53ae2d2d84862474f402cf0",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21001,
7
+ "versionCode": 21002,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ ee5d763221ede67f2b6e33e56d3487f3c05aab65a9a6999e456b273c7ccfecec agent-device-android-snapshot-helper-0.21.2.apk
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "android-snapshot-helper",
3
- "version": "0.21.1",
4
- "releaseTag": "v0.21.1",
5
- "assetName": "agent-device-android-snapshot-helper-0.21.1.apk",
3
+ "version": "0.21.2",
4
+ "releaseTag": "v0.21.2",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.2.apk",
6
6
  "apkUrl": null,
7
- "sha256": "6ee75909ac499eca439d3663717d8fc1928ada3fec71bf2ae85fb567967a3793",
8
- "checksumName": "agent-device-android-snapshot-helper-0.21.1.apk.sha256",
7
+ "sha256": "ee5d763221ede67f2b6e33e56d3487f3c05aab65a9a6999e456b273c7ccfecec",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.2.apk.sha256",
9
9
  "packageName": "com.callstack.agentdevice.snapshothelper",
10
- "versionCode": 21001,
10
+ "versionCode": 21002,
11
11
  "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
12
  "minSdk": 23,
13
13
  "targetSdk": 36,
@@ -0,0 +1,413 @@
1
+ import XCTest
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ enum RunnerScrollKeyboardClip: Equatable {
23
+
24
+ case unobstructed
25
+
26
+ case avoided(frame: CGRect, keyboardMinY: Double)
27
+
28
+ case occluded(keyboardMinY: Double, visibleHeight: Double)
29
+ }
30
+
31
+
32
+ enum RunnerScrollViewport {
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ case swipe(planFrame: CGRect, coordinateFrame: CGRect, keyboardMinY: Double?)
41
+
42
+ case occluded(keyboardMinY: Double, visibleHeight: Double)
43
+ }
44
+
45
+
46
+
47
+ struct ScrollGestureDispatch {
48
+ let plan: RunnerScrollGesturePlan
49
+ let planFrame: CGRect
50
+ let coordinateFrame: CGRect
51
+ let keyboardMinY: Double?
52
+ }
53
+
54
+
55
+ enum ScrollGestureOutcome {
56
+ case gesture(ScrollGestureDispatch)
57
+ case unusableFrame
58
+ case unusablePlan
59
+ case occluded(keyboardMinY: Double, visibleHeight: Double)
60
+ }
61
+
62
+ extension ScrollGestureDispatch {
63
+
64
+
65
+
66
+
67
+ func attachingEvidence(to response: Response) -> Response {
68
+ guard response.ok else { return response }
69
+ var payload = response.data ?? DataPayload()
70
+ payload.referenceWidth = Double(planFrame.width)
71
+ payload.referenceHeight = Double(planFrame.height)
72
+ guard let keyboardMinY else {
73
+ return Response(ok: response.ok, data: payload, error: response.error)
74
+ }
75
+ payload.keyboardAvoided = true
76
+ payload.keyboardMinY = keyboardMinY
77
+ return Response(ok: response.ok, data: payload, error: response.error)
78
+ }
79
+ }
80
+
81
+ extension RunnerScrollViewport {
82
+
83
+
84
+ func gestureDispatch(
85
+ direction: RunnerScrollDirection,
86
+ amount: Double?,
87
+ pixels: Double?
88
+ ) -> ScrollGestureOutcome {
89
+ switch self {
90
+ case .occluded(let keyboardMinY, let visibleHeight):
91
+ return .occluded(keyboardMinY: keyboardMinY, visibleHeight: visibleHeight)
92
+ case .swipe(let planFrame, let coordinateFrame, let keyboardMinY):
93
+ guard planFrame.width > 0, planFrame.height > 0 else {
94
+ return .unusableFrame
95
+ }
96
+ guard let plan = runnerScrollGesturePlan(
97
+ direction: direction,
98
+ amount: amount,
99
+ pixels: pixels,
100
+ referenceWidth: planFrame.width,
101
+ referenceHeight: planFrame.height
102
+ ) else {
103
+ return .unusablePlan
104
+ }
105
+ return .gesture(
106
+ ScrollGestureDispatch(
107
+ plan: plan,
108
+ planFrame: planFrame,
109
+ coordinateFrame: coordinateFrame,
110
+ keyboardMinY: keyboardMinY
111
+ )
112
+ )
113
+ }
114
+ }
115
+ }
116
+
117
+ enum ScrollViewportPolicy {
118
+
119
+ static let minVisibleFraction: Double = 0.15
120
+
121
+
122
+
123
+
124
+
125
+ static let accessoryAllowance: Double = 12
126
+
127
+
128
+
129
+ static let occlusionRunnerCode = "SCROLL_KEYBOARD_OCCLUDES_SURFACE"
130
+
131
+
132
+
133
+ static func clip(viewport: CGRect, keyboard: CGRect) -> RunnerScrollKeyboardClip {
134
+ guard isUsable(viewport), isUsable(keyboard) else {
135
+ return .unobstructed
136
+ }
137
+
138
+
139
+
140
+ let swipeCenterX = viewport.minX + viewport.width / 2
141
+ if swipeCenterX < keyboard.minX || swipeCenterX >= keyboard.maxX {
142
+ return .unobstructed
143
+ }
144
+ let keyboardMinY = keyboard.minY
145
+ if keyboardMinY >= viewport.maxY || keyboard.maxY <= viewport.minY {
146
+ return .unobstructed
147
+ }
148
+ let visibleHeight = max(0, keyboardMinY - accessoryAllowance - viewport.minY)
149
+ if visibleHeight < minVisibleFraction * viewport.height {
150
+ return .occluded(keyboardMinY: keyboardMinY, visibleHeight: visibleHeight)
151
+ }
152
+ return .avoided(
153
+ frame: CGRect(
154
+ x: viewport.minX,
155
+ y: viewport.minY,
156
+ width: viewport.width,
157
+ height: visibleHeight
158
+ ),
159
+ keyboardMinY: keyboardMinY
160
+ )
161
+ }
162
+
163
+
164
+
165
+
166
+ static func frames(referenceFrame: CGRect, clip: RunnerScrollKeyboardClip) -> RunnerScrollViewport {
167
+ switch clip {
168
+ case .unobstructed:
169
+ return .swipe(planFrame: referenceFrame, coordinateFrame: referenceFrame, keyboardMinY: nil)
170
+ case .avoided(let frame, let keyboardMinY):
171
+ return .swipe(planFrame: frame, coordinateFrame: referenceFrame, keyboardMinY: keyboardMinY)
172
+ case .occluded(let keyboardMinY, let visibleHeight):
173
+ return .occluded(keyboardMinY: keyboardMinY, visibleHeight: visibleHeight)
174
+ }
175
+ }
176
+
177
+ private static func isUsable(_ rect: CGRect) -> Bool {
178
+ return [rect.minX, rect.minY, rect.width, rect.height].allSatisfy(\.isFinite)
179
+ && rect.width > 0 && rect.height > 0
180
+ }
181
+ }
182
+
183
+ extension RunnerTests {
184
+
185
+
186
+
187
+
188
+ func resolvedScrollViewport(
189
+ app: XCUIApplication,
190
+ context: SynthesizedCoordinateContext
191
+ ) -> RunnerScrollViewport {
192
+ #if os(iOS)
193
+
194
+
195
+ guard context.allowsKeyboardProbe else {
196
+ logScrollViewport(decision: "probeSkipped", keyboardMinY: nil, swipeHeight: context.referenceFrame.height, context: context)
197
+ return ScrollViewportPolicy.frames(referenceFrame: context.referenceFrame, clip: .unobstructed)
198
+ }
199
+ guard let keyboardFrame = visibleKeyboardFrame(app: app) else {
200
+ logScrollViewport(decision: "noKeyboard", keyboardMinY: nil, swipeHeight: context.referenceFrame.height, context: context)
201
+ return ScrollViewportPolicy.frames(referenceFrame: context.referenceFrame, clip: .unobstructed)
202
+ }
203
+ let clip = ScrollViewportPolicy.clip(viewport: context.referenceFrame, keyboard: keyboardFrame)
204
+ switch clip {
205
+ case .unobstructed:
206
+ logScrollViewport(decision: "unobstructed", keyboardMinY: nil, swipeHeight: context.referenceFrame.height, context: context)
207
+ case .avoided(let frame, let keyboardMinY):
208
+ logScrollViewport(
209
+ decision: "avoided",
210
+ keyboardMinY: keyboardMinY,
211
+ swipeHeight: frame.height,
212
+ context: context
213
+ )
214
+ case .occluded(let keyboardMinY, let visibleHeight):
215
+ logScrollViewport(
216
+ decision: "occluded",
217
+ keyboardMinY: keyboardMinY,
218
+ swipeHeight: visibleHeight,
219
+ context: context
220
+ )
221
+ }
222
+ return ScrollViewportPolicy.frames(referenceFrame: context.referenceFrame, clip: clip)
223
+ #else
224
+ let fallbackFrame = resolvedTouchReferenceFrame(app: app, appFrame: app.frame)
225
+ return ScrollViewportPolicy.frames(referenceFrame: fallbackFrame, clip: .unobstructed)
226
+ #endif
227
+ }
228
+
229
+ #if os(iOS)
230
+
231
+
232
+
233
+ private func logScrollViewport(
234
+ decision: String,
235
+ keyboardMinY: Double?,
236
+ swipeHeight: Double,
237
+ context: SynthesizedCoordinateContext
238
+ ) {
239
+ NSLog(
240
+ "AGENT_DEVICE_RUNNER_SCROLL_VIEWPORT kind=scroll axHealth=%@ keyboardPolicy=%@ decision=%@ keyboardMinY=%@ swipeHeight=%.1f",
241
+ context.accessibilityHealth.rawValue,
242
+ context.keyboardPolicy.rawValue,
243
+ decision,
244
+ keyboardMinY.map { String(format: "%.1f", $0) } ?? "none",
245
+ swipeHeight
246
+ )
247
+ }
248
+ #endif
249
+ }
250
+
251
+
252
+
253
+
254
+
255
+
256
+
257
+
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+
296
+
297
+
298
+
299
+
300
+
301
+
302
+
303
+
304
+
305
+
306
+
307
+
308
+
309
+
310
+
311
+
312
+
313
+
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
@@ -65,6 +65,9 @@ extension RunnerTests {
65
65
  guard Date() < deadline else {
66
66
  return alertVerificationResponse(.timedOut, action: action, activated: false)
67
67
  }
68
+ guard waitUntilAlertButtonHittable(button, deadline: deadline) else {
69
+ return alertVerificationResponse(.timedOut, action: action, activated: false)
70
+ }
68
71
  let outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
69
72
  if let response = unsupportedResponse(for: outcome) {
70
73
  return response
@@ -222,6 +225,37 @@ extension RunnerTests {
222
225
  return enabled
223
226
  }
224
227
 
228
+
229
+
230
+
231
+
232
+
233
+
234
+
235
+
236
+ private func waitUntilAlertButtonHittable(_ button: XCUIElement, deadline: Date) -> Bool {
237
+ while Date() < deadline {
238
+ if probeAlertButtonHittable(button, deadline: deadline) {
239
+ return Date() < deadline
240
+ }
241
+ sleepFor(min(0.1, max(0, deadline.timeIntervalSinceNow)))
242
+ }
243
+ return false
244
+ }
245
+
246
+ private func probeAlertButtonHittable(_ button: XCUIElement, deadline: Date) -> Bool {
247
+
248
+
249
+
250
+
251
+
252
+ var hittable = false
253
+ _ = RunnerObjCExceptionCatcher.catchException({
254
+ hittable = button.exists && button.isHittable
255
+ })
256
+ return hittable
257
+ }
258
+
225
259
  private func isDismissPopupMarker(_ label: String) -> Bool {
226
260
  label.trimmingCharacters(in: .whitespacesAndNewlines).caseInsensitiveCompare("dismiss popup") == .orderedSame
227
261
  }
@@ -1953,21 +1953,25 @@ extension RunnerTests {
1953
1953
  error: ErrorPayload(message: "scroll could not resolve a usable interaction frame")
1954
1954
  )
1955
1955
  }
1956
- let frame = scrollReferenceFrame(app: activeApp, context: scrollContext)
1957
- guard frame.width > 0, frame.height > 0 else {
1956
+ let viewport = resolvedScrollViewport(app: activeApp, context: scrollContext)
1957
+ let defaults = runnerDragCommandDefaults(command)
1958
+ switch viewport.gestureDispatch(
1959
+ direction: direction,
1960
+ amount: defaults.scrollAmount,
1961
+ pixels: command.pixels
1962
+ ) {
1963
+ case .occluded(let occlusionKeyboardMinY, let visibleHeight):
1964
+ return scrollKeyboardOccludedResponse(
1965
+ direction: direction.rawValue,
1966
+ keyboardMinY: occlusionKeyboardMinY,
1967
+ visibleHeight: visibleHeight
1968
+ )
1969
+ case .unusableFrame:
1958
1970
  return Response(
1959
1971
  ok: false,
1960
1972
  error: ErrorPayload(message: "scroll could not resolve a usable interaction frame")
1961
1973
  )
1962
- }
1963
- let defaults = runnerDragCommandDefaults(command)
1964
- guard let plan = runnerScrollGesturePlan(
1965
- direction: direction,
1966
- amount: defaults.scrollAmount,
1967
- pixels: command.pixels,
1968
- referenceWidth: frame.width,
1969
- referenceHeight: frame.height
1970
- ) else {
1974
+ case .unusablePlan:
1971
1975
  return Response(
1972
1976
  ok: false,
1973
1977
  error: ErrorPayload(
@@ -1975,21 +1979,24 @@ extension RunnerTests {
1975
1979
  message: "scroll could not compute a gesture plan"
1976
1980
  )
1977
1981
  )
1982
+ case .gesture(let gesture):
1983
+ guard scrollDurationIsValid(command.durationMs) else {
1984
+ return invalidScrollDurationResponse(commandName: "scroll")
1985
+ }
1986
+ return gesture.attachingEvidence(
1987
+ to: executeScrollDragGesture(
1988
+ activeApp: activeApp,
1989
+ x: gesture.planFrame.minX + gesture.plan.x1,
1990
+ y: gesture.planFrame.minY + gesture.plan.y1,
1991
+ x2: gesture.planFrame.minX + gesture.plan.x2,
1992
+ y2: gesture.planFrame.minY + gesture.plan.y2,
1993
+ durationMs: defaults.durationMs,
1994
+ message: "scrolled",
1995
+ context: scrollContext.withReferenceFrame(gesture.coordinateFrame),
1996
+ releaseBehavior: command.scrollReleaseBehavior
1997
+ )
1998
+ )
1978
1999
  }
1979
- guard scrollDurationIsValid(command.durationMs) else {
1980
- return invalidScrollDurationResponse(commandName: "scroll")
1981
- }
1982
- return executeScrollDragGesture(
1983
- activeApp: activeApp,
1984
- x: frame.minX + plan.x1,
1985
- y: frame.minY + plan.y1,
1986
- x2: frame.minX + plan.x2,
1987
- y2: frame.minY + plan.y2,
1988
- durationMs: defaults.durationMs,
1989
- message: "scrolled",
1990
- context: scrollContext.withReferenceFrame(frame),
1991
- releaseBehavior: command.scrollReleaseBehavior
1992
- )
1993
2000
  case .desktopScroll:
1994
2001
  guard let rawDirection = command.direction,
1995
2002
  let direction = RunnerScrollDirection(rawValue: rawDirection)
@@ -2564,12 +2571,30 @@ extension RunnerTests {
2564
2571
  )
2565
2572
  }
2566
2573
 
2567
- private func scrollReferenceFrame(app: XCUIApplication, context: SynthesizedCoordinateContext) -> CGRect {
2568
- #if os(iOS)
2569
- return synthesizedFrameAvoidingKeyboardWhenAllowed(app: app, context: context)
2570
- #else
2571
- return resolvedTouchReferenceFrame(app: app, appFrame: app.frame)
2572
- #endif
2574
+
2575
+
2576
+
2577
+
2578
+
2579
+
2580
+
2581
+ private func scrollKeyboardOccludedResponse(
2582
+ direction: String,
2583
+ keyboardMinY: Double,
2584
+ visibleHeight: Double
2585
+ ) -> Response {
2586
+ return Response(
2587
+ ok: false,
2588
+ error: ErrorPayload(
2589
+ code: ScrollViewportPolicy.occlusionRunnerCode,
2590
+ message: String(
2591
+ format:
2592
+ "scroll %@ refused: the keyboard leaves %.0fpt of visible surface above it, too little to swipe",
2593
+ direction,
2594
+ visibleHeight
2595
+ )
2596
+ )
2597
+ )
2573
2598
  }
2574
2599
 
2575
2600
  private func dragCommandName(message: String) -> String {
@@ -11,7 +11,7 @@ private struct RunnerUnsupportedOperationError: LocalizedError {
11
11
  var errorDescription: String? { message }
12
12
  }
13
13
 
14
- private enum RunnerInterfaceOrientation {
14
+ enum RunnerInterfaceOrientation {
15
15
 
16
16
 
17
17
 
@@ -909,18 +909,6 @@ extension RunnerTests {
909
909
  return CGRect(x: 0, y: 0, width: width, height: height)
910
910
  }
911
911
 
912
- func synthesizedFrameAvoidingKeyboardWhenAllowed(
913
- app: XCUIApplication,
914
- context: SynthesizedCoordinateContext
915
- ) -> CGRect {
916
- #if os(iOS)
917
- guard context.allowsKeyboardProbe else { return context.referenceFrame }
918
- return frameAvoidingKeyboard(app: app, frame: context.referenceFrame)
919
- #else
920
- return context.referenceFrame
921
- #endif
922
- }
923
-
924
912
  func keyboardAvoidingSynthesizedDragPoints(
925
913
  app: XCUIApplication,
926
914
  x: Double,
@@ -268,6 +268,10 @@ struct DataPayload: Codable {
268
268
  var gestureFallback: String?
269
269
  var gestureFallbackMessage: String?
270
270
  var gestureFallbackHint: String?
271
+
272
+
273
+ var keyboardAvoided: Bool?
274
+ var keyboardMinY: Double?
271
275
  var maestroNonHittableCoordinateFallbackUsed: Bool?
272
276
  var textEntryRoute: String?
273
277
  var runnerFatal: Bool?
@@ -89,8 +89,14 @@ func synthesizedGesturePolicy(_ kind: SynthesizedGesturePolicyKind) -> Synthesiz
89
89
  fallbackPolicy: .xctestCoordinateAllowed
90
90
  )
91
91
  case .scroll:
92
+
93
+
94
+
95
+
96
+
97
+
92
98
  return SynthesizedGesturePolicy(
93
- keyboardPolicy: .whenAccessibilityHealthy,
99
+ keyboardPolicy: .requiredWhenAvailable,
94
100
  fallbackPolicy: .privateSynthesisRequired
95
101
  )
96
102
  case .synthesizedDrag:
@@ -225,6 +231,8 @@ extension RunnerTests {
225
231
 
226
232
 
227
233
 
234
+
235
+
228
236
 
229
237
 
230
238
 
@@ -182,6 +182,7 @@ final class RunnerTests: XCTestCase {
182
182
 
183
183
 
184
184
 
185
+
185
186
 
186
187
 
187
188
  var textInputProbeIssues: TextInputProbeIssues?