agent-device 0.21.2 → 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 (164) hide show
  1. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.2.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.2.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.2.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/auth.js +1 -1
  31. package/dist/src/back-mode.js +1 -1
  32. package/dist/src/cli-help.js +2 -2
  33. package/dist/src/cli.js +4 -4
  34. package/dist/src/client-metro-companion.js +1 -1
  35. package/dist/src/client-metro.js +1 -1
  36. package/dist/src/client-types.d.ts +70 -5
  37. package/dist/src/code-signature-cache.js +1 -1
  38. package/dist/src/command-schema.js +1 -1
  39. package/dist/src/command-tools.js +2 -2
  40. package/dist/src/command.js +1 -1
  41. package/dist/src/config.js +1 -1
  42. package/dist/src/connection-runtime.js +1 -1
  43. package/dist/src/connection.js +1 -1
  44. package/dist/src/contracts.js +1 -1
  45. package/dist/src/daemon-client-lifecycle.js +1 -1
  46. package/dist/src/daemon-registration.js +1 -1
  47. package/dist/src/daemon-resolution.js +1 -0
  48. package/dist/src/daemon-shutdown-report.js +1 -1
  49. package/dist/src/daemon.js +1 -1
  50. package/dist/src/device-claim-rule.js +1 -1
  51. package/dist/src/device-input-state.d.ts +10 -8
  52. package/dist/src/device-inventory-context.js +1 -1
  53. package/dist/src/device-session.js +1 -1
  54. package/dist/src/diagnostics.js +1 -1
  55. package/dist/src/error.js +2 -2
  56. package/dist/src/exec.js +1 -1
  57. package/dist/src/file.js +1 -1
  58. package/dist/src/find.js +1 -1
  59. package/dist/src/gesture-runtime.js +1 -1
  60. package/dist/src/harmonyos.js +1 -1
  61. package/dist/src/helper.js +1 -1
  62. package/dist/src/human-control-contract.js +1 -1
  63. package/dist/src/human-control.js +1 -1
  64. package/dist/src/index.js +1 -1
  65. package/dist/src/install-source-network-transport.js +1 -1
  66. package/dist/src/interaction.js +1 -1
  67. package/dist/src/interaction2.js +1 -1
  68. package/dist/src/interactor.js +4 -4
  69. package/dist/src/interactors.js +1 -1
  70. package/dist/src/internal/bin.js +1 -1
  71. package/dist/src/internal/daemon.js +2 -2
  72. package/dist/src/internal/png-worker.js +1 -1
  73. package/dist/src/ios-client.js +121 -7
  74. package/dist/src/ios-snapshot-adapter.js +1 -1
  75. package/dist/src/ios-system-surface.js +1 -1
  76. package/dist/src/lease-scope.js +1 -1
  77. package/dist/src/limrun-delta-sync-omitted.js +1 -0
  78. package/dist/src/limrun.d.ts +0 -1598
  79. package/dist/src/location-coordinates.js +1 -1
  80. package/dist/src/mechanics.js +16 -16
  81. package/dist/src/output-common.js +2 -0
  82. package/dist/src/owner-identity.js +1 -1
  83. package/dist/src/perf-runtime-operation-builder.js +1 -1
  84. package/dist/src/perf-runtime-plan.js +1 -1
  85. package/dist/src/physical-device-control.js +1 -1
  86. package/dist/src/platform-request-scope.js +1 -1
  87. package/dist/src/platform-runtime-android-adb-host.js +1 -1
  88. package/dist/src/platform-runtime-app-log-android-transport.js +1 -1
  89. package/dist/src/platform-runtime-app-log-process.js +1 -1
  90. package/dist/src/platform-runtime-host.js +1 -1
  91. package/dist/src/platform-runtime-operation-host.js +2 -2
  92. package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
  93. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  94. package/dist/src/platform-runtime-screen-recording-harmony-host.js +1 -1
  95. package/dist/src/platform-runtime-screen-recording-output-host.js +1 -1
  96. package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
  97. package/dist/src/platform-runtime.js +1 -1
  98. package/dist/src/process.js +1 -1
  99. package/dist/src/provider-webdriver.js +1 -1
  100. package/dist/src/proxy.js +1 -1
  101. package/dist/src/quality-warnings.js +1 -1
  102. package/dist/src/react-native.js +1 -1
  103. package/dist/src/record-runtime.js +1 -1
  104. package/dist/src/recording-stop-observation.js +1 -0
  105. package/dist/src/register-builtins.js +1 -1
  106. package/dist/src/registry.js +35 -36
  107. package/dist/src/replay-divergence.js +1 -1
  108. package/dist/src/reporting.js +11 -9
  109. package/dist/src/request-providers.js +1 -1
  110. package/dist/src/result-serialization.js +1 -1
  111. package/dist/src/runner-cache-metadata.js +2 -2
  112. package/dist/src/runner-client.js +1 -1
  113. package/dist/src/runner-disposal.js +1 -1
  114. package/dist/src/runner-operations-facade.js +1 -1
  115. package/dist/src/runner.js +1 -1
  116. package/dist/src/runtime.js +3 -3
  117. package/dist/src/runtime10.js +1 -1
  118. package/dist/src/runtime2.js +4 -4
  119. package/dist/src/runtime3.js +1 -1
  120. package/dist/src/runtime4.js +5 -5
  121. package/dist/src/runtime5.js +1 -1
  122. package/dist/src/runtime6.js +1 -1
  123. package/dist/src/runtime9.js +1 -1
  124. package/dist/src/screen-recording-completion.js +1 -1
  125. package/dist/src/screen-recording-resource-recovery.js +1 -1
  126. package/dist/src/screen-recording-runtime.js +1 -1
  127. package/dist/src/screenshot-result.js +1 -1
  128. package/dist/src/screenshot-runtime.js +1 -1
  129. package/dist/src/sdk-batch.js +2 -2
  130. package/dist/src/sdk-contracts.js +2 -2
  131. package/dist/src/sdk-remote-config.d.ts +1 -0
  132. package/dist/src/sdk-remote-config.js +1 -1
  133. package/dist/src/sdk-selectors.d.ts +36 -24
  134. package/dist/src/selector-runtime.js +1 -1
  135. package/dist/src/session-observation.js +1 -1
  136. package/dist/src/session-routing.js +1 -1
  137. package/dist/src/session-snapshot.js +1 -1
  138. package/dist/src/session-store.js +2 -2
  139. package/dist/src/session2.js +5 -5
  140. package/dist/src/simulator.js +1 -1
  141. package/dist/src/snapshot-runtime-binding.js +1 -1
  142. package/dist/src/snapshot-runtime.js +1 -1
  143. package/dist/src/snapshot.js +1 -1
  144. package/dist/src/snapshot4.js +1 -1
  145. package/dist/src/src3.js +1 -1
  146. package/dist/src/src4.js +1 -1
  147. package/dist/src/stop-sequence.js +1 -0
  148. package/dist/src/success-text.js +1 -1
  149. package/dist/src/text-entry-focus.js +1 -0
  150. package/dist/src/tool-provider.js +1 -1
  151. package/dist/src/tool-provider2.js +1 -1
  152. package/dist/src/tool-provider3.js +1 -1
  153. package/dist/src/type-text-runtime.js +1 -1
  154. package/dist/src/video.js +2 -2
  155. package/dist/src/yauzl.js +1 -0
  156. package/package.json +2 -2
  157. package/server.json +2 -2
  158. package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.2.apk.sha256 +0 -1
  159. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.2.apk.sha256 +0 -1
  160. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.2.manifest.json +0 -17
  161. package/dist/src/audio-probe-support.js +0 -1
  162. package/dist/src/config2.js +0 -1
  163. package/dist/src/provider-device-runtime.js +0 -1
  164. package/dist/src/screen-recording-live-handle.js +0 -1
