agent-device 0.21.3 → 0.21.4

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 (131) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.3.apk → agent-device-android-ime-helper-0.21.4.apk} +0 -0
  2. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.4.apk.sha256 +1 -0
  3. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.3.manifest.json → agent-device-android-ime-helper-0.21.4.manifest.json} +4 -4
  4. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.3.apk → agent-device-android-snapshot-helper-0.21.4.apk} +0 -0
  5. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.4.apk.sha256 +1 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.4.manifest.json +16 -0
  7. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +39 -0
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +9 -1
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +2 -1
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +10 -1
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +26 -124
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +46 -0
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +36 -4
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+MainThreadWork.swift +229 -0
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +15 -0
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +4 -40
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +6 -40
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +30 -9
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +22 -6
  20. package/dist/src/adapter.js +1 -1
  21. package/dist/src/adb-executor.js +2 -2
  22. package/dist/src/adb-failure.js +1 -1
  23. package/dist/src/agent-browser-process-record.js +1 -1
  24. package/dist/src/agent-device-client.js +2 -2
  25. package/dist/src/api.js +3 -11
  26. package/dist/src/app-launch.js +1 -1
  27. package/dist/src/app-lifecycle.js +1 -1
  28. package/dist/src/app-log-reconnect.js +1 -1
  29. package/dist/src/archive.js +2 -2
  30. package/dist/src/cli-help.js +2 -2
  31. package/dist/src/cli.js +4 -4
  32. package/dist/src/client-metro-companion.js +1 -1
  33. package/dist/src/client-metro.js +1 -1
  34. package/dist/src/client-types.d.ts +69 -4
  35. package/dist/src/code-signature-cache.js +1 -1
  36. package/dist/src/command-tools.js +2 -2
  37. package/dist/src/command.js +1 -1
  38. package/dist/src/connection-runtime.js +1 -1
  39. package/dist/src/contracts.js +1 -1
  40. package/dist/src/daemon-client-lifecycle.js +1 -1
  41. package/dist/src/daemon-shutdown-report.js +1 -1
  42. package/dist/src/device-claim-rule.js +1 -1
  43. package/dist/src/device-input-state.d.ts +10 -8
  44. package/dist/src/device-inventory-context.js +1 -1
  45. package/dist/src/device-session.js +1 -1
  46. package/dist/src/diagnostics.js +1 -1
  47. package/dist/src/error.js +2 -2
  48. package/dist/src/exec.js +1 -1
  49. package/dist/src/file.js +1 -1
  50. package/dist/src/find.js +1 -1
  51. package/dist/src/gesture-runtime.js +1 -1
  52. package/dist/src/helper.js +1 -1
  53. package/dist/src/index.js +1 -1
  54. package/dist/src/install-source-network-transport.js +1 -1
  55. package/dist/src/interaction2.js +1 -1
  56. package/dist/src/interactor.js +4 -4
  57. package/dist/src/internal/bin.js +1 -1
  58. package/dist/src/internal/daemon.js +2 -2
  59. package/dist/src/internal/png-worker.js +1 -1
  60. package/dist/src/ios-client.js +121 -7
  61. package/dist/src/ios-snapshot-adapter.js +1 -1
  62. package/dist/src/ios-system-surface.js +1 -1
  63. package/dist/src/limrun-delta-sync-omitted.js +1 -0
  64. package/dist/src/limrun.d.ts +0 -1598
  65. package/dist/src/location-coordinates.js +1 -1
  66. package/dist/src/mechanics.js +16 -16
  67. package/dist/src/output-common.js +2 -0
  68. package/dist/src/owner-identity.js +1 -1
  69. package/dist/src/perf-runtime-operation-builder.js +1 -1
  70. package/dist/src/perf-runtime-plan.js +1 -1
  71. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  72. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  73. package/dist/src/platform-runtime-app-log-process.js +1 -1
  74. package/dist/src/platform-runtime-host.js +1 -1
  75. package/dist/src/platform-runtime-operation-host.js +2 -2
  76. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  77. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  78. package/dist/src/platform-runtime-screen-recording-output-host.js +1 -1
  79. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  80. package/dist/src/process.js +1 -1
  81. package/dist/src/proxy.js +1 -1
  82. package/dist/src/quality-warnings.js +1 -1
  83. package/dist/src/react-native.js +1 -1
  84. package/dist/src/record-runtime.js +1 -1
  85. package/dist/src/recording-stop-observation.js +1 -0
  86. package/dist/src/registry.js +35 -36
  87. package/dist/src/replay-divergence.js +1 -1
  88. package/dist/src/reporting.js +11 -9
  89. package/dist/src/result-serialization.js +1 -1
  90. package/dist/src/runner-cache-metadata.js +1 -1
  91. package/dist/src/runner-client.js +1 -1
  92. package/dist/src/runner-disposal.js +1 -1
  93. package/dist/src/runner-operations-facade.js +1 -1
  94. package/dist/src/runner.js +1 -1
  95. package/dist/src/runtime.js +3 -3
  96. package/dist/src/runtime10.js +1 -1
  97. package/dist/src/runtime2.js +4 -4
  98. package/dist/src/runtime3.js +1 -1
  99. package/dist/src/runtime4.js +4 -4
  100. package/dist/src/runtime5.js +1 -1
  101. package/dist/src/runtime6.js +1 -1
  102. package/dist/src/runtime9.js +1 -1
  103. package/dist/src/screen-recording-completion.js +1 -1
  104. package/dist/src/screen-recording-resource-recovery.js +1 -1
  105. package/dist/src/screenshot-result.js +1 -1
  106. package/dist/src/sdk-contracts.js +2 -2
  107. package/dist/src/sdk-selectors.d.ts +36 -24
  108. package/dist/src/selector-runtime.js +1 -1
  109. package/dist/src/session-observation.js +1 -1
  110. package/dist/src/session-snapshot.js +1 -1
  111. package/dist/src/session-store.js +2 -2
  112. package/dist/src/session2.js +5 -5
  113. package/dist/src/snapshot-runtime.js +1 -1
  114. package/dist/src/snapshot.js +1 -1
  115. package/dist/src/snapshot4.js +1 -1
  116. package/dist/src/stop-sequence.js +1 -0
  117. package/dist/src/success-text.js +1 -1
  118. package/dist/src/text-entry-focus.js +1 -0
  119. package/dist/src/tool-provider.js +1 -1
  120. package/dist/src/tool-provider2.js +1 -1
  121. package/dist/src/tool-provider3.js +1 -1
  122. package/dist/src/type-text-runtime.js +1 -1
  123. package/dist/src/video.js +2 -2
  124. package/dist/src/yauzl.js +1 -0
  125. package/package.json +2 -2
  126. package/server.json +2 -2
  127. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.3.apk.sha256 +0 -1
  128. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.3.apk.sha256 +0 -1
  129. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.3.manifest.json +0 -17
  130. package/dist/src/audio-probe-support.js +0 -1
  131. package/dist/src/screen-recording-live-handle.js +0 -1
