agent-device 0.21.12 → 0.21.13
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.
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.12.apk → agent-device-android-ime-helper-0.21.13.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.13.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.12.manifest.json → agent-device-android-ime-helper-0.21.13.manifest.json} +4 -4
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.12.apk → agent-device-android-snapshot-helper-0.21.13.apk} +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.13.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.12.manifest.json → agent-device-android-snapshot-helper-0.21.13.manifest.json} +6 -6
- package/apple/macos-helper/Package.swift +4 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +3 -6
- package/apple/macos-helper/Tests/AgentDeviceMacOSHelperTests/ScreenshotResponseTests.swift +15 -0
- package/apple/snapshot-bridge/SnapshotBridgeRuntime.m +47 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +99 -42
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/Info.plist +23 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +17 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAppScreenCapture.swift +21 -13
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerScrollViewportPolicy.swift +1 -162
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +0 -9
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +0 -56
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSystemSurfaceHostPolicy.swift +0 -48
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +0 -50
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +22 -568
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +21 -9
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +0 -36
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandDispatch.swift +630 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +18 -2236
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +1 -338
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -12
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+GestureExecution.swift +165 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +0 -24
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +0 -142
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +90 -39
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+MainThreadWork.swift +78 -106
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +23 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +147 -95
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +0 -209
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +86 -10
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollDragExecution.swift +287 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +1 -200
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +0 -185
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +75 -285
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotAcquisition.swift +23 -10
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotBackendCapabilities.swift +0 -78
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +83 -578
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCaptureTarget.swift +106 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotExecution.swift +142 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTiming.swift +49 -78
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +0 -44
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +27 -97
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +1 -11
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +11 -87
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +2 -10
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +1 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +0 -35
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputCandidatePolicy.swift +0 -31
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +2 -18
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +34 -52
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -22
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +142 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +22 -6
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +140 -20
- package/dist/src/absence-observation-errors.js +1 -1
- package/dist/src/adapter.js +1 -1
- package/dist/src/agent-device-client.js +1 -1
- package/dist/src/alert-runtime.js +1 -1
- package/dist/src/android-adb.d.ts +1 -1
- package/dist/src/app-launch.js +2 -2
- package/dist/src/app-log-runtime2.js +3 -3
- package/dist/src/app-resolution.js +2 -2
- package/dist/src/back-mode.js +1 -1
- package/dist/src/capture-disclosure.js +1 -1
- package/dist/src/capture.js +1 -1
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +6 -5
- package/dist/src/cli-runner.js +1 -1
- package/dist/src/cli.js +1 -1
- package/dist/src/client-types.d.ts +4 -5
- package/dist/src/code-signature.js +1 -1
- package/dist/src/command-schema.js +3 -3
- package/dist/src/command-tools.js +3 -3
- package/dist/src/connection.js +1 -1
- package/dist/src/daemon-client-lifecycle.js +1 -1
- package/dist/src/daemon-resolution.js +1 -1
- package/dist/src/device-claim-rule.js +2 -2
- package/dist/src/device-input-state.d.ts +1 -1
- package/dist/src/device-session.js +1 -1
- package/dist/src/devicectl.js +1 -1
- package/dist/src/display-inventory.js +1 -1
- package/dist/src/doctor-facade.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/gesture-plan-types.d.ts +38 -0
- package/dist/src/harmonyos.js +1 -1
- package/dist/src/helper.js +1 -1
- package/dist/src/host.js +1 -1
- package/dist/src/install-source-network-transport.js +1 -1
- package/dist/src/install-source2.js +1 -1
- package/dist/src/interaction-ref-policy.js +1 -1
- package/dist/src/interaction2.js +1 -1
- package/dist/src/interactor-operation-catalog.js +1 -1
- package/dist/src/interactor.js +4 -4
- package/dist/src/interactor2.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +2 -2
- package/dist/src/inventory2.js +1 -1
- package/dist/src/ios-snapshot-acquisition.js +1 -1
- package/dist/src/ios-snapshot-runtime.js +1 -1
- package/dist/src/limrun-runtime-dependencies.js +1 -1
- package/dist/src/limrun.d.ts +435 -4
- package/dist/src/linux.js +1 -1
- package/dist/src/local-interactor-operation-set.js +1 -1
- package/dist/src/managed-device-scope.js +1 -1
- package/dist/src/mechanics.js +4 -4
- package/dist/src/mobile-snapshot-semantics.js +1 -1
- package/dist/src/perf-facade.js +1 -2
- package/dist/src/perf-runtime-plan.js +1 -1
- package/dist/src/perf-target.js +2 -0
- package/dist/src/physical-device-apps.js +1 -0
- package/dist/src/physical-device-control.js +1 -1
- package/dist/src/platform-request-scope.js +1 -1
- package/dist/src/platform-runtime-operation-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
- package/dist/src/platform-runtime-unavailable.js +1 -1
- package/dist/src/platform-runtime.js +1 -1
- package/dist/src/platform-runtime2.js +1 -1
- package/dist/src/provider-webdriver.js +1 -1
- package/dist/src/proxy.js +1 -1
- package/dist/src/quality-warnings.js +1 -1
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-runtime.js +1 -1
- package/dist/src/registry.js +35 -35
- package/dist/src/request-admission.js +1 -1
- package/dist/src/request-runtime-binding.js +1 -1
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-dev-tools-security.js +1 -1
- package/dist/src/runner-device-readiness.js +1 -1
- package/dist/src/runner-disposal.js +1 -1
- package/dist/src/runner-error-classification.js +3 -0
- package/dist/src/runner-operations-facade.js +1 -1
- package/dist/src/runner-presentation.js +1 -1
- package/dist/src/runner.js +1 -1
- package/dist/src/runtime.js +3 -3
- package/dist/src/runtime10.js +1 -1
- package/dist/src/runtime2.js +2 -2
- package/dist/src/runtime4.js +5 -5
- package/dist/src/runtime5.js +1 -1
- package/dist/src/runtime6.js +1 -1
- package/dist/src/runtime7.js +1 -1
- package/dist/src/runtime8.js +1 -1
- package/dist/src/runtime9.js +1 -1
- package/dist/src/screen-recording-admission-ledger.js +1 -1
- package/dist/src/screen-recording-resource-recovery.js +1 -1
- package/dist/src/screen-recording-transport.js +1 -1
- package/dist/src/screenshot-runtime.js +1 -1
- package/dist/src/screenshot3.js +1 -1
- package/dist/src/script.js +1 -1
- package/dist/src/scroll-command.d.ts +377 -0
- package/dist/src/scroll-command.js +1 -1
- package/dist/src/sdk-batch.js +2 -2
- package/dist/src/sdk-contracts.d.ts +9 -0
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-selectors.d.ts +23 -726
- package/dist/src/selection.js +0 -0
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/semantic-index.js +1 -1
- package/dist/src/server.js +1 -1
- package/dist/src/session-observation.js +1 -1
- package/dist/src/session-snapshot.js +1 -1
- package/dist/src/session2.js +2 -2
- package/dist/src/simctl-facade.js +1 -1
- package/dist/src/simctl.js +1 -1
- package/dist/src/simulator-state.js +1 -1
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot-runtime-binding.js +1 -1
- package/dist/src/snapshot-runtime.js +1 -1
- package/dist/src/snapshot-state.js +1 -1
- package/dist/src/snapshot4.js +1 -1
- package/dist/src/src7.js +1 -1
- package/dist/src/text-entry-focus.js +1 -1
- package/dist/src/tool-provider.js +1 -1
- package/dist/src/touch-runtime.js +1 -1
- package/dist/src/tree.js +1 -1
- package/dist/src/update-check.js +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/web2.js +1 -1
- package/package.json +3 -2
- package/server.json +2 -2
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.12.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.12.apk.sha256 +0 -1
- package/dist/src/app-inventory.d.ts +0 -4
- package/dist/src/apps-simctl.js +0 -1
- package/dist/src/cloud-artifacts.d.ts +0 -61
- package/dist/src/runner-contract.js +0 -3
|
@@ -1,1629 +1,7 @@
|
|
|
1
1
|
import XCTest
|
|
2
2
|
import AgentDeviceSnapshotPresentation
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
extension RunnerTests {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
private func currentUptimeMs() -> Double {
|
|
34
|
-
ProcessInfo.processInfo.systemUptime * 1000
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private func measureGesture(_ action: () -> Void) -> (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double) {
|
|
38
|
-
let gestureStartUptimeMs = currentUptimeMs()
|
|
39
|
-
action()
|
|
40
|
-
return (gestureStartUptimeMs, currentUptimeMs())
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
func synthesizedSwipeFallbackHoldDuration(durationMs: Double) -> TimeInterval {
|
|
44
|
-
min(max((durationMs / 5.0) / 1000.0, 0.016), 0.120)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func coordinateDragHoldDuration() -> TimeInterval {
|
|
48
|
-
0.050
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
func unsupportedResponse(for outcome: RunnerInteractionOutcome) -> Response? {
|
|
52
|
-
switch outcome {
|
|
53
|
-
case .performed:
|
|
54
|
-
return nil
|
|
55
|
-
case .unsupported(let message, let hint):
|
|
56
|
-
return Response(
|
|
57
|
-
ok: false,
|
|
58
|
-
error: ErrorPayload(code: "UNSUPPORTED_OPERATION", message: message, hint: hint)
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
enum GestureFrame {
|
|
65
|
-
case none
|
|
66
|
-
case touch(TouchVisualizationFrame?)
|
|
67
|
-
case drag(DragVisualizationFrame)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
struct GestureFallback {
|
|
71
|
-
let strategy: String
|
|
72
|
-
let message: String
|
|
73
|
-
let hint: String?
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private func gestureFallback(strategy: String, from outcome: RunnerInteractionOutcome) -> GestureFallback? {
|
|
77
|
-
switch outcome {
|
|
78
|
-
case .performed:
|
|
79
|
-
return nil
|
|
80
|
-
case .unsupported(let message, let hint):
|
|
81
|
-
return GestureFallback(strategy: strategy, message: message, hint: hint)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
func performGesture(
|
|
94
|
-
_ app: XCUIApplication,
|
|
95
|
-
idleTimeout: Bool = true,
|
|
96
|
-
_ action: () -> RunnerInteractionOutcome
|
|
97
|
-
) -> (timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double), outcome: RunnerInteractionOutcome) {
|
|
98
|
-
var outcome = RunnerInteractionOutcome.performed
|
|
99
|
-
let timing = measureGesture {
|
|
100
|
-
if idleTimeout {
|
|
101
|
-
withBoundedInteractionIdleTimeoutIfSupported(app, waits: .bothSkipped) {
|
|
102
|
-
outcome = action()
|
|
103
|
-
}
|
|
104
|
-
} else {
|
|
105
|
-
outcome = action()
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return (timing, outcome)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
private func gestureResponse(
|
|
114
|
-
message: String,
|
|
115
|
-
timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double),
|
|
116
|
-
frame: GestureFrame = .none,
|
|
117
|
-
fallback: GestureFallback? = nil,
|
|
118
|
-
maestroNonHittableCoordinateFallbackUsed: Bool? = nil
|
|
119
|
-
) -> Response {
|
|
120
|
-
let data: DataPayload
|
|
121
|
-
switch frame {
|
|
122
|
-
case .none:
|
|
123
|
-
data = DataPayload(
|
|
124
|
-
message: message,
|
|
125
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
126
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
127
|
-
gestureFallback: fallback?.strategy,
|
|
128
|
-
gestureFallbackMessage: fallback?.message,
|
|
129
|
-
gestureFallbackHint: fallback?.hint,
|
|
130
|
-
maestroNonHittableCoordinateFallbackUsed: maestroNonHittableCoordinateFallbackUsed
|
|
131
|
-
)
|
|
132
|
-
case .touch(let f):
|
|
133
|
-
data = DataPayload(
|
|
134
|
-
message: message,
|
|
135
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
136
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
137
|
-
x: f?.x,
|
|
138
|
-
y: f?.y,
|
|
139
|
-
referenceWidth: f?.referenceWidth,
|
|
140
|
-
referenceHeight: f?.referenceHeight,
|
|
141
|
-
gestureFallback: fallback?.strategy,
|
|
142
|
-
gestureFallbackMessage: fallback?.message,
|
|
143
|
-
gestureFallbackHint: fallback?.hint,
|
|
144
|
-
maestroNonHittableCoordinateFallbackUsed: maestroNonHittableCoordinateFallbackUsed
|
|
145
|
-
)
|
|
146
|
-
case .drag(let f):
|
|
147
|
-
data = DataPayload(
|
|
148
|
-
message: message,
|
|
149
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
150
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
151
|
-
x: f.x,
|
|
152
|
-
y: f.y,
|
|
153
|
-
x2: f.x2,
|
|
154
|
-
y2: f.y2,
|
|
155
|
-
referenceWidth: f.referenceWidth,
|
|
156
|
-
referenceHeight: f.referenceHeight,
|
|
157
|
-
gestureFallback: fallback?.strategy,
|
|
158
|
-
gestureFallbackMessage: fallback?.message,
|
|
159
|
-
gestureFallbackHint: fallback?.hint
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
return Response(ok: true, data: data)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
private func canonicalPlannedGestureResponse(_ response: Response) -> Response {
|
|
169
|
-
guard response.ok, let data = response.data else { return response }
|
|
170
|
-
return Response(
|
|
171
|
-
ok: true,
|
|
172
|
-
data: DataPayload(
|
|
173
|
-
message: data.message,
|
|
174
|
-
gestureStartUptimeMs: data.gestureStartUptimeMs,
|
|
175
|
-
gestureEndUptimeMs: data.gestureEndUptimeMs,
|
|
176
|
-
gestureFallback: data.gestureFallback,
|
|
177
|
-
gestureFallbackMessage: data.gestureFallbackMessage,
|
|
178
|
-
gestureFallbackHint: data.gestureFallbackHint
|
|
179
|
-
)
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private func plannedGestureResponse(
|
|
184
|
-
plan: RunnerGesturePlan,
|
|
185
|
-
timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double),
|
|
186
|
-
outcome: RunnerInteractionOutcome
|
|
187
|
-
) -> Response {
|
|
188
|
-
if let response = unsupportedResponse(for: outcome) {
|
|
189
|
-
return response
|
|
190
|
-
}
|
|
191
|
-
return gestureResponse(message: plan.intent, timing: timing)
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
func executeAccepted(command: Command) throws -> Response {
|
|
952
|
-
commandJournal.start(command: command)
|
|
953
|
-
pendingTargetActivation = nil
|
|
954
|
-
do {
|
|
955
|
-
let response = try executeDispatched(command: command)
|
|
956
|
-
commandJournal.finish(command: command, response: response)
|
|
957
|
-
guard let fact = pendingTargetActivation else { return response }
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
pendingTargetActivation = nil
|
|
962
|
-
return response.stampingTargetActivation(fact)
|
|
963
|
-
} catch {
|
|
964
|
-
pendingTargetActivation = nil
|
|
965
|
-
commandJournal.fail(command: command, error: error)
|
|
966
|
-
throw error
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
func executeStatus(command: Command) -> Response {
|
|
971
|
-
guard
|
|
972
|
-
let statusCommandId = command.statusCommandId?.trimmedNonEmpty
|
|
973
|
-
else {
|
|
974
|
-
return Response(
|
|
975
|
-
ok: false,
|
|
976
|
-
error: ErrorPayload(
|
|
977
|
-
code: "INVALID_ARGS",
|
|
978
|
-
message: "status requires statusCommandId",
|
|
979
|
-
hint: "Set statusCommandId to the commandId of the runner command to inspect."
|
|
980
|
-
)
|
|
981
|
-
)
|
|
982
|
-
}
|
|
983
|
-
return Response(ok: true, data: commandJournal.status(normalizedCommandId: statusCommandId))
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
func executeUptime() -> Response {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
Response(
|
|
990
|
-
ok: true,
|
|
991
|
-
data: DataPayload(currentUptimeMs: currentUptimeMs())
|
|
992
|
-
)
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
struct ActiveCommandContext {
|
|
996
|
-
let app: XCUIApplication
|
|
997
|
-
|
|
998
|
-
var systemSurface: SystemSurfaceHost? = nil
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
enum ActiveCommandPreparation {
|
|
1002
|
-
case response(Response)
|
|
1003
|
-
case context(ActiveCommandContext)
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
private func runnerBusyResponse(command: Command, abandonedForSeconds: TimeInterval) -> Response {
|
|
1007
|
-
NSLog(
|
|
1008
|
-
"AGENT_DEVICE_RUNNER_BUSY command=%@ commandId=%@ abandonedForSeconds=%.1f",
|
|
1009
|
-
command.command.rawValue,
|
|
1010
|
-
command.commandId ?? "",
|
|
1011
|
-
abandonedForSeconds
|
|
1012
|
-
)
|
|
1013
|
-
return Response(
|
|
1014
|
-
ok: false,
|
|
1015
|
-
error: ErrorPayload(
|
|
1016
|
-
code: "RUNNER_BUSY",
|
|
1017
|
-
message:
|
|
1018
|
-
"The iOS runner is still finishing a previous command that exceeded its execution watchdog (usually an accessibility capture on a heavy or animating screen).",
|
|
1019
|
-
hint:
|
|
1020
|
-
"Wait a few seconds and retry. If snapshots keep failing on this screen, use screenshot as visual truth and interact by coordinates, or navigate to another screen."
|
|
1021
|
-
)
|
|
1022
|
-
)
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
private func runnerWedgedResponse(command: Command, abandonedForSeconds: TimeInterval) -> Response {
|
|
1026
|
-
NSLog(
|
|
1027
|
-
"AGENT_DEVICE_RUNNER_WEDGED command=%@ commandId=%@ abandonedForSeconds=%.1f",
|
|
1028
|
-
command.command.rawValue,
|
|
1029
|
-
command.commandId ?? "",
|
|
1030
|
-
abandonedForSeconds
|
|
1031
|
-
)
|
|
1032
|
-
return Response(
|
|
1033
|
-
ok: false,
|
|
1034
|
-
error: ErrorPayload(
|
|
1035
|
-
code: "RUNNER_WEDGED",
|
|
1036
|
-
message:
|
|
1037
|
-
"The iOS runner main thread has been stuck in abandoned work for \(Int(abandonedForSeconds)) seconds and cannot recover on its own.",
|
|
1038
|
-
hint:
|
|
1039
|
-
"The runner session will be restarted. Retry the command after the restart; if this screen keeps wedging captures, use screenshot as visual truth and interact by coordinates."
|
|
1040
|
-
)
|
|
1041
|
-
)
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
private func runnerUnavailableResponse(command: Command) -> Response? {
|
|
1045
|
-
switch currentMainThreadBusyState() {
|
|
1046
|
-
case .idle:
|
|
1047
|
-
return nil
|
|
1048
|
-
case .busy(let abandonedForSeconds):
|
|
1049
|
-
return runnerBusyResponse(command: command, abandonedForSeconds: abandonedForSeconds)
|
|
1050
|
-
case .wedged(let abandonedForSeconds):
|
|
1051
|
-
return runnerWedgedResponse(command: command, abandonedForSeconds: abandonedForSeconds)
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
private func executeDispatched(command: Command) throws -> Response {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
if let unavailable = runnerUnavailableResponse(command: command) {
|
|
1061
|
-
return unavailable
|
|
1062
|
-
}
|
|
1063
|
-
let alertDeadline = command.command == .alert
|
|
1064
|
-
? Date().addingTimeInterval(Self.alertCommandTimeout(timeoutMs: command.timeoutMs))
|
|
1065
|
-
: nil
|
|
1066
|
-
if Thread.isMainThread {
|
|
1067
|
-
let routeToSpringboard = shouldRouteToSpringboardBlockingSystemModal(command)
|
|
1068
|
-
return try executeOnMainSafely(
|
|
1069
|
-
command: command,
|
|
1070
|
-
alertDeadline: alertDeadline,
|
|
1071
|
-
routeToSpringboard: routeToSpringboard
|
|
1072
|
-
)
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
let routeToSpringboard = shouldRouteToSpringboardBlockingSystemModal(command)
|
|
1078
|
-
if let unavailable = runnerUnavailableResponse(command: command) {
|
|
1079
|
-
return unavailable
|
|
1080
|
-
}
|
|
1081
|
-
if command.command == .snapshot {
|
|
1082
|
-
return try executeSnapshotDispatched(command: command)
|
|
1083
|
-
}
|
|
1084
|
-
if command.command == .alert, let deadline = alertDeadline {
|
|
1085
|
-
return try runMainThreadWork(
|
|
1086
|
-
"command_execution",
|
|
1087
|
-
timeout: max(0.001, deadline.timeIntervalSinceNow),
|
|
1088
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1089
|
-
) {
|
|
1090
|
-
try self.executeOnMainSafely(
|
|
1091
|
-
command: command,
|
|
1092
|
-
alertDeadline: deadline,
|
|
1093
|
-
routeToSpringboard: routeToSpringboard
|
|
1094
|
-
)
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
return try runMainThreadWork(
|
|
1098
|
-
"command_execution",
|
|
1099
|
-
timeout: mainThreadExecutionTimeout,
|
|
1100
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1101
|
-
) {
|
|
1102
|
-
try self.executeOnMainSafely(command: command, routeToSpringboard: routeToSpringboard)
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
private func executeOnMainSafely(
|
|
1109
|
-
command: Command,
|
|
1110
|
-
alertDeadline: Date? = nil,
|
|
1111
|
-
routeToSpringboard: Bool
|
|
1112
|
-
) throws -> Response {
|
|
1113
|
-
var hasRetried = false
|
|
1114
|
-
while true {
|
|
1115
|
-
var response: Response?
|
|
1116
|
-
var swiftError: Error?
|
|
1117
|
-
let failureCountBefore = currentXCTestFailureCount()
|
|
1118
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
1119
|
-
do {
|
|
1120
|
-
response = try self.executeOnMain(
|
|
1121
|
-
command: command,
|
|
1122
|
-
alertDeadline: alertDeadline,
|
|
1123
|
-
routeToSpringboard: routeToSpringboard
|
|
1124
|
-
)
|
|
1125
|
-
} catch {
|
|
1126
|
-
swiftError = error
|
|
1127
|
-
}
|
|
1128
|
-
})
|
|
1129
|
-
|
|
1130
|
-
if let exceptionMessage {
|
|
1131
|
-
invalidateCachedTarget(reason: "objc_exception")
|
|
1132
|
-
if !hasRetried, shouldRetryException(command, message: exceptionMessage) {
|
|
1133
|
-
NSLog(
|
|
1134
|
-
"AGENT_DEVICE_RUNNER_RETRY command=%@ reason=objc_exception",
|
|
1135
|
-
command.command.rawValue
|
|
1136
|
-
)
|
|
1137
|
-
hasRetried = true
|
|
1138
|
-
sleepFor(retryCooldown)
|
|
1139
|
-
continue
|
|
1140
|
-
}
|
|
1141
|
-
throw NSError(
|
|
1142
|
-
domain: RunnerErrorDomain.exception,
|
|
1143
|
-
code: RunnerErrorCode.objcException,
|
|
1144
|
-
userInfo: [NSLocalizedDescriptionKey: exceptionMessage]
|
|
1145
|
-
)
|
|
1146
|
-
}
|
|
1147
|
-
if let swiftError {
|
|
1148
|
-
throw swiftError
|
|
1149
|
-
}
|
|
1150
|
-
guard let response else {
|
|
1151
|
-
throw NSError(
|
|
1152
|
-
domain: RunnerErrorDomain.general,
|
|
1153
|
-
code: RunnerErrorCode.commandReturnedNoResponse,
|
|
1154
|
-
userInfo: [NSLocalizedDescriptionKey: "command returned no response"]
|
|
1155
|
-
)
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if didRecordXCTestFailure(since: failureCountBefore),
|
|
1172
|
-
let failureResponse = xctestRecordedFailureResponse(command: command, response: response)
|
|
1173
|
-
{
|
|
1174
|
-
invalidateCachedTarget(reason: "xctest_recorded_failure")
|
|
1175
|
-
return failureResponse
|
|
1176
|
-
}
|
|
1177
|
-
if !hasRetried, shouldRetryCommand(command), shouldRetryResponse(response) {
|
|
1178
|
-
NSLog(
|
|
1179
|
-
"AGENT_DEVICE_RUNNER_RETRY command=%@ reason=response_unavailable",
|
|
1180
|
-
command.command.rawValue
|
|
1181
|
-
)
|
|
1182
|
-
hasRetried = true
|
|
1183
|
-
invalidateCachedTarget(reason: "response_unavailable")
|
|
1184
|
-
sleepFor(retryCooldown)
|
|
1185
|
-
continue
|
|
1186
|
-
}
|
|
1187
|
-
return response
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
private func executeSnapshotDispatched(command: Command) throws -> Response {
|
|
1192
|
-
try executeDispatchedWithRecovery(command: command) {
|
|
1193
|
-
try self.executeSnapshotDispatchedOnce(command: command)
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
func executeDispatchedWithRecovery(
|
|
1201
|
-
command: Command,
|
|
1202
|
-
perform: () throws -> Response
|
|
1203
|
-
) throws -> Response {
|
|
1204
|
-
var hasRetried = false
|
|
1205
|
-
while true {
|
|
1206
|
-
let failureCountBefore = try runMainThreadWork(
|
|
1207
|
-
"recorded_failure_count",
|
|
1208
|
-
timeout: mainThreadExecutionTimeout,
|
|
1209
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1210
|
-
) {
|
|
1211
|
-
self.currentXCTestFailureCount()
|
|
1212
|
-
}
|
|
1213
|
-
let response = try perform()
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
if hasAbandonedMainThreadWork() {
|
|
1217
|
-
NSLog(
|
|
1218
|
-
"AGENT_DEVICE_RUNNER_DISPATCH_RECOVERY_SKIPPED_XCTEST_OCCUPIED command=%@",
|
|
1219
|
-
command.command.rawValue
|
|
1220
|
-
)
|
|
1221
|
-
return response
|
|
1222
|
-
}
|
|
1223
|
-
let recordedFailureResponse = try runMainThreadWork(
|
|
1224
|
-
"recorded_failure_count",
|
|
1225
|
-
timeout: mainThreadExecutionTimeout,
|
|
1226
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1227
|
-
) {
|
|
1228
|
-
self.didRecordXCTestFailure(since: failureCountBefore)
|
|
1229
|
-
? self.xctestRecordedFailureResponse(command: command, response: response)
|
|
1230
|
-
: nil
|
|
1231
|
-
}
|
|
1232
|
-
if let recordedFailureResponse {
|
|
1233
|
-
try runMainThreadWork(
|
|
1234
|
-
"target_invalidation",
|
|
1235
|
-
timeout: mainThreadExecutionTimeout,
|
|
1236
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1237
|
-
) {
|
|
1238
|
-
self.invalidateCachedTarget(reason: "xctest_recorded_failure")
|
|
1239
|
-
}
|
|
1240
|
-
return recordedFailureResponse
|
|
1241
|
-
}
|
|
1242
|
-
if !hasRetried, shouldRetryCommand(command), shouldRetryResponse(response) {
|
|
1243
|
-
NSLog(
|
|
1244
|
-
"AGENT_DEVICE_RUNNER_RETRY command=%@ reason=response_unavailable",
|
|
1245
|
-
command.command.rawValue
|
|
1246
|
-
)
|
|
1247
|
-
hasRetried = true
|
|
1248
|
-
try runMainThreadWork(
|
|
1249
|
-
"target_invalidation",
|
|
1250
|
-
timeout: mainThreadExecutionTimeout,
|
|
1251
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1252
|
-
) {
|
|
1253
|
-
self.invalidateCachedTarget(reason: "response_unavailable")
|
|
1254
|
-
self.sleepFor(self.retryCooldown)
|
|
1255
|
-
}
|
|
1256
|
-
continue
|
|
1257
|
-
}
|
|
1258
|
-
return response
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
private func executeSnapshotDispatchedOnce(command: Command) throws -> Response {
|
|
1263
|
-
let preparation = try runMainThreadWork(
|
|
1264
|
-
"command_preparation",
|
|
1265
|
-
timeout: mainThreadExecutionTimeout,
|
|
1266
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1267
|
-
) {
|
|
1268
|
-
try self.prepareActiveCommandContextSafely(command: command, routeToSpringboard: false)
|
|
1269
|
-
}
|
|
1270
|
-
switch preparation {
|
|
1271
|
-
case .response(let response):
|
|
1272
|
-
return response
|
|
1273
|
-
case .context(let context):
|
|
1274
|
-
return try executeSnapshotPrepared(
|
|
1275
|
-
command: command,
|
|
1276
|
-
activeApp: context.app,
|
|
1277
|
-
systemSurface: context.systemSurface
|
|
1278
|
-
)
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
static func presentationOptions(from command: Command) -> PresentationOptions {
|
|
1285
|
-
let customActions = command.customActions ?? false
|
|
1286
|
-
return PresentationOptions(
|
|
1287
|
-
interactiveOnly: command.interactiveOnly ?? false,
|
|
1288
|
-
depth: command.depth,
|
|
1289
|
-
scope: command.scope,
|
|
1290
|
-
raw: command.raw ?? false,
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
preferredBackend: command.preferredBackend
|
|
1295
|
-
?? (customActions ? SnapshotBackendKind.privateAX.rawValue : nil),
|
|
1296
|
-
customActions: customActions
|
|
1297
|
-
)
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
private func executeSnapshotPrepared(
|
|
1301
|
-
command: Command,
|
|
1302
|
-
activeApp: XCUIApplication,
|
|
1303
|
-
systemSurface: SystemSurfaceHost? = nil
|
|
1304
|
-
) throws -> Response {
|
|
1305
|
-
let options = Self.presentationOptions(from: command)
|
|
1306
|
-
do {
|
|
1307
|
-
var payload: DataPayload
|
|
1308
|
-
if options.raw {
|
|
1309
|
-
payload = try snapshotRaw(app: activeApp, options: options)
|
|
1310
|
-
} else {
|
|
1311
|
-
payload = try snapshotFast(app: activeApp, options: options)
|
|
1312
|
-
}
|
|
1313
|
-
if let systemSurface {
|
|
1314
|
-
payload.systemSurface = SystemSurfaceProvenancePayload(
|
|
1315
|
-
bundleId: systemSurface.bundleId,
|
|
1316
|
-
kind: systemSurface.kind.rawValue
|
|
1317
|
-
)
|
|
1318
|
-
}
|
|
1319
|
-
setNeedsPostSnapshotInteractionDelay()
|
|
1320
|
-
return Response(ok: true, data: payload)
|
|
1321
|
-
} catch let failure as SnapshotCaptureFailure {
|
|
1322
|
-
invalidateCachedTargetAfterSnapshotFailure()
|
|
1323
|
-
return Response(
|
|
1324
|
-
ok: false,
|
|
1325
|
-
error: ErrorPayload(
|
|
1326
|
-
code: failure.code,
|
|
1327
|
-
message: failure.message,
|
|
1328
|
-
hint: failure.hint
|
|
1329
|
-
)
|
|
1330
|
-
)
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
private func setNeedsPostSnapshotInteractionDelay() {
|
|
1335
|
-
if Thread.isMainThread {
|
|
1336
|
-
needsPostSnapshotInteractionDelay = true
|
|
1337
|
-
return
|
|
1338
|
-
}
|
|
1339
|
-
guard !hasAbandonedMainThreadWork() else {
|
|
1340
|
-
NSLog("AGENT_DEVICE_RUNNER_POST_SNAPSHOT_DELAY_MARK_SKIPPED_XCTEST_OCCUPIED")
|
|
1341
|
-
return
|
|
1342
|
-
}
|
|
1343
|
-
do {
|
|
1344
|
-
try runMainThreadWork(
|
|
1345
|
-
"post_snapshot_delay_mark",
|
|
1346
|
-
timeout: 1,
|
|
1347
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1348
|
-
) {
|
|
1349
|
-
self.needsPostSnapshotInteractionDelay = true
|
|
1350
|
-
}
|
|
1351
|
-
} catch {
|
|
1352
|
-
NSLog("AGENT_DEVICE_RUNNER_POST_SNAPSHOT_DELAY_MARK_FAILED=%@", String(describing: error))
|
|
1353
|
-
}
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
private func invalidateCachedTargetAfterSnapshotFailure() {
|
|
1357
|
-
if Thread.isMainThread {
|
|
1358
|
-
invalidateCachedTarget(reason: "ax_snapshot_failure")
|
|
1359
|
-
return
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
guard !hasAbandonedMainThreadWork() else {
|
|
1364
|
-
NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_INVALIDATION_DEFERRED_XCTEST_OCCUPIED")
|
|
1365
|
-
DispatchQueue.main.async {
|
|
1366
|
-
self.invalidateCachedTarget(reason: "ax_snapshot_failure")
|
|
1367
|
-
}
|
|
1368
|
-
return
|
|
1369
|
-
}
|
|
1370
|
-
do {
|
|
1371
|
-
try runMainThreadWork(
|
|
1372
|
-
"target_invalidation",
|
|
1373
|
-
timeout: 1,
|
|
1374
|
-
timeoutError: mainThreadExecutionTimeoutError
|
|
1375
|
-
) {
|
|
1376
|
-
self.invalidateCachedTarget(reason: "ax_snapshot_failure")
|
|
1377
|
-
}
|
|
1378
|
-
} catch {
|
|
1379
|
-
NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_INVALIDATION_FAILED=%@", String(describing: error))
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
private func prepareActiveCommandContextSafely(
|
|
1384
|
-
command: Command,
|
|
1385
|
-
routeToSpringboard: Bool
|
|
1386
|
-
) throws -> ActiveCommandPreparation {
|
|
1387
|
-
var preparation: ActiveCommandPreparation?
|
|
1388
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
1389
|
-
preparation = self.prepareActiveCommandContext(
|
|
1390
|
-
command: command,
|
|
1391
|
-
routeToSpringboard: routeToSpringboard
|
|
1392
|
-
)
|
|
1393
|
-
})
|
|
1394
|
-
if let exceptionMessage {
|
|
1395
|
-
throw NSError(
|
|
1396
|
-
domain: RunnerErrorDomain.exception,
|
|
1397
|
-
code: RunnerErrorCode.objcException,
|
|
1398
|
-
userInfo: [NSLocalizedDescriptionKey: exceptionMessage]
|
|
1399
|
-
)
|
|
1400
|
-
}
|
|
1401
|
-
guard let preparation else {
|
|
1402
|
-
throw NSError(
|
|
1403
|
-
domain: RunnerErrorDomain.general,
|
|
1404
|
-
code: RunnerErrorCode.commandReturnedNoResponse,
|
|
1405
|
-
userInfo: [NSLocalizedDescriptionKey: "snapshot preflight returned no response"]
|
|
1406
|
-
)
|
|
1407
|
-
}
|
|
1408
|
-
return preparation
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
private func executeOnMain(
|
|
1412
|
-
command: Command,
|
|
1413
|
-
alertDeadline: Date?,
|
|
1414
|
-
routeToSpringboard: Bool
|
|
1415
|
-
) throws -> Response {
|
|
1416
|
-
let preparation = prepareActiveCommandContext(
|
|
1417
|
-
command: command,
|
|
1418
|
-
routeToSpringboard: routeToSpringboard
|
|
1419
|
-
)
|
|
1420
|
-
let activeApp: XCUIApplication
|
|
1421
|
-
switch preparation {
|
|
1422
|
-
case .response(let response):
|
|
1423
|
-
return response
|
|
1424
|
-
case .context(let context):
|
|
1425
|
-
activeApp = context.app
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
switch command.command {
|
|
1429
|
-
case .status:
|
|
1430
|
-
return executeStatus(command: command)
|
|
1431
|
-
case .targetReset:
|
|
1432
|
-
return resetTargetAfterExternalRelaunch()
|
|
1433
|
-
case .shutdown:
|
|
1434
|
-
stopRecordingIfNeeded()
|
|
1435
|
-
return Response(ok: true, data: DataPayload(message: "shutdown"))
|
|
1436
|
-
case .recordStart:
|
|
1437
|
-
guard
|
|
1438
|
-
let requestedOutPath = command.outPath?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1439
|
-
!requestedOutPath.isEmpty
|
|
1440
|
-
else {
|
|
1441
|
-
return Response(ok: false, error: ErrorPayload(message: "recordStart requires outPath"))
|
|
1442
|
-
}
|
|
1443
|
-
let hasAppBundleId = !(command.appBundleId?
|
|
1444
|
-
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1445
|
-
.isEmpty ?? true)
|
|
1446
|
-
guard hasAppBundleId else {
|
|
1447
|
-
return Response(ok: false, error: ErrorPayload(message: "recordStart requires appBundleId"))
|
|
1448
|
-
}
|
|
1449
|
-
if activeRecording != nil {
|
|
1450
|
-
return Response(ok: false, error: ErrorPayload(message: "recording already in progress"))
|
|
1451
|
-
}
|
|
1452
|
-
if let requestedFps = command.fps, (requestedFps < minRecordingFps || requestedFps > maxRecordingFps) {
|
|
1453
|
-
return Response(ok: false, error: ErrorPayload(message: "recordStart fps must be between \(minRecordingFps) and \(maxRecordingFps)"))
|
|
1454
|
-
}
|
|
1455
|
-
do {
|
|
1456
|
-
let resolvedOutPath = resolveRecordingOutPath(requestedOutPath)
|
|
1457
|
-
let fpsLabel = command.fps.map(String.init) ?? String(RunnerTests.defaultRecordingFps)
|
|
1458
|
-
NSLog(
|
|
1459
|
-
"AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@",
|
|
1460
|
-
requestedOutPath,
|
|
1461
|
-
resolvedOutPath,
|
|
1462
|
-
fpsLabel
|
|
1463
|
-
)
|
|
1464
|
-
let recorder = ScreenRecorder(
|
|
1465
|
-
outputPath: resolvedOutPath,
|
|
1466
|
-
fps: command.fps.map { Int32($0) }
|
|
1467
|
-
)
|
|
1468
|
-
try recorder.start { [weak self] in
|
|
1469
|
-
return self?.captureRunnerFrame(app: activeApp)
|
|
1470
|
-
}
|
|
1471
|
-
activeRecording = recorder
|
|
1472
|
-
return Response(ok: true, data: DataPayload(message: "recording started"))
|
|
1473
|
-
} catch {
|
|
1474
|
-
activeRecording = nil
|
|
1475
|
-
return Response(ok: false, error: ErrorPayload(message: "failed to start recording: \(error.localizedDescription)"))
|
|
1476
|
-
}
|
|
1477
|
-
case .recordStop:
|
|
1478
|
-
guard let recorder = activeRecording else {
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
return Response(ok: true, data: DataPayload(message: "recording already stopped"))
|
|
1484
|
-
}
|
|
1485
|
-
do {
|
|
1486
|
-
try recorder.stop()
|
|
1487
|
-
activeRecording = nil
|
|
1488
|
-
return Response(ok: true, data: DataPayload(message: "recording stopped"))
|
|
1489
|
-
} catch {
|
|
1490
|
-
activeRecording = nil
|
|
1491
|
-
return Response(ok: false, error: ErrorPayload(message: "failed to stop recording: \(error.localizedDescription)"))
|
|
1492
|
-
}
|
|
1493
|
-
case .uptime:
|
|
1494
|
-
return executeUptime()
|
|
1495
|
-
case .activate:
|
|
1496
|
-
guard
|
|
1497
|
-
let bundleId = command.appBundleId?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1498
|
-
!bundleId.isEmpty
|
|
1499
|
-
else {
|
|
1500
|
-
return Response(ok: false, error: ErrorPayload(message: "activate requires appBundleId"))
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
return Response(ok: true, data: DataPayload(message: "app activated"))
|
|
1505
|
-
case .terminate:
|
|
1506
|
-
guard
|
|
1507
|
-
let bundleId = command.appBundleId?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1508
|
-
!bundleId.isEmpty
|
|
1509
|
-
else {
|
|
1510
|
-
return Response(ok: false, error: ErrorPayload(message: "terminate requires appBundleId"))
|
|
1511
|
-
}
|
|
1512
|
-
XCUIApplication(bundleIdentifier: bundleId).terminate()
|
|
1513
|
-
if currentBundleId == bundleId {
|
|
1514
|
-
invalidateCachedTarget(reason: "target_terminated")
|
|
1515
|
-
}
|
|
1516
|
-
return Response(ok: true, data: DataPayload(message: "app terminated"))
|
|
1517
|
-
default:
|
|
1518
|
-
break
|
|
1519
|
-
}
|
|
1520
|
-
return try executeOnMainPrepared(
|
|
1521
|
-
command: command,
|
|
1522
|
-
activeApp: activeApp,
|
|
1523
|
-
alertDeadline: alertDeadline
|
|
1524
|
-
)
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
private func prepareActiveCommandContext(
|
|
1528
|
-
command: Command,
|
|
1529
|
-
routeToSpringboard: Bool = false
|
|
1530
|
-
) -> ActiveCommandPreparation {
|
|
1531
|
-
var activeApp = currentApp ?? app
|
|
1532
|
-
var systemSurface: SystemSurfaceHost? = nil
|
|
1533
|
-
if routeToSpringboard {
|
|
1534
|
-
activeApp = springboard
|
|
1535
|
-
} else if shouldSkipAppActivationPreflight(command) {
|
|
1536
|
-
activeApp = resolveAppWithoutActivation(command: command)
|
|
1537
|
-
} else if let presented = presentedSystemSurfaceHost() {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
activeApp = presented.app
|
|
1542
|
-
systemSurface = presented.host
|
|
1543
|
-
if isInteractionCommand(command.command) {
|
|
1544
|
-
applyInteractionStabilizationIfNeeded()
|
|
1545
|
-
}
|
|
1546
|
-
} else if !isRunnerLifecycleCommand(command.command) {
|
|
1547
|
-
let normalizedBundleId = command.appBundleId?
|
|
1548
|
-
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1549
|
-
let requestedBundleId = (normalizedBundleId?.isEmpty == true) ? nil : normalizedBundleId
|
|
1550
|
-
if let bundleId = requestedBundleId {
|
|
1551
|
-
if currentBundleId != bundleId || currentApp == nil {
|
|
1552
|
-
_ = activateTarget(bundleId: bundleId, reason: "bundle_changed")
|
|
1553
|
-
} else {
|
|
1554
|
-
refreshCachedTargetIfProcessChanged(bundleId: bundleId)
|
|
1555
|
-
}
|
|
1556
|
-
} else {
|
|
1557
|
-
|
|
1558
|
-
invalidateCachedTarget(reason: "missing_app_bundle")
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
activeApp = currentApp ?? app
|
|
1562
|
-
if let bundleId = requestedBundleId, targetNeedsActivation(activeApp) {
|
|
1563
|
-
activeApp = activateTarget(bundleId: bundleId, reason: "stale_target")
|
|
1564
|
-
} else if requestedBundleId == nil, targetNeedsActivation(activeApp) {
|
|
1565
|
-
ensureRunnerHostAppActive(reason: "missing_app_bundle")
|
|
1566
|
-
activeApp = app
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
let skipExistenceWait = canUseFastForegroundAppGuard(
|
|
1570
|
-
activeApp: activeApp,
|
|
1571
|
-
requestedBundleId: requestedBundleId,
|
|
1572
|
-
command: command.command
|
|
1573
|
-
)
|
|
1574
|
-
if !skipExistenceWait && !activeApp.waitForExistence(timeout: appExistenceTimeout) {
|
|
1575
|
-
if let bundleId = requestedBundleId {
|
|
1576
|
-
activeApp = activateTarget(bundleId: bundleId, reason: "missing_after_wait")
|
|
1577
|
-
guard activeApp.waitForExistence(timeout: appExistenceTimeout) else {
|
|
1578
|
-
return .response(Response(ok: false, error: ErrorPayload(message: "app '\(bundleId)' is not available")))
|
|
1579
|
-
}
|
|
1580
|
-
} else {
|
|
1581
|
-
return .response(Response(ok: false, error: ErrorPayload(message: "runner app is not available")))
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
if isInteractionCommand(command.command) {
|
|
1586
|
-
if let bundleId = requestedBundleId, activeApp.state != .runningForeground {
|
|
1587
|
-
activeApp = activateTarget(bundleId: bundleId, reason: "interaction_foreground_guard")
|
|
1588
|
-
} else if requestedBundleId == nil, activeApp.state != .runningForeground {
|
|
1589
|
-
ensureRunnerHostAppActive(reason: "interaction_missing_app_bundle")
|
|
1590
|
-
activeApp = app
|
|
1591
|
-
}
|
|
1592
|
-
let skipInteractionExistenceWait = canUseFastForegroundAppGuard(
|
|
1593
|
-
activeApp: activeApp,
|
|
1594
|
-
requestedBundleId: requestedBundleId,
|
|
1595
|
-
command: command.command
|
|
1596
|
-
)
|
|
1597
|
-
if !skipInteractionExistenceWait && !activeApp.waitForExistence(timeout: 2) {
|
|
1598
|
-
if let bundleId = requestedBundleId {
|
|
1599
|
-
return .response(Response(ok: false, error: ErrorPayload(message: "app '\(bundleId)' is not available")))
|
|
1600
|
-
}
|
|
1601
|
-
return .response(Response(ok: false, error: ErrorPayload(message: "runner app is not available")))
|
|
1602
|
-
}
|
|
1603
|
-
applyInteractionStabilizationIfNeeded()
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
return .context(ActiveCommandContext(app: activeApp, systemSurface: systemSurface))
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
private func presentedSystemSurfaceHost() -> (host: SystemSurfaceHost, app: XCUIApplication)? {
|
|
1614
|
-
#if os(iOS)
|
|
1615
|
-
for host in SystemSurfaceHostRegistry.hosts {
|
|
1616
|
-
let candidate = XCUIApplication(bundleIdentifier: host.bundleId)
|
|
1617
|
-
if candidate.state == .runningForeground {
|
|
1618
|
-
return (host, candidate)
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
return nil
|
|
1622
|
-
#else
|
|
1623
|
-
return nil
|
|
1624
|
-
#endif
|
|
1625
|
-
}
|
|
1626
|
-
|
|
4
|
+
extension RunnerTests {
|
|
1627
5
|
func executeOnMainPrepared(
|
|
1628
6
|
command: Command,
|
|
1629
7
|
activeApp: XCUIApplication,
|
|
@@ -1634,7 +12,8 @@ extension RunnerTests {
|
|
|
1634
12
|
clearRememberedTextEntryTap()
|
|
1635
13
|
}
|
|
1636
14
|
switch command.command {
|
|
1637
|
-
case .status, .activate, .terminate, .targetReset, .shutdown, .recordStart, .recordStop, .uptime
|
|
15
|
+
case .status, .activate, .terminate, .targetReset, .shutdown, .recordStart, .recordStop, .uptime,
|
|
16
|
+
.snapshot:
|
|
1638
17
|
return Response(
|
|
1639
18
|
ok: false,
|
|
1640
19
|
error: ErrorPayload(
|
|
@@ -1868,9 +247,7 @@ extension RunnerTests {
|
|
|
1868
247
|
x2: x2,
|
|
1869
248
|
y2: y2,
|
|
1870
249
|
durationMs: defaults.durationMs,
|
|
1871
|
-
|
|
1872
|
-
message: "dragged",
|
|
1873
|
-
synthesizedPolicyKind: .synthesizedDrag
|
|
250
|
+
message: "dragged"
|
|
1874
251
|
)
|
|
1875
252
|
case .scroll:
|
|
1876
253
|
|
|
@@ -2052,8 +429,6 @@ extension RunnerTests {
|
|
|
2052
429
|
return Response(ok: false, error: ErrorPayload(message: "readText did not resolve text"))
|
|
2053
430
|
}
|
|
2054
431
|
return Response(ok: true, data: DataPayload(text: text))
|
|
2055
|
-
case .snapshot:
|
|
2056
|
-
return try executeSnapshotPrepared(command: command, activeApp: activeApp)
|
|
2057
432
|
case .screenshot:
|
|
2058
433
|
#if os(macOS)
|
|
2059
434
|
|
|
@@ -2119,12 +494,20 @@ extension RunnerTests {
|
|
|
2119
494
|
inlineScreenshot: command.inlineScreenshot == true
|
|
2120
495
|
)
|
|
2121
496
|
#endif
|
|
2122
|
-
case .
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
return Response(ok: true, data: DataPayload(message:
|
|
497
|
+
case .backInApp:
|
|
498
|
+
switch tapInAppBackControl(app: activeApp) {
|
|
499
|
+
case .performed:
|
|
500
|
+
return Response(ok: true, data: DataPayload(message: "backInApp"))
|
|
501
|
+
case .unavailable:
|
|
502
|
+
return Response(
|
|
503
|
+
ok: false,
|
|
504
|
+
error: ErrorPayload(message: "in-app back control is not available")
|
|
505
|
+
)
|
|
506
|
+
case .unverified(let error):
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
return Response(ok: false, error: error)
|
|
2126
510
|
}
|
|
2127
|
-
return Response(ok: false, error: ErrorPayload(message: "in-app back control is not available"))
|
|
2128
511
|
case .backSystem:
|
|
2129
512
|
if performSystemBackAction(app: activeApp) {
|
|
2130
513
|
return Response(ok: true, data: DataPayload(message: "backSystem"))
|
|
@@ -2232,10 +615,8 @@ extension RunnerTests {
|
|
|
2232
615
|
x2: last.x,
|
|
2233
616
|
y2: last.y,
|
|
2234
617
|
durationMs: plan.durationMs,
|
|
2235
|
-
synthesized: true,
|
|
2236
618
|
message: plan.intent,
|
|
2237
|
-
|
|
2238
|
-
synthesizedProfile: .fastSwipe
|
|
619
|
+
synthesized: (profile: .fastSwipe, policyKind: .synthesizedDrag)
|
|
2239
620
|
)
|
|
2240
621
|
)
|
|
2241
622
|
case .sampled:
|
|
@@ -2254,603 +635,4 @@ extension RunnerTests {
|
|
|
2254
635
|
return executeSequence(command: command, activeApp: activeApp)
|
|
2255
636
|
}
|
|
2256
637
|
}
|
|
2257
|
-
|
|
2258
|
-
private func invalidScrollDirectionResponse(commandName: String) -> Response {
|
|
2259
|
-
Response(
|
|
2260
|
-
ok: false,
|
|
2261
|
-
error: ErrorPayload(
|
|
2262
|
-
code: "INVALID_ARGS",
|
|
2263
|
-
message: "\(commandName) requires direction up|down|left|right"
|
|
2264
|
-
)
|
|
2265
|
-
)
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
private func scrollDurationIsValid(_ durationMs: Double?) -> Bool {
|
|
2269
|
-
guard let durationMs else { return true }
|
|
2270
|
-
return durationMs.isFinite && durationMs >= 0 && durationMs <= 10000
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
private func invalidScrollDurationResponse(commandName: String) -> Response {
|
|
2274
|
-
return Response(
|
|
2275
|
-
ok: false,
|
|
2276
|
-
error: ErrorPayload(
|
|
2277
|
-
code: "INVALID_ARGS",
|
|
2278
|
-
message: "\(commandName) durationMs must be between 0 and 10000"
|
|
2279
|
-
)
|
|
2280
|
-
)
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
private func executeScrollDragGesture(
|
|
2284
|
-
activeApp: XCUIApplication,
|
|
2285
|
-
x: Double,
|
|
2286
|
-
y: Double,
|
|
2287
|
-
x2: Double,
|
|
2288
|
-
y2: Double,
|
|
2289
|
-
durationMs: Double,
|
|
2290
|
-
message: String,
|
|
2291
|
-
context: SynthesizedCoordinateContext,
|
|
2292
|
-
releaseBehavior: ScrollReleaseBehavior?
|
|
2293
|
-
) -> Response {
|
|
2294
|
-
#if os(iOS)
|
|
2295
|
-
return executeDragGesture(
|
|
2296
|
-
activeApp: activeApp,
|
|
2297
|
-
x: x,
|
|
2298
|
-
y: y,
|
|
2299
|
-
x2: x2,
|
|
2300
|
-
y2: y2,
|
|
2301
|
-
durationMs: durationMs,
|
|
2302
|
-
synthesized: true,
|
|
2303
|
-
message: message,
|
|
2304
|
-
synthesizedContext: context,
|
|
2305
|
-
synthesizedPolicyKind: .scroll,
|
|
2306
|
-
synthesizedProfile: scrollDragProfile(releaseBehavior: releaseBehavior)
|
|
2307
|
-
)
|
|
2308
|
-
#else
|
|
2309
|
-
return executeDragGesture(
|
|
2310
|
-
activeApp: activeApp,
|
|
2311
|
-
x: x,
|
|
2312
|
-
y: y,
|
|
2313
|
-
x2: x2,
|
|
2314
|
-
y2: y2,
|
|
2315
|
-
durationMs: durationMs,
|
|
2316
|
-
synthesized: false,
|
|
2317
|
-
message: message,
|
|
2318
|
-
synthesizedPolicyKind: .scroll
|
|
2319
|
-
)
|
|
2320
|
-
#endif
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
private func executeDragGesture(
|
|
2326
|
-
activeApp: XCUIApplication,
|
|
2327
|
-
x: Double,
|
|
2328
|
-
y: Double,
|
|
2329
|
-
x2: Double,
|
|
2330
|
-
y2: Double,
|
|
2331
|
-
durationMs: Double?,
|
|
2332
|
-
synthesized: Bool,
|
|
2333
|
-
message: String,
|
|
2334
|
-
synthesizedContext: SynthesizedCoordinateContext? = nil,
|
|
2335
|
-
synthesizedPolicyKind: SynthesizedGesturePolicyKind,
|
|
2336
|
-
synthesizedProfile: SynthesizedDragProfile = .continuous
|
|
2337
|
-
) -> Response {
|
|
2338
|
-
let durationMs = durationMs ?? runnerDefaultDragDurationMs
|
|
2339
|
-
let commandName = dragCommandName(message: message)
|
|
2340
|
-
guard x.isFinite, y.isFinite, x2.isFinite, y2.isFinite else {
|
|
2341
|
-
return Response(
|
|
2342
|
-
ok: false,
|
|
2343
|
-
error: ErrorPayload(code: "INVALID_ARGS", message: "\(commandName) requires finite coordinates")
|
|
2344
|
-
)
|
|
2345
|
-
}
|
|
2346
|
-
if synthesized, let synthesizedResponse = executeSynthesizedDragGesture(
|
|
2347
|
-
activeApp: activeApp,
|
|
2348
|
-
x: x,
|
|
2349
|
-
y: y,
|
|
2350
|
-
x2: x2,
|
|
2351
|
-
y2: y2,
|
|
2352
|
-
durationMs: durationMs,
|
|
2353
|
-
message: message,
|
|
2354
|
-
context: synthesizedContext,
|
|
2355
|
-
policyKind: synthesizedPolicyKind,
|
|
2356
|
-
profile: synthesizedProfile
|
|
2357
|
-
) {
|
|
2358
|
-
return synthesizedResponse
|
|
2359
|
-
}
|
|
2360
|
-
let dragPoints = keyboardAvoidingDragPoints(app: activeApp, x: x, y: y, x2: x2, y2: y2)
|
|
2361
|
-
let dragFrame = resolvedDragVisualizationFrame(
|
|
2362
|
-
app: activeApp,
|
|
2363
|
-
x: dragPoints.x,
|
|
2364
|
-
y: dragPoints.y,
|
|
2365
|
-
x2: dragPoints.x2,
|
|
2366
|
-
y2: dragPoints.y2
|
|
2367
|
-
)
|
|
2368
|
-
var fallback: GestureFallback?
|
|
2369
|
-
if synthesized {
|
|
2370
|
-
let durationMs = min(max(durationMs, 16), 10000)
|
|
2371
|
-
let context = synthesizedCoordinateContext(
|
|
2372
|
-
app: activeApp,
|
|
2373
|
-
policy: synthesizedGesturePolicy(synthesizedPolicyKind)
|
|
2374
|
-
)
|
|
2375
|
-
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
2376
|
-
synthesizedDragAt(
|
|
2377
|
-
app: activeApp,
|
|
2378
|
-
x: dragPoints.x,
|
|
2379
|
-
y: dragPoints.y,
|
|
2380
|
-
x2: dragPoints.x2,
|
|
2381
|
-
y2: dragPoints.y2,
|
|
2382
|
-
durationMs: durationMs,
|
|
2383
|
-
profile: synthesizedProfile,
|
|
2384
|
-
context: context
|
|
2385
|
-
)
|
|
2386
|
-
}
|
|
2387
|
-
if case .performed = outcome {
|
|
2388
|
-
return gestureResponse(message: message, timing: timing, frame: .drag(dragFrame))
|
|
2389
|
-
}
|
|
2390
|
-
fallback = gestureFallback(strategy: "xctest-coordinate-drag", from: outcome)
|
|
2391
|
-
}
|
|
2392
|
-
let holdDuration = synthesized
|
|
2393
|
-
? synthesizedSwipeFallbackHoldDuration(durationMs: durationMs)
|
|
2394
|
-
: coordinateDragHoldDuration()
|
|
2395
|
-
let (timing, outcome) = performGesture(activeApp) {
|
|
2396
|
-
dragAt(
|
|
2397
|
-
app: activeApp,
|
|
2398
|
-
x: dragPoints.x,
|
|
2399
|
-
y: dragPoints.y,
|
|
2400
|
-
x2: dragPoints.x2,
|
|
2401
|
-
y2: dragPoints.y2,
|
|
2402
|
-
holdDuration: holdDuration
|
|
2403
|
-
)
|
|
2404
|
-
}
|
|
2405
|
-
if let response = unsupportedResponse(for: outcome) {
|
|
2406
|
-
return response
|
|
2407
|
-
}
|
|
2408
|
-
return gestureResponse(
|
|
2409
|
-
message: message,
|
|
2410
|
-
timing: timing,
|
|
2411
|
-
frame: .drag(dragFrame),
|
|
2412
|
-
fallback: fallback
|
|
2413
|
-
)
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
|
-
private func executeSynthesizedDragGesture(
|
|
2417
|
-
activeApp: XCUIApplication,
|
|
2418
|
-
x: Double,
|
|
2419
|
-
y: Double,
|
|
2420
|
-
x2: Double,
|
|
2421
|
-
y2: Double,
|
|
2422
|
-
durationMs: Double,
|
|
2423
|
-
message: String,
|
|
2424
|
-
context: SynthesizedCoordinateContext?,
|
|
2425
|
-
policyKind: SynthesizedGesturePolicyKind,
|
|
2426
|
-
profile: SynthesizedDragProfile
|
|
2427
|
-
) -> Response? {
|
|
2428
|
-
#if os(iOS)
|
|
2429
|
-
let policy = synthesizedGesturePolicy(policyKind)
|
|
2430
|
-
let context = context ?? synthesizedCoordinateContext(app: activeApp, policy: policy)
|
|
2431
|
-
guard let plan = axFreeSynthesizedDragPlan(
|
|
2432
|
-
app: activeApp,
|
|
2433
|
-
x: x,
|
|
2434
|
-
y: y,
|
|
2435
|
-
x2: x2,
|
|
2436
|
-
y2: y2,
|
|
2437
|
-
context: context
|
|
2438
|
-
)
|
|
2439
|
-
else {
|
|
2440
|
-
if context?.allowsXCTestCoordinateFallback == true {
|
|
2441
|
-
logSynthesizedGesturePolicyDecision(kind: policyKind, context: context, fallbackAttempted: true)
|
|
2442
|
-
return executeCoordinateDragFallback(
|
|
2443
|
-
activeApp: activeApp,
|
|
2444
|
-
x: x,
|
|
2445
|
-
y: y,
|
|
2446
|
-
x2: x2,
|
|
2447
|
-
y2: y2,
|
|
2448
|
-
durationMs: durationMs,
|
|
2449
|
-
message: message,
|
|
2450
|
-
fallback: nil
|
|
2451
|
-
)
|
|
2452
|
-
}
|
|
2453
|
-
logSynthesizedGesturePolicyDecision(kind: policyKind, context: context, fallbackAttempted: false)
|
|
2454
|
-
return Response(
|
|
2455
|
-
ok: false,
|
|
2456
|
-
error: ErrorPayload(
|
|
2457
|
-
code: "INVALID_ARGS",
|
|
2458
|
-
message: "\(dragCommandName(message: message)) could not resolve a finite synthesized coordinate frame"
|
|
2459
|
-
)
|
|
2460
|
-
)
|
|
2461
|
-
}
|
|
2462
|
-
let durationMs = min(max(durationMs, 16), 10000)
|
|
2463
|
-
let dragFrame = axFreeDragVisualizationFrame(
|
|
2464
|
-
x: plan.points.x,
|
|
2465
|
-
y: plan.points.y,
|
|
2466
|
-
x2: plan.points.x2,
|
|
2467
|
-
y2: plan.points.y2,
|
|
2468
|
-
referenceFrame: plan.referenceFrame
|
|
2469
|
-
)
|
|
2470
|
-
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
2471
|
-
synthesizedDragAt(
|
|
2472
|
-
app: activeApp,
|
|
2473
|
-
x: plan.points.x,
|
|
2474
|
-
y: plan.points.y,
|
|
2475
|
-
x2: plan.points.x2,
|
|
2476
|
-
y2: plan.points.y2,
|
|
2477
|
-
durationMs: durationMs,
|
|
2478
|
-
profile: profile,
|
|
2479
|
-
context: plan.context
|
|
2480
|
-
)
|
|
2481
|
-
}
|
|
2482
|
-
if case .performed = outcome {
|
|
2483
|
-
logSynthesizedGesturePolicyDecision(kind: policyKind, context: plan.context, fallbackAttempted: false)
|
|
2484
|
-
return gestureResponse(message: message, timing: timing, frame: .drag(dragFrame))
|
|
2485
|
-
}
|
|
2486
|
-
if plan.context.allowsXCTestCoordinateFallback {
|
|
2487
|
-
logSynthesizedGesturePolicyDecision(kind: policyKind, context: plan.context, fallbackAttempted: true)
|
|
2488
|
-
return executeCoordinateDragFallback(
|
|
2489
|
-
activeApp: activeApp,
|
|
2490
|
-
x: plan.points.x,
|
|
2491
|
-
y: plan.points.y,
|
|
2492
|
-
x2: plan.points.x2,
|
|
2493
|
-
y2: plan.points.y2,
|
|
2494
|
-
durationMs: durationMs,
|
|
2495
|
-
message: message,
|
|
2496
|
-
fallback: gestureFallback(strategy: "xctest-coordinate-drag", from: outcome)
|
|
2497
|
-
)
|
|
2498
|
-
}
|
|
2499
|
-
logSynthesizedGesturePolicyDecision(kind: policyKind, context: plan.context, fallbackAttempted: false)
|
|
2500
|
-
return unsupportedResponse(for: outcome)
|
|
2501
|
-
#else
|
|
2502
|
-
return nil
|
|
2503
|
-
#endif
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
private func executeCoordinateDragFallback(
|
|
2507
|
-
activeApp: XCUIApplication,
|
|
2508
|
-
x: Double,
|
|
2509
|
-
y: Double,
|
|
2510
|
-
x2: Double,
|
|
2511
|
-
y2: Double,
|
|
2512
|
-
durationMs: Double,
|
|
2513
|
-
message: String,
|
|
2514
|
-
fallback: GestureFallback?
|
|
2515
|
-
) -> Response {
|
|
2516
|
-
let dragPoints = keyboardAvoidingDragPoints(app: activeApp, x: x, y: y, x2: x2, y2: y2)
|
|
2517
|
-
let dragFrame = resolvedDragVisualizationFrame(
|
|
2518
|
-
app: activeApp,
|
|
2519
|
-
x: dragPoints.x,
|
|
2520
|
-
y: dragPoints.y,
|
|
2521
|
-
x2: dragPoints.x2,
|
|
2522
|
-
y2: dragPoints.y2
|
|
2523
|
-
)
|
|
2524
|
-
let holdDuration = synthesizedSwipeFallbackHoldDuration(durationMs: durationMs)
|
|
2525
|
-
let (timing, outcome) = performGesture(activeApp) {
|
|
2526
|
-
dragAt(
|
|
2527
|
-
app: activeApp,
|
|
2528
|
-
x: dragPoints.x,
|
|
2529
|
-
y: dragPoints.y,
|
|
2530
|
-
x2: dragPoints.x2,
|
|
2531
|
-
y2: dragPoints.y2,
|
|
2532
|
-
holdDuration: holdDuration
|
|
2533
|
-
)
|
|
2534
|
-
}
|
|
2535
|
-
if let response = unsupportedResponse(for: outcome) {
|
|
2536
|
-
return response
|
|
2537
|
-
}
|
|
2538
|
-
return gestureResponse(
|
|
2539
|
-
message: message,
|
|
2540
|
-
timing: timing,
|
|
2541
|
-
frame: .drag(dragFrame),
|
|
2542
|
-
fallback: fallback
|
|
2543
|
-
)
|
|
2544
|
-
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
private func scrollKeyboardOccludedResponse(
|
|
2554
|
-
direction: String,
|
|
2555
|
-
keyboardMinY: Double,
|
|
2556
|
-
visibleHeight: Double
|
|
2557
|
-
) -> Response {
|
|
2558
|
-
return Response(
|
|
2559
|
-
ok: false,
|
|
2560
|
-
error: ErrorPayload(
|
|
2561
|
-
code: ScrollViewportPolicy.occlusionRunnerCode,
|
|
2562
|
-
message: String(
|
|
2563
|
-
format:
|
|
2564
|
-
"scroll %@ refused: the keyboard leaves %.0fpt of visible surface above it, too little to swipe",
|
|
2565
|
-
direction,
|
|
2566
|
-
visibleHeight
|
|
2567
|
-
)
|
|
2568
|
-
)
|
|
2569
|
-
)
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
private func dragCommandName(message: String) -> String {
|
|
2573
|
-
return message == "scrolled" ? "scroll" : "drag"
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
func currentXCTestFailureCount() -> Int {
|
|
2577
|
-
return testRun?.failureCount ?? 0
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
func didRecordXCTestFailure(since failureCountBefore: Int) -> Bool {
|
|
2581
|
-
return currentXCTestFailureCount() > failureCountBefore
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
private func xctestRecordedFailureResponse(command: Command, response: Response) -> Response? {
|
|
2585
|
-
guard response.ok else { return nil }
|
|
2586
|
-
if response.data?.runnerFatal == true {
|
|
2587
|
-
return nil
|
|
2588
|
-
}
|
|
2589
|
-
guard !isReadOnlyCommand(command), !isRunnerLifecycleCommand(command.command) else {
|
|
2590
|
-
return nil
|
|
2591
|
-
}
|
|
2592
|
-
return Response(
|
|
2593
|
-
ok: false,
|
|
2594
|
-
error: ErrorPayload(
|
|
2595
|
-
code: "XCTEST_RECORDED_FAILURE",
|
|
2596
|
-
message: "XCTest recorded a failure while executing \(command.command.rawValue); the action may not have been performed.",
|
|
2597
|
-
hint: "The iOS runner session was invalidated. Re-observe with a fresh snapshot before retrying; if the accessibility tree is unavailable, use screenshot plus coordinate commands instead of retrying the tap blindly."
|
|
2598
|
-
)
|
|
2599
|
-
)
|
|
2600
|
-
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
private func shouldSkipAppActivationPreflight(_ command: Command) -> Bool {
|
|
2609
|
-
#if os(iOS)
|
|
2610
|
-
if command.command == .alert {
|
|
2611
|
-
return true
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
if command.command == .actionButton {
|
|
2619
|
-
return true
|
|
2620
|
-
}
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
guard command.text == nil, command.selectorKey == nil else { return false }
|
|
2626
|
-
guard hasCachedTargetForActivationSkip(command: command) else { return false }
|
|
2627
|
-
return isCoordinateOnlyTap(command)
|
|
2628
|
-
#else
|
|
2629
|
-
return false
|
|
2630
|
-
#endif
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
private func shouldRouteToSpringboardBlockingSystemModal(
|
|
2634
|
-
_ command: Command
|
|
2635
|
-
) -> Bool {
|
|
2636
|
-
#if os(iOS)
|
|
2637
|
-
guard isCoordinateOnlyTap(command) else {
|
|
2638
|
-
return false
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
let probeDeadline = Date().addingTimeInterval(systemModalProbeBudget)
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
if Thread.isMainThread {
|
|
2650
|
-
return firstBlockingSystemModal(
|
|
2651
|
-
in: springboard,
|
|
2652
|
-
deadline: probeDeadline
|
|
2653
|
-
) != nil
|
|
2654
|
-
}
|
|
2655
|
-
return boundedBlockingSystemAlertSnapshot(
|
|
2656
|
-
deadline: probeDeadline
|
|
2657
|
-
) != nil
|
|
2658
|
-
#else
|
|
2659
|
-
return false
|
|
2660
|
-
#endif
|
|
2661
|
-
}
|
|
2662
|
-
|
|
2663
|
-
private func isCoordinateOnlyTap(_ command: Command) -> Bool {
|
|
2664
|
-
return command.command == .tap
|
|
2665
|
-
&& command.text == nil
|
|
2666
|
-
&& command.selectorKey == nil
|
|
2667
|
-
&& command.x != nil
|
|
2668
|
-
&& command.y != nil
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
private func hasCachedTargetForActivationSkip(command: Command) -> Bool {
|
|
2672
|
-
guard let currentApp, currentApp.state == .runningForeground else { return false }
|
|
2673
|
-
guard let bundleId = command.appBundleId?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
2674
|
-
!bundleId.isEmpty
|
|
2675
|
-
else {
|
|
2676
|
-
return true
|
|
2677
|
-
}
|
|
2678
|
-
return currentBundleId == bundleId
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
private func resolveAppWithoutActivation(command: Command) -> XCUIApplication {
|
|
2682
|
-
guard let bundleId = command.appBundleId?
|
|
2683
|
-
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
2684
|
-
!bundleId.isEmpty
|
|
2685
|
-
else {
|
|
2686
|
-
return currentApp ?? app
|
|
2687
|
-
}
|
|
2688
|
-
if currentBundleId == bundleId, let currentApp {
|
|
2689
|
-
return currentApp
|
|
2690
|
-
}
|
|
2691
|
-
return XCUIApplication(bundleIdentifier: bundleId)
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
func executeTypeCommand(activeApp: XCUIApplication, command: Command) -> Response {
|
|
2695
|
-
guard let text = command.text else {
|
|
2696
|
-
return Response(ok: false, error: ErrorPayload(message: "type requires text"))
|
|
2697
|
-
}
|
|
2698
|
-
let delaySeconds = Double(max(command.delayMs ?? 0, 0)) / 1000.0
|
|
2699
|
-
let textEntryMode = resolveTextEntryMode(command)
|
|
2700
|
-
let target: TextEntryTarget
|
|
2701
|
-
var resolvedCoordinateContext: SynthesizedCoordinateContext?
|
|
2702
|
-
var maestroNonHittableCoordinateFallbackUsed: Bool?
|
|
2703
|
-
if command.allowNonHittableCoordinateFallback == true,
|
|
2704
|
-
command.x != nil,
|
|
2705
|
-
command.y != nil
|
|
2706
|
-
{
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
maestroNonHittableCoordinateFallbackUsed = true
|
|
2710
|
-
}
|
|
2711
|
-
let focusStartedAt = Date()
|
|
2712
|
-
#if os(iOS)
|
|
2713
|
-
let xCTestChannelPenalized = isSnapshotXCTestChannelPenalized(bundleId: currentBundleId)
|
|
2714
|
-
var resolvedCoordinateTarget: TextEntryTarget?
|
|
2715
|
-
if Self.shouldUseResolvedCoordinateTextEntryRoute(
|
|
2716
|
-
repairMode: textEntryMode,
|
|
2717
|
-
hasX: command.x != nil,
|
|
2718
|
-
hasY: command.y != nil,
|
|
2719
|
-
xCTestChannelPenalized: xCTestChannelPenalized
|
|
2720
|
-
), let x = command.x, let y = command.y {
|
|
2721
|
-
let policyKind = SynthesizedGesturePolicyKind.coordinateTap
|
|
2722
|
-
let context = synthesizedCoordinateContext(
|
|
2723
|
-
app: activeApp,
|
|
2724
|
-
policy: synthesizedGesturePolicy(policyKind)
|
|
2725
|
-
)
|
|
2726
|
-
let (_, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
2727
|
-
synthesizedTapAt(app: activeApp, x: x, y: y, context: context)
|
|
2728
|
-
}
|
|
2729
|
-
if Self.shouldFallbackFromSynthesizedTextEntryFocus(outcome) {
|
|
2730
|
-
logSynthesizedGesturePolicyDecision(
|
|
2731
|
-
kind: policyKind,
|
|
2732
|
-
context: context,
|
|
2733
|
-
fallbackAttempted: true
|
|
2734
|
-
)
|
|
2735
|
-
} else {
|
|
2736
|
-
logSynthesizedGesturePolicyDecision(
|
|
2737
|
-
kind: policyKind,
|
|
2738
|
-
context: context,
|
|
2739
|
-
fallbackAttempted: false
|
|
2740
|
-
)
|
|
2741
|
-
resolvedCoordinateContext = context
|
|
2742
|
-
resolvedCoordinateTarget = TextEntryTarget(
|
|
2743
|
-
element: nil,
|
|
2744
|
-
refreshPoint: CGPoint(x: x, y: y),
|
|
2745
|
-
prefersFocusedElement: false
|
|
2746
|
-
)
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
#else
|
|
2750
|
-
let xCTestChannelPenalized = false
|
|
2751
|
-
let resolvedCoordinateTarget: TextEntryTarget? = nil
|
|
2752
|
-
#endif
|
|
2753
|
-
if let resolvedCoordinateTarget {
|
|
2754
|
-
target = resolvedCoordinateTarget
|
|
2755
|
-
} else if let selectorKey = command.selectorKey, let selectorValue = command.selectorValue {
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
let match = findElement(
|
|
2759
|
-
app: activeApp,
|
|
2760
|
-
selectorKey: selectorKey,
|
|
2761
|
-
selectorValue: selectorValue,
|
|
2762
|
-
allowNonHittableFallback: command.allowNonHittableCoordinateFallback == true
|
|
2763
|
-
)
|
|
2764
|
-
if match.isAmbiguous {
|
|
2765
|
-
return Response(ok: false, error: ErrorPayload(code: "AMBIGUOUS_MATCH", message: "selector matched multiple elements"))
|
|
2766
|
-
}
|
|
2767
|
-
guard let element = match.element else {
|
|
2768
|
-
return Response(ok: false, error: ErrorPayload(code: "NO_MATCH", message: "selector did not match an element"))
|
|
2769
|
-
}
|
|
2770
|
-
guard isTextEntryElement(element) else {
|
|
2771
|
-
return Response(ok: false, error: ErrorPayload(code: "INVALID_TARGET", message: "selector did not match a text input"))
|
|
2772
|
-
}
|
|
2773
|
-
if command.allowNonHittableCoordinateFallback == true {
|
|
2774
|
-
maestroNonHittableCoordinateFallbackUsed = match.usedNonHittableFallback
|
|
2775
|
-
}
|
|
2776
|
-
target = focusTextInputForTextEntry(app: activeApp, element: element)
|
|
2777
|
-
} else {
|
|
2778
|
-
target = focusTextInputForTextEntry(app: activeApp, x: command.x, y: command.y)
|
|
2779
|
-
}
|
|
2780
|
-
NSLog(
|
|
2781
|
-
"AGENT_DEVICE_RUNNER_TEXT_ENTRY_PHASE commandId=%@ phase=focus durationMs=%.1f chars=%d mode=%@",
|
|
2782
|
-
command.commandId ?? "",
|
|
2783
|
-
Date().timeIntervalSince(focusStartedAt) * 1000.0,
|
|
2784
|
-
text.count,
|
|
2785
|
-
textEntryModeName(textEntryMode)
|
|
2786
|
-
)
|
|
2787
|
-
if textEntryMode == .replacement {
|
|
2788
|
-
#if os(iOS)
|
|
2789
|
-
let canReplaceResolvedFirstResponder = Self.shouldUseSynthesizedFirstResponderReplacement(
|
|
2790
|
-
hasResolvedElement: target.element != nil,
|
|
2791
|
-
hasRefreshPoint: target.refreshPoint != nil,
|
|
2792
|
-
xCTestChannelPenalized: xCTestChannelPenalized
|
|
2793
|
-
)
|
|
2794
|
-
#else
|
|
2795
|
-
let canReplaceResolvedFirstResponder = false
|
|
2796
|
-
#endif
|
|
2797
|
-
guard target.element != nil || canReplaceResolvedFirstResponder else {
|
|
2798
|
-
let message =
|
|
2799
|
-
(command.x != nil && command.y != nil)
|
|
2800
|
-
? "no text input found at the provided coordinates to clear"
|
|
2801
|
-
: "no focused text input to clear"
|
|
2802
|
-
return Response(ok: false, error: ErrorPayload(message: message))
|
|
2803
|
-
}
|
|
2804
|
-
}
|
|
2805
|
-
let textResult = typeTextReliably(
|
|
2806
|
-
app: activeApp,
|
|
2807
|
-
target: target,
|
|
2808
|
-
text: text,
|
|
2809
|
-
delaySeconds: delaySeconds,
|
|
2810
|
-
repairMode: textEntryMode,
|
|
2811
|
-
xCTestChannelPenalized: xCTestChannelPenalized,
|
|
2812
|
-
synthesizer: PrivateXCTestTextEntrySynthesizer(),
|
|
2813
|
-
commandId: command.commandId
|
|
2814
|
-
)
|
|
2815
|
-
if let failure = textResult.failure {
|
|
2816
|
-
return Response(
|
|
2817
|
-
ok: false,
|
|
2818
|
-
error: ErrorPayload(code: failure.rawValue, message: failure.message, hint: failure.hint)
|
|
2819
|
-
)
|
|
2820
|
-
}
|
|
2821
|
-
if textResult.verified == false {
|
|
2822
|
-
let expected = textResult.expectedText ?? ""
|
|
2823
|
-
let observed = textResult.observedText ?? ""
|
|
2824
|
-
return Response(
|
|
2825
|
-
ok: false,
|
|
2826
|
-
error: ErrorPayload(
|
|
2827
|
-
code: "TEXT_ENTRY_MISMATCH",
|
|
2828
|
-
message: "text entry verification failed: expected \"\(expected)\", observed \"\(observed)\""
|
|
2829
|
-
)
|
|
2830
|
-
)
|
|
2831
|
-
}
|
|
2832
|
-
let point = target.refreshPoint
|
|
2833
|
-
let frame: CGRect
|
|
2834
|
-
if let resolvedCoordinateContext {
|
|
2835
|
-
frame = resolvedCoordinateContext.referenceFrame
|
|
2836
|
-
} else if point != nil {
|
|
2837
|
-
frame = activeApp.frame
|
|
2838
|
-
} else {
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
frame = .zero
|
|
2842
|
-
}
|
|
2843
|
-
return Response(
|
|
2844
|
-
ok: true,
|
|
2845
|
-
data: DataPayload(
|
|
2846
|
-
message: textResult.repaired ? "typed after repair" : "typed",
|
|
2847
|
-
x: point.map { Double($0.x) },
|
|
2848
|
-
y: point.map { Double($0.y) },
|
|
2849
|
-
referenceWidth: frame.isEmpty ? nil : Double(frame.width),
|
|
2850
|
-
referenceHeight: frame.isEmpty ? nil : Double(frame.height),
|
|
2851
|
-
maestroNonHittableCoordinateFallbackUsed: maestroNonHittableCoordinateFallbackUsed,
|
|
2852
|
-
textEntryRoute: textResult.textEntryRoute
|
|
2853
|
-
)
|
|
2854
|
-
)
|
|
2855
|
-
}
|
|
2856
638
|
}
|