@@ -0,0 +1 @@
1
+ fdf9f5946ee579efe8ef31eb43865d19706dba5f78c75bf908a0e3daf6b7a163 agent-device-android-ime-helper-0.21.4.apk
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "android-ime-helper",
3
- "version": "0.21.2",
4
- "assetName": "agent-device-android-ime-helper-0.21.2.apk",
5
- "sha256": "17a3645b2baca245adaa37e025a89eb35da7c5c6d53ae2d2d84862474f402cf0",
3
+ "version": "0.21.4",
4
+ "assetName": "agent-device-android-ime-helper-0.21.4.apk",
5
+ "sha256": "fdf9f5946ee579efe8ef31eb43865d19706dba5f78c75bf908a0e3daf6b7a163",
6
6
  "packageName": "com.callstack.agentdevice.imehelper",
7
- "versionCode": 21002,
7
+ "versionCode": 21004,
8
8
  "serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
9
9
  "broadcastProtocol": "android-ime-helper-v1"
10
10
  }
@@ -0,0 +1 @@
1
+ 9fae32c14211310223ba7f6cdf19433d03f01d01e5dc24d1e5b3294cd80ad501 agent-device-android-snapshot-helper-0.21.4.apk
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "android-snapshot-helper",
3
+ "version": "0.21.4",
4
+ "releaseTag": "v0.21.4",
5
+ "assetName": "agent-device-android-snapshot-helper-0.21.4.apk",
6
+ "apkUrl": null,
7
+ "sha256": "9fae32c14211310223ba7f6cdf19433d03f01d01e5dc24d1e5b3294cd80ad501",
8
+ "checksumName": "agent-device-android-snapshot-helper-0.21.4.apk.sha256",
9
+ "packageName": "com.callstack.agentdevice.snapshothelper",
10
+ "versionCode": 21004,
11
+ "instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
12
+ "minSdk": 23,
13
+ "targetSdk": 36,
14
+ "outputFormat": "uiautomator-xml",
15
+ "statusProtocol": "android-snapshot-helper-v1"
16
+ }
@@ -62,11 +62,46 @@ int main(int argc, const char *argv[]) {
62
62
  @property(nonatomic, assign) NSUInteger firstAlertActions;
63
63
  @property(nonatomic, assign) NSUInteger replacementAlertActions;
64
64
  @property(nonatomic, assign) BOOL alertFixtureStarted;
65
+ @property(nonatomic, strong) NSTimer *alertActivationBusyBackstop;
65
66
  @end
66
67
 
67
68
  @implementation AgentDeviceRunnerViewController
68
69
 
69
70
  #if TARGET_OS_IOS
71
+ // An animation that never ends is what "busy" looks like to XCTest while it decides whether the app
72
+ // may receive an event: the app keeps reporting work in flight, which is the state that cost an alert
73
+ // command its whole deadline in #2546. It stops the moment an alert button is answered, since that
74
+ // answer is the event the runner is trying to land, and the backstop stops it even when no answer
75
+ // arrives so a regressed run finishes rather than waiting out XCTest's own timeout. A layer
76
+ // animation on its own is not enough; only a UIView animation counts as in-flight work here.
77
+ static NSTimeInterval const kAgentDeviceAlertActivationBusyWindow = 20.0;
78
+
79
+ - (void)startAlertActivationBusy {
80
+ if (self.alertActivationBusyBackstop != nil) {
81
+ return;
82
+ }
83
+ self.alertActivationBusyBackstop = [NSTimer scheduledTimerWithTimeInterval:kAgentDeviceAlertActivationBusyWindow
84
+ target:self
85
+ selector:@selector(stopAlertActivationBusy)
86
+ userInfo:nil
87
+ repeats:NO];
88
+ [UIView animateWithDuration:0.4
89
+ delay:0
90
+ options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse)
91
+ animations:^{
92
+ self.alertActionStatus.transform = CGAffineTransformMakeTranslation(0, 8);
93
+ }
94
+ completion:nil];
95
+ }
96
+
97
+ - (void)stopAlertActivationBusy {
98
+ [self.alertActionStatus.layer removeAllAnimations];
99
+ self.alertActionStatus.transform = CGAffineTransformIdentity;
100
+ [self.alertActivationBusyBackstop invalidate];
101
+ self.alertActivationBusyBackstop = nil;
102
+ }
103
+
104
+
70
105
  - (void)updateAlertActionStatus {
71
106
  self.alertActionStatus.text = [NSString stringWithFormat:@"First actions: %lu; replacement actions: %lu",
72
107
  (unsigned long)self.firstAlertActions,
@@ -88,6 +123,7 @@ int main(int argc, const char *argv[]) {
88
123
  ? UIAlertActionStyleCancel : UIAlertActionStyleDefault;
89
124
  [alert addAction:[UIAlertAction actionWithTitle:buttonTitle style:style handler:^(UIAlertAction *action) {
90
125
  (void)action;
126
+ [self stopAlertActivationBusy];
91
127
  if (replacement) {
92
128
  self.replacementAlertActions += 1;
93
129
  } else {
@@ -110,6 +146,9 @@ int main(int argc, const char *argv[]) {
110
146
  [NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-replacement-regression"]) {
111
147
  self.alertFixtureStarted = YES;
112
148
  [self presentAlertFixtureReplacement:NO];
149
+ if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-alert-activation-busy"]) {
150
+ [self startAlertActivationBusy];
151
+ }
113
152
  }
114
153
  }
115
154
  #endif
@@ -4,8 +4,10 @@ import Foundation
4
4
 
5
5
 
6
6
 
7
+
7
8
  enum SystemSurfaceHostKind: String {
8
9
  case webAuth = "web-auth"
10
+ case payment = "payment"
9
11
  }
10
12
 
11
13
  struct SystemSurfaceHost: Equatable {
@@ -15,7 +17,8 @@ struct SystemSurfaceHost: Equatable {
15
17
 
16
18
  enum SystemSurfaceHostRegistry {
17
19
  static let hosts: [SystemSurfaceHost] = [
18
- SystemSurfaceHost(bundleId: "com.apple.SafariViewService", kind: .webAuth)
20
+ SystemSurfaceHost(bundleId: "com.apple.SafariViewService", kind: .webAuth),
21
+ SystemSurfaceHost(bundleId: "com.apple.PassbookUIService", kind: .payment),
19
22
  ]
20
23
 
21
24
  static func host(forBundleId bundleId: String?) -> SystemSurfaceHost? {
@@ -64,6 +67,11 @@ enum SystemSurfaceHostRegistry {
64
67
 
65
68
 
66
69
 
70
+
71
+
72
+
73
+
74
+
67
75
 
68
76
 
69
77
 
@@ -266,7 +266,7 @@ extension RunnerTests {
266
266
 
267
267
 
268
268
  func shouldReadPrivateAXViewportViaXCTest() -> Bool {
269
- !hasAbandonedTreeCapture() && !isSnapshotXCTestChannelPenalized(bundleId: currentBundleId)
269
+ !hasAbandonedMainThreadWork() && !isSnapshotXCTestChannelPenalized(bundleId: currentBundleId)
270
270
  }
271
271
 
272
272
  private func privateAXSnapshotViewport(app: XCUIApplication, rootFrame: CGRect) -> CGRect {
@@ -276,6 +276,7 @@ extension RunnerTests {
276
276
  }
277
277
  do {
278
278
  let viewport = try runMainThreadWork(
279
+ "private_ax_viewport",
279
280
  timeout: 1,
280
281
  timeoutError: snapshotMainThreadTimeoutError("reading private AX viewport")
281
282
  ) {
@@ -68,7 +68,16 @@ extension RunnerTests {
68
68
  guard waitUntilAlertButtonHittable(button, deadline: deadline) else {
69
69
  return alertVerificationResponse(.timedOut, action: action, activated: false)
70
70
  }
71
- let outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
71
+
72
+
73
+
74
+
75
+
76
+
77
+ var outcome = RunnerInteractionOutcome.performed
78
+ withBoundedInteractionIdleTimeoutIfSupported(alert.ownerApp, waits: .preEventSkipped) {
79
+ outcome = activateElement(app: alert.ownerApp, element: button, action: "alert \(action)")
80
+ }
72
81
  if let response = unsupportedResponse(for: outcome) {
73
82
  return response
74
83
  }
@@ -97,7 +97,9 @@ extension RunnerTests {
97
97
  var outcome = RunnerInteractionOutcome.performed
98
98
  let timing = measureGesture {
99
99
  if idleTimeout {
100
- withTemporaryScrollIdleTimeoutIfSupported(app) { outcome = action() }
100
+ withBoundedInteractionIdleTimeoutIfSupported(app, waits: .bothSkipped) {
101
+ outcome = action()
102
+ }
101
103
  } else {
102
104
  outcome = action()
103
105
  }
@@ -896,29 +898,6 @@ extension RunnerTests {
896
898
 
897
899
 
898
900
 
899
-
900
-
901
-
902
-
903
-
904
-
905
-
906
-
907
-
908
-
909
-
910
-
911
-
912
-
913
-
914
-
915
-
916
-
917
-
918
-
919
-
920
-
921
-
922
901
 
923
902
 
924
903
 
@@ -977,13 +956,6 @@ extension RunnerTests {
977
956
  )
978
957
  }
979
958
 
980
-
981
-
982
- private final class MainThreadWorkState {
983
- var finished = false
984
- var abandoned = false
985
- }
986
-
987
959
  struct ActiveCommandContext {
988
960
  let app: XCUIApplication
989
961
 
@@ -995,23 +967,6 @@ extension RunnerTests {
995
967
  case context(ActiveCommandContext)
996
968
  }
997
969
 
998
- enum MainThreadBusyState {
999
- case idle
1000
- case busy(abandonedForSeconds: TimeInterval)
1001
- case wedged(abandonedForSeconds: TimeInterval)
1002
- }
1003
-
1004
- func currentMainThreadBusyState() -> MainThreadBusyState {
1005
- mainThreadWorkLock.lock()
1006
- defer { mainThreadWorkLock.unlock() }
1007
- guard abandonedMainThreadWorkCount > 0 else { return .idle }
1008
- let abandonedFor = abandonedMainThreadWorkSince.map { Date().timeIntervalSince($0) } ?? 0
1009
- if abandonedFor > mainThreadWedgeThreshold {
1010
- return .wedged(abandonedForSeconds: abandonedFor)
1011
- }
1012
- return .busy(abandonedForSeconds: abandonedFor)
1013
- }
1014
-
1015
970
  private func runnerBusyResponse(command: Command, abandonedForSeconds: TimeInterval) -> Response {
1016
971
  NSLog(
1017
972
  "AGENT_DEVICE_RUNNER_BUSY command=%@ commandId=%@ abandonedForSeconds=%.1f",
@@ -1092,6 +1047,7 @@ extension RunnerTests {
1092
1047
  }
1093
1048
  if command.command == .alert, let deadline = alertDeadline {
1094
1049
  return try runMainThreadWork(
1050
+ "command_execution",
1095
1051
  timeout: max(0.001, deadline.timeIntervalSinceNow),
1096
1052
  timeoutError: mainThreadExecutionTimeoutError
1097
1053
  ) {
@@ -1103,6 +1059,7 @@ extension RunnerTests {
1103
1059
  }
1104
1060
  }
1105
1061
  return try runMainThreadWork(
1062
+ "command_execution",
1106
1063
  timeout: mainThreadExecutionTimeout,
1107
1064
  timeoutError: mainThreadExecutionTimeoutError
1108
1065
  ) {
@@ -1110,77 +1067,6 @@ extension RunnerTests {
1110
1067
  }
1111
1068
  }
1112
1069
 
1113
- func runMainThreadWork<T>(
1114
- timeout: TimeInterval,
1115
- timeoutError: @escaping () -> Error,
1116
- onAbandoned: (() -> Void)? = nil,
1117
- onDrained: (() -> Void)? = nil,
1118
- _ work: @escaping () throws -> T
1119
- ) throws -> T {
1120
- if Thread.isMainThread {
1121
- return try work()
1122
- }
1123
- var result: Result<T, Error>?
1124
- let semaphore = DispatchSemaphore(value: 0)
1125
- let workState = MainThreadWorkState()
1126
- DispatchQueue.main.async {
1127
- do {
1128
- result = .success(try work())
1129
- } catch {
1130
- result = .failure(error)
1131
- }
1132
- self.mainThreadWorkLock.lock()
1133
- if workState.abandoned {
1134
- self.abandonedMainThreadWorkCount -= 1
1135
- if self.abandonedMainThreadWorkCount == 0 {
1136
- self.abandonedMainThreadWorkSince = nil
1137
- NSLog("AGENT_DEVICE_RUNNER_ABANDONED_WORK_DRAINED")
1138
- }
1139
- self.mainThreadWorkLock.unlock()
1140
- onDrained?()
1141
- } else {
1142
- workState.finished = true
1143
- self.mainThreadWorkLock.unlock()
1144
- }
1145
- semaphore.signal()
1146
- }
1147
- let waitResult = semaphore.wait(timeout: .now() + timeout)
1148
- if waitResult == .timedOut {
1149
- mainThreadWorkLock.lock()
1150
- let stillRunning = !workState.finished
1151
- if stillRunning {
1152
- workState.abandoned = true
1153
- abandonedMainThreadWorkCount += 1
1154
- if abandonedMainThreadWorkSince == nil {
1155
- abandonedMainThreadWorkSince = Date()
1156
- }
1157
- onAbandoned?()
1158
- }
1159
- mainThreadWorkLock.unlock()
1160
- throw timeoutError()
1161
- }
1162
- switch result {
1163
- case .success(let value):
1164
- return value
1165
- case .failure(let error):
1166
- throw error
1167
- case .none:
1168
- throw NSError(
1169
- domain: RunnerErrorDomain.general,
1170
- code: RunnerErrorCode.noResponseFromMainThread,
1171
- userInfo: [NSLocalizedDescriptionKey: "no response from main thread"]
1172
- )
1173
- }
1174
- }
1175
-
1176
- private func mainThreadExecutionTimeoutError() -> Error {
1177
- NSError(
1178
- domain: RunnerErrorDomain.general,
1179
- code: RunnerErrorCode.mainThreadExecutionTimedOut,
1180
- userInfo: [NSLocalizedDescriptionKey: "main thread execution timed out"]
1181
- )
1182
- }
1183
-
1184
1070
 
1185
1071
 
1186
1072
  private func executeOnMainSafely(
@@ -1282,6 +1168,7 @@ extension RunnerTests {
1282
1168
  var hasRetried = false
1283
1169
  while true {
1284
1170
  let failureCountBefore = try runMainThreadWork(
1171
+ "recorded_failure_count",
1285
1172
  timeout: mainThreadExecutionTimeout,
1286
1173
  timeoutError: mainThreadExecutionTimeoutError
1287
1174
  ) {
@@ -1290,7 +1177,7 @@ extension RunnerTests {
1290
1177
  let response = try perform()
1291
1178
 
1292
1179
 
1293
- if hasAbandonedTreeCapture() {
1180
+ if hasAbandonedMainThreadWork() {
1294
1181
  NSLog(
1295
1182
  "AGENT_DEVICE_RUNNER_DISPATCH_RECOVERY_SKIPPED_XCTEST_OCCUPIED command=%@",
1296
1183
  command.command.rawValue
@@ -1298,6 +1185,7 @@ extension RunnerTests {
1298
1185
  return response
1299
1186
  }
1300
1187
  let recordedFailureResponse = try runMainThreadWork(
1188
+ "recorded_failure_count",
1301
1189
  timeout: mainThreadExecutionTimeout,
1302
1190
  timeoutError: mainThreadExecutionTimeoutError
1303
1191
  ) {
@@ -1307,6 +1195,7 @@ extension RunnerTests {
1307
1195
  }
1308
1196
  if let recordedFailureResponse {
1309
1197
  try runMainThreadWork(
1198
+ "target_invalidation",
1310
1199
  timeout: mainThreadExecutionTimeout,
1311
1200
  timeoutError: mainThreadExecutionTimeoutError
1312
1201
  ) {
@@ -1321,6 +1210,7 @@ extension RunnerTests {
1321
1210
  )
1322
1211
  hasRetried = true
1323
1212
  try runMainThreadWork(
1213
+ "target_invalidation",
1324
1214
  timeout: mainThreadExecutionTimeout,
1325
1215
  timeoutError: mainThreadExecutionTimeoutError
1326
1216
  ) {
@@ -1335,6 +1225,7 @@ extension RunnerTests {
1335
1225
 
1336
1226
  private func executeSnapshotDispatchedOnce(command: Command) throws -> Response {
1337
1227
  let preparation = try runMainThreadWork(
1228
+ "command_preparation",
1338
1229
  timeout: mainThreadExecutionTimeout,
1339
1230
  timeoutError: mainThreadExecutionTimeoutError
1340
1231
  ) {
@@ -1409,12 +1300,13 @@ extension RunnerTests {
1409
1300
  needsPostSnapshotInteractionDelay = true
1410
1301
  return
1411
1302
  }
1412
- guard !hasAbandonedTreeCapture() else {
1303
+ guard !hasAbandonedMainThreadWork() else {
1413
1304
  NSLog("AGENT_DEVICE_RUNNER_POST_SNAPSHOT_DELAY_MARK_SKIPPED_XCTEST_OCCUPIED")
1414
1305
  return
1415
1306
  }
1416
1307
  do {
1417
1308
  try runMainThreadWork(
1309
+ "post_snapshot_delay_mark",
1418
1310
  timeout: 1,
1419
1311
  timeoutError: mainThreadExecutionTimeoutError
1420
1312
  ) {
@@ -1430,8 +1322,18 @@ extension RunnerTests {
1430
1322
  invalidateCachedTarget(reason: "ax_snapshot_failure")
1431
1323
  return
1432
1324
  }
1325
+
1326
+
1327
+ guard !hasAbandonedMainThreadWork() else {
1328
+ NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_INVALIDATION_DEFERRED_XCTEST_OCCUPIED")
1329
+ DispatchQueue.main.async {
1330
+ self.invalidateCachedTarget(reason: "ax_snapshot_failure")
1331
+ }
1332
+ return
1333
+ }
1433
1334
  do {
1434
1335
  try runMainThreadWork(
1336
+ "target_invalidation",
1435
1337
  timeout: 1,
1436
1338
  timeoutError: mainThreadExecutionTimeoutError
1437
1339
  ) {
@@ -2075,7 +1977,7 @@ extension RunnerTests {
2075
1977
  return Response(ok: true, data: DataPayload(message: "remote pressed"))
2076
1978
  case .type:
2077
1979
  var response: Response?
2078
- withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
1980
+ withBoundedInteractionIdleTimeoutIfSupported(activeApp, waits: .bothSkipped) {
2079
1981
  response = executeTypeCommand(activeApp: activeApp, command: command)
2080
1982
  }
2081
1983
  return response ?? Response(ok: false, error: ErrorPayload(message: "type produced no response"))
@@ -2087,7 +1989,7 @@ extension RunnerTests {
2087
1989
 
2088
1990
  var executedFrame: DragVisualizationFrame?
2089
1991
  let timing = measureGesture {
2090
- withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
1992
+ withBoundedInteractionIdleTimeoutIfSupported(activeApp, waits: .bothSkipped) {
2091
1993
  executedFrame = swipe(app: activeApp, direction: direction)
2092
1994
  }
2093
1995
  }
@@ -2187,7 +2089,7 @@ extension RunnerTests {
2187
2089
  code: "UNSUPPORTED_OPERATION",
2188
2090
  message: "Unable to dismiss the iOS keyboard: the keyboard exposes no dismiss key, and background taps are never attempted (no tap outside the keyboard can be proven side-effect-free)",
2189
2091
  hint:
2190
- "The on-screen keyboard usually does not block agent-device interactions: press the next target directly instead of retrying dismiss. If that press fails or reports no visible effect, scroll the target into view, or use keyboard enter to press the return key when submission is wanted."
2092
+ "An element whose center sits behind the on-screen keyboard is refused with tap_keyboard_occludes_target; one whose center stays above the keys presses normally. To end editing, tap the app's own Done/Cancel control, or use keyboard enter to press the return key when submission is wanted."
2191
2093
  )
2192
2094
  )
2193
2095
  }
@@ -21,6 +21,24 @@ func runnerCGImage(from image: RunnerImage) -> CGImage? {
21
21
  #endif
22
22
  }
23
23
 
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+ enum RunnerInteractionIdleWaits {
32
+
33
+
34
+
35
+ case bothSkipped
36
+
37
+
38
+
39
+ case preEventSkipped
40
+ }
41
+
24
42
  extension RunnerTests {
25
43
 
26
44
 
@@ -209,8 +227,14 @@ extension RunnerTests {
209
227
  return target
210
228
  }
211
229
 
212
- func withTemporaryScrollIdleTimeoutIfSupported(
230
+
231
+
232
+
233
+
234
+
235
+ func withBoundedInteractionIdleTimeoutIfSupported(
213
236
  _ target: XCUIApplication,
237
+ waits: RunnerInteractionIdleWaits,
214
238
  operation: () -> Void
215
239
  ) {
216
240
  let setter = NSSelectorFromString("setWaitForIdleTimeout:")
@@ -219,19 +243,20 @@ extension RunnerTests {
219
243
  ? (target.value(forKey: "waitForIdleTimeout") as? NSNumber)
220
244
  : nil
221
245
  if supportsWaitForIdleTimeout {
222
- target.setValue(scrollInteractionIdleTimeoutDefault, forKey: "waitForIdleTimeout")
246
+ target.setValue(interactionIdleTimeoutDefault, forKey: "waitForIdleTimeout")
223
247
  }
224
248
  defer {
225
249
  if let previous {
226
250
  target.setValue(previous.doubleValue, forKey: "waitForIdleTimeout")
227
251
  }
228
252
  }
229
- performWithQuiescenceSkippedIfSupported(target, operation: operation)
253
+ performWithQuiescenceSkippedIfSupported(target, waits: waits, operation: operation)
230
254
  }
231
255
 
232
256
 
233
257
  private func performWithQuiescenceSkippedIfSupported(
234
258
  _ target: XCUIApplication,
259
+ waits: RunnerInteractionIdleWaits,
235
260
  operation: () -> Void
236
261
  ) {
237
262
  let selector = NSSelectorFromString("_performWithInteractionOptions:block:")
@@ -252,12 +277,19 @@ extension RunnerTests {
252
277
  )
253
278
  let skipPreEventQuiescence = UInt(1)
254
279
  let skipPostEventQuiescence = UInt(2)
280
+ let options: UInt
281
+ switch waits {
282
+ case .bothSkipped:
283
+ options = skipPreEventQuiescence | skipPostEventQuiescence
284
+ case .preEventSkipped:
285
+ options = skipPreEventQuiescence
286
+ }
255
287
  withoutActuallyEscaping(operation) { escapableOperation in
256
288
  let block: @convention(block) () -> Void = escapableOperation
257
289
  performWithOptions(
258
290
  target,
259
291
  selector,
260
- skipPreEventQuiescence | skipPostEventQuiescence,
292
+ options,
261
293
  block
262
294
  )
263
295
  }