@@ -0,0 +1,229 @@
1
+ import XCTest
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ extension RunnerTests {
13
+
14
+
15
+ private final class MainThreadWorkState {
16
+ var finished = false
17
+ var abandoned = false
18
+ }
19
+
20
+ enum MainThreadBusyState {
21
+ case idle
22
+ case busy(abandonedForSeconds: TimeInterval)
23
+ case wedged(abandonedForSeconds: TimeInterval)
24
+
25
+
26
+
27
+
28
+ var reportsMainThreadBusy: Bool {
29
+ if case .idle = self { return false }
30
+ return true
31
+ }
32
+ }
33
+
34
+ func currentMainThreadBusyState() -> MainThreadBusyState {
35
+ mainThreadWorkLock.lock()
36
+ defer { mainThreadWorkLock.unlock() }
37
+ guard abandonedMainThreadWorkCount > 0 else { return .idle }
38
+ let abandonedFor = abandonedMainThreadWorkSince.map { Date().timeIntervalSince($0) } ?? 0
39
+ if abandonedFor > mainThreadWedgeThreshold {
40
+ return .wedged(abandonedForSeconds: abandonedFor)
41
+ }
42
+ return .busy(abandonedForSeconds: abandonedFor)
43
+ }
44
+
45
+ func hasAbandonedMainThreadWork() -> Bool {
46
+ mainThreadWorkLock.lock()
47
+ defer { mainThreadWorkLock.unlock() }
48
+ return abandonedMainThreadWorkCount > 0
49
+ }
50
+
51
+
52
+
53
+
54
+
55
+ func runMainThreadWork<T>(
56
+ _ operation: String,
57
+ timeout: TimeInterval,
58
+ timeoutError: @escaping () -> Error,
59
+ onAbandoned: (() -> Void)? = nil,
60
+ _ work: @escaping () throws -> T
61
+ ) throws -> T {
62
+ if Thread.isMainThread {
63
+ return try work()
64
+ }
65
+ var result: Result<T, Error>?
66
+ let semaphore = DispatchSemaphore(value: 0)
67
+ let workState = MainThreadWorkState()
68
+ DispatchQueue.main.async {
69
+ do {
70
+ result = .success(try work())
71
+ } catch {
72
+ result = .failure(error)
73
+ }
74
+ self.mainThreadWorkLock.lock()
75
+ let abandoned = workState.abandoned
76
+ if abandoned {
77
+ self.abandonedMainThreadWorkCount -= 1
78
+ if self.abandonedMainThreadWorkCount == 0 {
79
+ self.abandonedMainThreadWorkSince = nil
80
+ }
81
+ } else {
82
+ workState.finished = true
83
+ }
84
+ let allDrained = abandoned && self.abandonedMainThreadWorkCount == 0
85
+ self.mainThreadWorkLock.unlock()
86
+ if abandoned {
87
+ NSLog("AGENT_DEVICE_RUNNER_MAIN_THREAD_WORK_DRAINED operation=%@", operation)
88
+ if allDrained {
89
+ NSLog("AGENT_DEVICE_RUNNER_ABANDONED_WORK_DRAINED")
90
+ }
91
+ }
92
+ semaphore.signal()
93
+ }
94
+ let waitResult = semaphore.wait(timeout: .now() + timeout)
95
+ if waitResult == .timedOut {
96
+ mainThreadWorkLock.lock()
97
+ let abandoned = !workState.finished
98
+ if abandoned {
99
+ workState.abandoned = true
100
+ abandonedMainThreadWorkCount += 1
101
+ if abandonedMainThreadWorkSince == nil {
102
+ abandonedMainThreadWorkSince = Date()
103
+ }
104
+ }
105
+ mainThreadWorkLock.unlock()
106
+ if abandoned {
107
+ NSLog(
108
+ "AGENT_DEVICE_RUNNER_MAIN_THREAD_WORK_ABANDONED operation=%@ slice=%.1f",
109
+ operation,
110
+ timeout
111
+ )
112
+ onAbandoned?()
113
+ }
114
+ throw timeoutError()
115
+ }
116
+ switch result {
117
+ case .success(let value):
118
+ return value
119
+ case .failure(let error):
120
+ throw error
121
+ case .none:
122
+ throw NSError(
123
+ domain: RunnerErrorDomain.general,
124
+ code: RunnerErrorCode.noResponseFromMainThread,
125
+ userInfo: [NSLocalizedDescriptionKey: "no response from main thread"]
126
+ )
127
+ }
128
+ }
129
+
130
+ func mainThreadExecutionTimeoutError() -> Error {
131
+ NSError(
132
+ domain: RunnerErrorDomain.general,
133
+ code: RunnerErrorCode.mainThreadExecutionTimedOut,
134
+ userInfo: [NSLocalizedDescriptionKey: "main thread execution timed out"]
135
+ )
136
+ }
137
+ }
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+
166
+
167
+
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+
193
+
194
+
195
+
196
+
197
+
198
+
199
+
200
+
201
+
202
+
203
+
204
+
205
+
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+
227
+
228
+
229
+
@@ -229,6 +229,16 @@ extension Response {
229
229
  payload.currentUptimeMs = value
230
230
  return Response(ok: ok, data: payload, error: error)
231
231
  }
232
+
233
+
234
+
235
+
236
+ func stampingCurrentMainThreadBusy(_ value: Bool) -> Response {
237
+ guard ok else { return self }
238
+ var payload = data ?? DataPayload()
239
+ payload.runnerMainThreadBusy = value
240
+ return Response(ok: ok, data: payload, error: error)
241
+ }
232
242
  }
233
243
 
234
244
  struct DataPayload: Codable {
@@ -276,6 +286,11 @@ struct DataPayload: Codable {
276
286
  var textEntryRoute: String?
277
287
  var runnerFatal: Bool?
278
288
  var runnerFatalReason: String?
289
+
290
+
291
+
292
+
293
+ var runnerMainThreadBusy: Bool?
279
294
  var completedSteps: Int?
280
295
  var failedStepIndex: Int?
281
296
  var sequenceResults: [SequenceStepResult]?
@@ -321,6 +321,7 @@ extension RunnerTests {
321
321
  let startedAt = Date()
322
322
  do {
323
323
  return try runMainThreadWork(
324
+ "system_modal_probe",
324
325
  timeout: slice,
325
326
  timeoutError: {
326
327
  SnapshotCaptureFailure(
@@ -330,16 +331,10 @@ extension RunnerTests {
330
331
  )
331
332
  },
332
333
  onAbandoned: {
333
- self.retainAbandonedXCTestChannelWork()
334
- NSLog("AGENT_DEVICE_RUNNER_SYSTEM_MODAL_PROBE_TIMEOUT slice=%.1f", slice)
335
334
  self.penalizeSnapshotXCTestChannel(
336
335
  bundleId: self.currentBundleId,
337
336
  reason: "system_modal_probe_timeout"
338
337
  )
339
- },
340
- onDrained: {
341
- self.releaseAbandonedXCTestChannelWork()
342
- NSLog("AGENT_DEVICE_RUNNER_SYSTEM_MODAL_PROBE_DRAINED")
343
338
  }
344
339
  ) {
345
340
  probe(probeDeadline)
@@ -788,8 +783,6 @@ extension RunnerTests {
788
783
 
789
784
 
790
785
 
791
-
792
-
793
786
 
794
787
 
795
788
 
@@ -839,6 +832,7 @@ extension RunnerTests {
839
832
  treeCaptureSliceBudgetOverride: TimeInterval? = nil
840
833
  ) throws -> SnapshotTraversalContext? {
841
834
  let viewport = try runMainThreadWork(
835
+ "snapshot_viewport",
842
836
  timeout: min(1.0, max(0.1, captureDeadline.timeIntervalSinceNow)),
843
837
  timeoutError: snapshotMainThreadTimeoutError("preparing tree snapshot")
844
838
  ) {
@@ -860,25 +854,6 @@ extension RunnerTests {
860
854
 
861
855
  static let xCTestSnapshotTimeoutCode = "IOS_TREE_CAPTURE_TIMEOUT"
862
856
 
863
- func hasAbandonedTreeCapture() -> Bool {
864
- treeCaptureLock.lock()
865
- defer { treeCaptureLock.unlock() }
866
- return abandonedTreeCaptureCount > 0
867
- }
868
-
869
-
870
-
871
- func retainAbandonedXCTestChannelWork() {
872
- treeCaptureLock.lock()
873
- abandonedTreeCaptureCount += 1
874
- treeCaptureLock.unlock()
875
- }
876
-
877
- func releaseAbandonedXCTestChannelWork() {
878
- treeCaptureLock.lock()
879
- abandonedTreeCaptureCount -= 1
880
- treeCaptureLock.unlock()
881
- }
882
857
 
883
858
 
884
859
 
@@ -893,20 +868,9 @@ extension RunnerTests {
893
868
  return try captureSnapshotRoot(element)
894
869
  }
895
870
  return try runMainThreadWork(
871
+ "tree_capture",
896
872
  timeout: sliceSeconds,
897
- timeoutError: treeCaptureTimeoutError(sliceSeconds: sliceSeconds),
898
- onAbandoned: {
899
- self.retainAbandonedXCTestChannelWork()
900
- NSLog("AGENT_DEVICE_RUNNER_TREE_CAPTURE_SLICE_TIMEOUT slice=%.1f", sliceSeconds)
901
- self.penalizeSnapshotXCTestChannel(
902
- bundleId: self.currentBundleId,
903
- reason: "tree_capture_slice_timeout"
904
- )
905
- },
906
- onDrained: {
907
- self.releaseAbandonedXCTestChannelWork()
908
- NSLog("AGENT_DEVICE_RUNNER_TREE_CAPTURE_DRAINED")
909
- }
873
+ timeoutError: treeCaptureTimeoutError(sliceSeconds: sliceSeconds)
910
874
  ) {
911
875
  try self.captureSnapshotRoot(element)
912
876
  }
@@ -248,8 +248,8 @@ extension RunnerTests {
248
248
  )
249
249
  }
250
250
 
251
- func shouldSkipSnapshotBackendForAbandonedTreeCapture(_ kind: SnapshotBackendKind) -> Bool {
252
- kind.usesXCTestAccessibilityChannel && hasAbandonedTreeCapture()
251
+ func shouldSkipSnapshotBackendForAbandonedMainThreadWork(_ kind: SnapshotBackendKind) -> Bool {
252
+ kind.usesXCTestAccessibilityChannel && hasAbandonedMainThreadWork()
253
253
  }
254
254
 
255
255
 
@@ -316,11 +316,11 @@ extension RunnerTests {
316
316
  }
317
317
 
318
318
 
319
- if shouldSkipSnapshotBackendForAbandonedTreeCapture(kind) {
319
+ if shouldSkipSnapshotBackendForAbandonedMainThreadWork(kind) {
320
320
  NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_TIER_SKIPPED_XCTEST_OCCUPIED tier=%@", kind.rawValue)
321
321
  if firstFailure == nil {
322
322
  firstFailure = (
323
- "the XCTest capture channel is occupied by an abandoned tree capture",
323
+ "the XCTest capture channel is occupied by abandoned main-thread work",
324
324
  "budget"
325
325
  )
326
326
  }
@@ -431,6 +431,7 @@ extension RunnerTests {
431
431
  return nil
432
432
  }
433
433
  return try self.runMainThreadWork(
434
+ "tree_processing",
434
435
  timeout: min(self.treeCaptureSliceBudget, max(0.5, deadline.timeIntervalSinceNow)),
435
436
  timeoutError: self.snapshotMainThreadTimeoutError("processing tree snapshot")
436
437
  ) {
@@ -440,6 +441,7 @@ extension RunnerTests {
440
441
  }
441
442
  case .querySweep:
442
443
  return try self.runMainThreadWork(
444
+ "query_sweep",
443
445
  timeout: min(Self.flatInteractiveFallbackBudget, max(0.1, deadline.timeIntervalSinceNow)),
444
446
  timeoutError: self.snapshotMainThreadTimeoutError("running query-sweep snapshot")
445
447
  ) {
@@ -1169,42 +1171,6 @@ extension RunnerTests {
1169
1171
 
1170
1172
 
1171
1173
 
1172
-
1173
-
1174
-
1175
-
1176
-
1177
-
1178
-
1179
-
1180
-
1181
-
1182
-
1183
-
1184
-
1185
-
1186
-
1187
-
1188
-
1189
-
1190
-
1191
-
1192
-
1193
-
1194
-
1195
-
1196
-
1197
-
1198
-
1199
-
1200
-
1201
-
1202
-
1203
-
1204
-
1205
-
1206
-
1207
-
1208
1174
 
1209
1175
 
1210
1176
 
@@ -152,14 +152,7 @@ extension RunnerTests {
152
152
  self.deliverCommandResult(
153
153
  command: command,
154
154
  result: (
155
- self.jsonResponse(
156
- status: 500,
157
- response: self.errorResponse(
158
- code: "COMMAND_FAILED",
159
- message: error.localizedDescription,
160
- hint: "Check the runner log for XCTest details, then retry after the app is foregrounded if this was a timeout or activation failure."
161
- )
162
- ),
155
+ self.jsonResponse(status: 500, response: self.commandFailedResponse(from: error)),
163
156
  false
164
157
  ),
165
158
  completion: completion
@@ -274,7 +267,9 @@ extension RunnerTests {
274
267
 
275
268
  let stamped =
276
269
  response.ok
277
- ? response.stampingCurrentUptimeMs(ProcessInfo.processInfo.systemUptime * 1000)
270
+ ? response
271
+ .stampingCurrentUptimeMs(ProcessInfo.processInfo.systemUptime * 1000)
272
+ .stampingCurrentMainThreadBusy(currentMainThreadBusyState().reportsMainThreadBusy)
278
273
  : response
279
274
  let encoder = JSONEncoder()
280
275
  let body = (try? encoder.encode(stamped)).flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
@@ -285,6 +280,32 @@ extension RunnerTests {
285
280
  Response(ok: false, error: ErrorPayload(code: code, message: message, hint: hint))
286
281
  }
287
282
 
283
+
284
+
285
+
286
+
287
+ func commandFailedResponse(from error: Error) -> Response {
288
+ let nsError = error as NSError
289
+ if nsError.domain == RunnerErrorDomain.general,
290
+ nsError.code == RunnerErrorCode.mainThreadExecutionTimedOut
291
+ {
292
+ return Response(
293
+ ok: false,
294
+ error: ErrorPayload(
295
+ code: RunnerWireErrorCode.mainThreadTimeout,
296
+ message: nsError.localizedDescription,
297
+ hint:
298
+ "The runner abandoned this command's main-thread work past its execution watchdog and it is still draining. Wait and retry, or use a screenshot and interact by coordinates."
299
+ )
300
+ )
301
+ }
302
+ return errorResponse(
303
+ code: "COMMAND_FAILED",
304
+ message: error.localizedDescription,
305
+ hint: "Check the runner log for XCTest details, then retry after the app is foregrounded if this was a timeout or activation failure."
306
+ )
307
+ }
308
+
288
309
  private func httpResponse(status: Int, body: String) -> Data {
289
310
  let headers = [
290
311
  "HTTP/1.1 \(status) OK",
@@ -28,6 +28,13 @@ final class RunnerTests: XCTestCase {
28
28
  static let objcException = 1
29
29
  }
30
30
 
31
+
32
+
33
+
34
+ enum RunnerWireErrorCode {
35
+ static let mainThreadTimeout = "MAIN_THREAD_TIMEOUT"
36
+ }
37
+
31
38
  static let springboardBundleId = "com.apple.springboard"
32
39
 
33
40
 
@@ -59,7 +66,7 @@ final class RunnerTests: XCTestCase {
59
66
  let retryCooldown: TimeInterval = 0.2
60
67
  let postSnapshotInteractionDelay: TimeInterval = 0.2
61
68
  let firstInteractionAfterActivateDelay: TimeInterval = 0.25
62
- let scrollInteractionIdleTimeoutDefault: TimeInterval = 1.0
69
+ let interactionIdleTimeoutDefault: TimeInterval = 1.0
63
70
  let tvRemoteDoublePressDelayDefault: TimeInterval = 0.0
64
71
 
65
72
  let xctestIdleKeepaliveInterval: TimeInterval = 60.0
@@ -81,6 +88,8 @@ final class RunnerTests: XCTestCase {
81
88
  var inFlightCommandWaiters: [String: [((data: Data, shouldFinish: Bool)) -> Void]] = [:]
82
89
 
83
90
 
91
+
92
+
84
93
  let mainThreadWorkLock = NSLock()
85
94
  var abandonedMainThreadWorkCount = 0
86
95
  var abandonedMainThreadWorkSince: Date?
@@ -115,10 +124,6 @@ final class RunnerTests: XCTestCase {
115
124
 
116
125
 
117
126
 
118
-
119
-
120
- let treeCaptureLock = NSLock()
121
- var abandonedTreeCaptureCount = 0
122
127
  let treeCaptureSliceBudget: TimeInterval = 8
123
128
 
124
129
 
@@ -210,10 +215,20 @@ final class RunnerTests: XCTestCase {
210
215
 
211
216
 
212
217
 
218
+ static let axServerRejectionFetchWordings = [
219
+ "Failed to get matching snapshot",
220
+ "Failed to resolve query"
221
+ ]
222
+
223
+
224
+
225
+
226
+
213
227
 
214
228
 
215
229
  static func isSuppressedAxSnapshotIssueDescription(_ description: String) -> Bool {
216
- description.contains("Failed to get matching snapshot") && description.contains("kAXError")
230
+ guard description.contains("kAXError") else { return false }
231
+ return axServerRejectionFetchWordings.contains { description.contains($0) }
217
232
  }
218
233
 
219
234
 
@@ -224,6 +239,7 @@ final class RunnerTests: XCTestCase {
224
239
 
225
240
 
226
241
 
242
+
227
243
  override func record(_ issue: XCTIssue) {
228
244
  if containTextInputProbeIssue(issue) { return }
229
245
  let description = issue.compactDescription