agent-device 0.21.7 → 0.21.9
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.7.apk → agent-device-android-ime-helper-0.21.9.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.9.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.7.manifest.json → agent-device-android-ime-helper-0.21.9.manifest.json} +4 -4
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.7.apk → agent-device-android-snapshot-helper-0.21.9.apk} +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.9.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.7.manifest.json → agent-device-android-snapshot-helper-0.21.9.manifest.json} +6 -6
- package/apple/macos-helper/Package.swift +16 -1
- package/apple/macos-helper/Sources/AgentDeviceMacOSDeviceHub/DeviceHubPose.swift +47 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSDeviceHub/DeviceHubWindowInventory.swift +65 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubPose.swift +225 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubSidebar.swift +57 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubWindowDiscovery.swift +108 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +66 -13
- package/apple/macos-helper/Sources/AgentDeviceMacOSInput/MouseClickDelivery.swift +116 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSInput/MouseClickSchedule.swift +61 -0
- package/apple/macos-helper/Tests/AgentDeviceMacOSDeviceHubTests/DeviceHubPoseTests.swift +30 -0
- package/apple/macos-helper/Tests/AgentDeviceMacOSDeviceHubTests/DeviceHubWindowInventoryTests.swift +65 -0
- package/apple/macos-helper/Tests/AgentDeviceMacOSInputTests/MouseClickScheduleTests.swift +58 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAppScreenCapture.swift +175 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +10 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +42 -15
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +8 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +45 -22
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +39 -734
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +20 -4
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +63 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +13 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +25 -10
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +5 -562
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotAcquisition.swift +566 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +5 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +594 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.h +35 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.m +160 -0
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +3 -0
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-frames.swift +203 -0
- package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotCoordinateSpace.swift +5 -0
- package/dist/src/absence-observation-errors.js +1 -1
- package/dist/src/adapter.js +1 -1
- package/dist/src/agent-browser-lifecycle.js +1 -1
- package/dist/src/agent-browser-process-record.js +1 -1
- package/dist/src/agent-device-client.js +2 -2
- package/dist/src/alert-runtime.js +1 -1
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-input-ownership.js +1 -1
- package/dist/src/android.js +1 -1
- package/dist/src/app-launch.js +1 -1
- package/dist/src/app-log-runtime2.js +3 -3
- package/dist/src/app-resolution.js +2 -2
- package/dist/src/apple-runner-platform.js +1 -1
- package/dist/src/application-lifecycle-interaction.js +1 -1
- package/dist/src/artifact-paths.js +1 -0
- package/dist/src/back-mode.js +1 -1
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +15 -11
- package/dist/src/cli.js +4 -4
- package/dist/src/client-types.d.ts +51 -1
- package/dist/src/command-tools.js +3 -3
- package/dist/src/config.js +1 -1
- package/dist/src/connection-runtime.js +1 -1
- package/dist/src/device-claim-inspection.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-rotation.js +1 -1
- package/dist/src/device-selection-resolver.js +1 -1
- package/dist/src/device-session.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/device2.js +1 -1
- package/dist/src/devicectl.js +1 -1
- package/dist/src/dispatch-resolve.js +1 -1
- package/dist/src/display-inventory.js +1 -1
- package/dist/src/doctor-facade.js +1 -1
- package/dist/src/durable-resource.js +1 -1
- package/dist/src/exec.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/finders.js +1 -1
- package/dist/src/fold-runtime.js +1 -0
- package/dist/src/gesture-admission.js +1 -1
- package/dist/src/gesture-runtime.js +1 -1
- package/dist/src/harmonyos.js +1 -1
- package/dist/src/helper.js +2 -2
- package/dist/src/host.js +1 -0
- package/dist/src/interaction-positionals.js +1 -1
- package/dist/src/interaction-ref-policy.js +1 -1
- package/dist/src/interaction.js +1 -1
- package/dist/src/interaction2.js +1 -1
- package/dist/src/interactor-operation-binding.js +1 -0
- package/dist/src/interactor-operation-catalog.js +1 -1
- package/dist/src/interactor.js +4 -5
- package/dist/src/interactor2.js +1 -1
- package/dist/src/interactors.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +2 -2
- package/dist/src/internal/png-worker.d.ts +5 -0
- package/dist/src/internal/png-worker.js +1 -1
- 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/lease.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/local-interactor-operation-set.js +1 -1
- package/dist/src/location-coordinates.js +1 -1
- package/dist/src/macos-facade.js +1 -1
- package/dist/src/managed-device-scope.js +1 -1
- package/dist/src/mechanics.js +17 -16
- package/dist/src/mobile-snapshot-semantics.js +1 -1
- package/dist/src/mp4-duration.js +1 -0
- package/dist/src/multitouch-support.js +1 -1
- package/dist/src/owner-identity.js +1 -1
- package/dist/src/parameterized-recorded-fill.js +1 -1
- package/dist/src/perf-facade.js +2 -2
- package/dist/src/perf-runtime-plan.js +1 -1
- package/dist/src/physical-device-control.js +1 -1
- package/dist/src/platform-request-scope.js +1 -1
- package/dist/src/platform-runtime-managed-owner.js +1 -1
- package/dist/src/platform-runtime-open-target.js +1 -1
- package/dist/src/platform-runtime-operation-host.js +2 -2
- package/dist/src/platform-runtime-runtime-hints.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/png-crop-bytes.js +1 -1
- package/dist/src/png-encode.js +1 -0
- package/dist/src/png-size.js +1 -0
- package/dist/src/png-worker-client.js +1 -1
- package/dist/src/provider-webdriver.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/recording-export-quality.js +1 -1
- package/dist/src/recording-scope.js +1 -0
- package/dist/src/recording.js +3 -0
- package/dist/src/registry.js +35 -35
- package/dist/src/replay.js +1 -1
- package/dist/src/request-admission.js +1 -1
- package/dist/src/request-runtime-binding.js +1 -1
- package/dist/src/result-serialization.js +1 -1
- package/dist/src/runner-cache-metadata.js +2 -2
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-contract.js +3 -0
- package/dist/src/runner-dev-tools-security.js +1 -0
- package/dist/src/runner-device-readiness.js +1 -0
- package/dist/src/runner-disposal.js +1 -1
- 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 +2 -2
- package/dist/src/runtime10.js +2 -1
- package/dist/src/runtime2.js +4 -4
- 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-density.js +1 -1
- package/dist/src/screenshot-image.js +1 -0
- package/dist/src/screenshot-overlay-draw.js +1 -0
- package/dist/src/screenshot-runtime.js +1 -1
- package/dist/src/script.js +1 -1
- package/dist/src/scroll-gesture.js +1 -1
- package/dist/src/sdk-batch.js +2 -2
- package/dist/src/sdk-contracts.d.ts +7 -5
- package/dist/src/sdk-contracts.js +2 -2
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-finders.js +1 -1
- package/dist/src/sdk-selectors.d.ts +58 -6
- package/dist/src/sdk-selectors.js +1 -1
- package/dist/src/selection.js +0 -0
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selectors.js +1 -1
- package/dist/src/semantic-index.js +1 -1
- package/dist/src/session-routing.js +1 -1
- package/dist/src/session-snapshot.js +1 -1
- package/dist/src/session-store.js +3 -3
- package/dist/src/session2.js +3 -3
- package/dist/src/simctl.js +1 -1
- package/dist/src/simulator-boot.js +1 -1
- package/dist/src/simulator-facade.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/src.js +1 -1
- package/dist/src/src7.js +1 -1
- package/dist/src/src8.js +1 -1
- package/dist/src/stop-sequence.js +1 -1
- package/dist/src/text-entry-focus.js +1 -1
- package/dist/src/touch-runtime.js +1 -1
- package/dist/src/tree.js +1 -1
- package/dist/src/type-text-runtime.js +1 -1
- package/dist/src/video.js +2 -2
- package/dist/src/web2.js +1 -1
- package/package.json +3 -1
- package/server.json +2 -2
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.7.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.7.apk.sha256 +0 -1
- package/dist/src/png-transcode.js +0 -1
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
import AgentDeviceSnapshotPresentation
|
|
3
|
+
|
|
4
|
+
extension RunnerTests {
|
|
5
|
+
func synthesizedDragAt(
|
|
6
|
+
app: XCUIApplication,
|
|
7
|
+
x: Double,
|
|
8
|
+
y: Double,
|
|
9
|
+
x2: Double,
|
|
10
|
+
y2: Double,
|
|
11
|
+
durationMs: Double,
|
|
12
|
+
profile: SynthesizedDragProfile = .continuous,
|
|
13
|
+
context: SynthesizedCoordinateContext? = nil
|
|
14
|
+
) -> RunnerInteractionOutcome {
|
|
15
|
+
#if os(iOS)
|
|
16
|
+
guard x.isFinite, y.isFinite, x2.isFinite, y2.isFinite else {
|
|
17
|
+
return .unsupported(
|
|
18
|
+
message: "synthesized coordinate drag requires finite coordinates",
|
|
19
|
+
hint: "Retry with finite x, y, x2, and y2 values."
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
let orientation = Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
23
|
+
guard let context = context ?? synthesizedCoordinateContext(
|
|
24
|
+
app: app,
|
|
25
|
+
policy: synthesizedGesturePolicy(.synthesizedDrag)
|
|
26
|
+
) else {
|
|
27
|
+
return .unsupported(
|
|
28
|
+
message: "synthesized coordinate drag could not resolve an app window with a finite screen frame",
|
|
29
|
+
hint: "Retry after the app is foregrounded, or use a plain screenshot to choose coordinates."
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
let frame = context.referenceFrame
|
|
33
|
+
let start = CoordinateSpaceRotation.native(
|
|
34
|
+
point: CGPoint(x: x, y: y),
|
|
35
|
+
in: frame,
|
|
36
|
+
interfaceOrientation: orientation
|
|
37
|
+
)
|
|
38
|
+
let end = CoordinateSpaceRotation.native(
|
|
39
|
+
point: CGPoint(x: x2, y: y2),
|
|
40
|
+
in: frame,
|
|
41
|
+
interfaceOrientation: orientation
|
|
42
|
+
)
|
|
43
|
+
logSynthesizedDispatch(
|
|
44
|
+
kind: "drag",
|
|
45
|
+
start: start,
|
|
46
|
+
end: end,
|
|
47
|
+
referenceFrame: frame,
|
|
48
|
+
orientation: orientation
|
|
49
|
+
)
|
|
50
|
+
let message = switch profile {
|
|
51
|
+
case .continuous:
|
|
52
|
+
RunnerSynthesizedGesture.synthesizeContinuousDrag(
|
|
53
|
+
withApplication: app,
|
|
54
|
+
resolvedWindow: context.resolvedWindow,
|
|
55
|
+
x: Double(start.x),
|
|
56
|
+
y: Double(start.y),
|
|
57
|
+
x2: Double(end.x),
|
|
58
|
+
y2: Double(end.y),
|
|
59
|
+
durationMs: durationMs
|
|
60
|
+
)
|
|
61
|
+
case .controlledScroll:
|
|
62
|
+
RunnerSynthesizedGesture.synthesizeControlledScroll(
|
|
63
|
+
withApplication: app,
|
|
64
|
+
resolvedWindow: context.resolvedWindow,
|
|
65
|
+
x: Double(start.x),
|
|
66
|
+
y: Double(start.y),
|
|
67
|
+
x2: Double(end.x),
|
|
68
|
+
y2: Double(end.y),
|
|
69
|
+
durationMs: durationMs
|
|
70
|
+
)
|
|
71
|
+
case .fastSwipe:
|
|
72
|
+
RunnerSynthesizedGesture.synthesizeSwipe(
|
|
73
|
+
withApplication: app,
|
|
74
|
+
resolvedWindow: context.resolvedWindow,
|
|
75
|
+
x: Double(start.x),
|
|
76
|
+
y: Double(start.y),
|
|
77
|
+
x2: Double(end.x),
|
|
78
|
+
y2: Double(end.y),
|
|
79
|
+
durationMs: durationMs
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
if let message {
|
|
83
|
+
return .unsupported(
|
|
84
|
+
message: message,
|
|
85
|
+
hint: "Private XCTest event synthesis is required for AX-free coordinate drag on iOS; update Xcode if this persists."
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return .performed
|
|
89
|
+
#elseif os(tvOS)
|
|
90
|
+
return .unsupported(
|
|
91
|
+
message: "coordinate drag is not supported on tvOS",
|
|
92
|
+
hint: "tvOS has no coordinate input; use remote-driven swipe/scroll to move focus instead."
|
|
93
|
+
)
|
|
94
|
+
#else
|
|
95
|
+
return .unsupported(
|
|
96
|
+
message: "coordinate drag is not supported on macOS",
|
|
97
|
+
hint: "macOS automation has no touchscreen; use mouse-driven interactions instead."
|
|
98
|
+
)
|
|
99
|
+
#endif
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func synthesizedTapAt(
|
|
103
|
+
app: XCUIApplication,
|
|
104
|
+
x: Double,
|
|
105
|
+
y: Double,
|
|
106
|
+
context: SynthesizedCoordinateContext? = nil
|
|
107
|
+
) -> RunnerInteractionOutcome {
|
|
108
|
+
#if os(iOS)
|
|
109
|
+
guard x.isFinite, y.isFinite else {
|
|
110
|
+
return .unsupported(
|
|
111
|
+
message: "synthesized coordinate tap requires finite coordinates",
|
|
112
|
+
hint: "Retry with finite x and y values."
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
let orientation = Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
116
|
+
guard let context = context ?? synthesizedCoordinateContext(
|
|
117
|
+
app: app,
|
|
118
|
+
policy: synthesizedGesturePolicy(.coordinateTap)
|
|
119
|
+
) else {
|
|
120
|
+
return .unsupported(
|
|
121
|
+
message: "synthesized coordinate tap could not resolve an app window with a finite screen frame",
|
|
122
|
+
hint: "Retry after the app is foregrounded, or use a plain screenshot to choose coordinates."
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
let point = CoordinateSpaceRotation.native(
|
|
126
|
+
point: CGPoint(x: x, y: y),
|
|
127
|
+
in: context.referenceFrame,
|
|
128
|
+
interfaceOrientation: orientation
|
|
129
|
+
)
|
|
130
|
+
logSynthesizedDispatch(
|
|
131
|
+
kind: "tap",
|
|
132
|
+
start: point,
|
|
133
|
+
end: nil,
|
|
134
|
+
referenceFrame: context.referenceFrame,
|
|
135
|
+
orientation: orientation
|
|
136
|
+
)
|
|
137
|
+
if let message = RunnerSynthesizedGesture.synthesizeTap(
|
|
138
|
+
withApplication: app,
|
|
139
|
+
resolvedWindow: context.resolvedWindow,
|
|
140
|
+
x: Double(point.x),
|
|
141
|
+
y: Double(point.y)
|
|
142
|
+
) {
|
|
143
|
+
return .unsupported(
|
|
144
|
+
message: message,
|
|
145
|
+
hint: "Falling back to XCTest coordinate tap may be slower and can still need a healthy accessibility tree."
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
return .performed
|
|
149
|
+
#elseif os(tvOS)
|
|
150
|
+
return .unsupported(
|
|
151
|
+
message: "coordinate tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element",
|
|
152
|
+
hint: "tvOS has no coordinate input; move focus with swipe/scroll to the target, then select it."
|
|
153
|
+
)
|
|
154
|
+
#else
|
|
155
|
+
return .unsupported(
|
|
156
|
+
message: "synthesized coordinate tap is not supported on macOS",
|
|
157
|
+
hint: "macOS automation has no touchscreen; use mouse-driven interactions instead."
|
|
158
|
+
)
|
|
159
|
+
#endif
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func keyboardAvoidingDragPoints(
|
|
163
|
+
app: XCUIApplication,
|
|
164
|
+
x: Double,
|
|
165
|
+
y: Double,
|
|
166
|
+
x2: Double,
|
|
167
|
+
y2: Double
|
|
168
|
+
) -> DragPoints {
|
|
169
|
+
let original = DragPoints(x: x, y: y, x2: x2, y2: y2)
|
|
170
|
+
#if os(iOS)
|
|
171
|
+
guard let keyboardFrame = visibleKeyboardFrame(app: app) else {
|
|
172
|
+
return original
|
|
173
|
+
}
|
|
174
|
+
let minX = min(x, x2)
|
|
175
|
+
let minY = min(y, y2)
|
|
176
|
+
let gestureBounds = CGRect(
|
|
177
|
+
x: CGFloat(minX),
|
|
178
|
+
y: CGFloat(minY),
|
|
179
|
+
width: CGFloat(max(abs(x2 - x), 1)),
|
|
180
|
+
height: CGFloat(max(abs(y2 - y), 1))
|
|
181
|
+
)
|
|
182
|
+
guard gestureBounds.intersects(keyboardFrame) else {
|
|
183
|
+
return original
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let appFrame = onScreenWindowFrame(app: app)
|
|
187
|
+
guard !appFrame.isEmpty else {
|
|
188
|
+
return original
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let padding: Double = 12
|
|
192
|
+
let targetMaxY = Double(keyboardFrame.minY) - padding
|
|
193
|
+
let currentMaxY = max(y, y2)
|
|
194
|
+
let shift = currentMaxY - targetMaxY
|
|
195
|
+
guard shift > 0 else {
|
|
196
|
+
return original
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let adjustedY = y - shift
|
|
200
|
+
let adjustedY2 = y2 - shift
|
|
201
|
+
guard min(adjustedY, adjustedY2) >= Double(appFrame.minY) + padding else {
|
|
202
|
+
return original
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
NSLog(
|
|
206
|
+
"AGENT_DEVICE_RUNNER_KEYBOARD_AVOIDING_DRAG from=(%.1f,%.1f)->(%.1f,%.1f) adjusted=(%.1f,%.1f)->(%.1f,%.1f) keyboardMinY=%.1f",
|
|
207
|
+
x,
|
|
208
|
+
y,
|
|
209
|
+
x2,
|
|
210
|
+
y2,
|
|
211
|
+
x,
|
|
212
|
+
adjustedY,
|
|
213
|
+
x2,
|
|
214
|
+
adjustedY2,
|
|
215
|
+
Double(keyboardFrame.minY)
|
|
216
|
+
)
|
|
217
|
+
return DragPoints(x: x, y: adjustedY, x2: x2, y2: adjustedY2)
|
|
218
|
+
#else
|
|
219
|
+
return original
|
|
220
|
+
#endif
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func resolvedTouchVisualizationFrame(app: XCUIApplication, x: Double, y: Double) -> TouchVisualizationFrame {
|
|
224
|
+
let appFrame = app.frame
|
|
225
|
+
let referenceFrame = resolvedTouchReferenceFrame(app: app, appFrame: appFrame)
|
|
226
|
+
let originX = appFrame.isEmpty ? referenceFrame.minX : appFrame.minX
|
|
227
|
+
let originY = appFrame.isEmpty ? referenceFrame.minY : appFrame.minY
|
|
228
|
+
return TouchVisualizationFrame(
|
|
229
|
+
x: originX + x,
|
|
230
|
+
y: originY + y,
|
|
231
|
+
referenceWidth: referenceFrame.width,
|
|
232
|
+
referenceHeight: referenceFrame.height
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
func resolvedDragVisualizationFrame(
|
|
237
|
+
app: XCUIApplication,
|
|
238
|
+
x: Double,
|
|
239
|
+
y: Double,
|
|
240
|
+
x2: Double,
|
|
241
|
+
y2: Double
|
|
242
|
+
) -> DragVisualizationFrame {
|
|
243
|
+
let start = resolvedTouchVisualizationFrame(app: app, x: x, y: y)
|
|
244
|
+
let end = resolvedTouchVisualizationFrame(app: app, x: x2, y: y2)
|
|
245
|
+
return DragVisualizationFrame(
|
|
246
|
+
x: start.x,
|
|
247
|
+
y: start.y,
|
|
248
|
+
x2: end.x,
|
|
249
|
+
y2: end.y,
|
|
250
|
+
referenceWidth: start.referenceWidth,
|
|
251
|
+
referenceHeight: start.referenceHeight
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func resolvedTouchReferenceFrame(app: XCUIApplication, appFrame: CGRect) -> CGRect {
|
|
256
|
+
let resolved = resolveRunnerWindow(app: app)
|
|
257
|
+
if resolved.window != nil {
|
|
258
|
+
return frameAvoidingKeyboard(app: app, frame: resolved.frame)
|
|
259
|
+
}
|
|
260
|
+
if !appFrame.isEmpty {
|
|
261
|
+
return frameAvoidingKeyboard(app: app, frame: appFrame)
|
|
262
|
+
}
|
|
263
|
+
return CGRect(x: 0, y: 0, width: 0, height: 0)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private func frameAvoidingKeyboard(app: XCUIApplication, frame: CGRect) -> CGRect {
|
|
267
|
+
#if os(iOS)
|
|
268
|
+
guard let keyboardFrame = visibleKeyboardFrame(app: app), !frame.isEmpty else {
|
|
269
|
+
return frame
|
|
270
|
+
}
|
|
271
|
+
let intersection = frame.intersection(keyboardFrame)
|
|
272
|
+
guard !intersection.isNull && intersection.height > 0 else {
|
|
273
|
+
return frame
|
|
274
|
+
}
|
|
275
|
+
let keyboardCoverage = intersection.width / max(frame.width, 1)
|
|
276
|
+
guard keyboardCoverage >= 0.5 else {
|
|
277
|
+
return frame
|
|
278
|
+
}
|
|
279
|
+
let safeHeight = keyboardFrame.minY - frame.minY
|
|
280
|
+
guard safeHeight >= frame.height * 0.25 else {
|
|
281
|
+
return frame
|
|
282
|
+
}
|
|
283
|
+
return CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: safeHeight)
|
|
284
|
+
#else
|
|
285
|
+
return frame
|
|
286
|
+
#endif
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
func axFreeSynthesizedDragPlan(
|
|
290
|
+
app: XCUIApplication,
|
|
291
|
+
x: Double,
|
|
292
|
+
y: Double,
|
|
293
|
+
x2: Double,
|
|
294
|
+
y2: Double,
|
|
295
|
+
context: SynthesizedCoordinateContext? = nil
|
|
296
|
+
) -> SynthesizedDragPlan? {
|
|
297
|
+
#if os(iOS)
|
|
298
|
+
let context = context ?? synthesizedCoordinateContext(
|
|
299
|
+
app: app,
|
|
300
|
+
policy: synthesizedGesturePolicy(.synthesizedDrag)
|
|
301
|
+
)
|
|
302
|
+
guard x.isFinite, y.isFinite, x2.isFinite, y2.isFinite,
|
|
303
|
+
let context
|
|
304
|
+
else {
|
|
305
|
+
return nil
|
|
306
|
+
}
|
|
307
|
+
let points = keyboardAvoidingSynthesizedDragPoints(
|
|
308
|
+
app: app,
|
|
309
|
+
x: x,
|
|
310
|
+
y: y,
|
|
311
|
+
x2: x2,
|
|
312
|
+
y2: y2,
|
|
313
|
+
context: context
|
|
314
|
+
)
|
|
315
|
+
return SynthesizedDragPlan(
|
|
316
|
+
points: points,
|
|
317
|
+
context: context
|
|
318
|
+
)
|
|
319
|
+
#else
|
|
320
|
+
return nil
|
|
321
|
+
#endif
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
func axFreeDragVisualizationFrame(
|
|
325
|
+
x: Double,
|
|
326
|
+
y: Double,
|
|
327
|
+
x2: Double,
|
|
328
|
+
y2: Double,
|
|
329
|
+
referenceFrame: CGRect
|
|
330
|
+
) -> DragVisualizationFrame {
|
|
331
|
+
return DragVisualizationFrame(
|
|
332
|
+
x: x,
|
|
333
|
+
y: y,
|
|
334
|
+
x2: x2,
|
|
335
|
+
y2: y2,
|
|
336
|
+
referenceWidth: Double(referenceFrame.width),
|
|
337
|
+
referenceHeight: Double(referenceFrame.height)
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
func logSynthesizedDispatch(
|
|
342
|
+
kind: String,
|
|
343
|
+
start: CGPoint,
|
|
344
|
+
end: CGPoint?,
|
|
345
|
+
referenceFrame: CGRect,
|
|
346
|
+
orientation: Int
|
|
347
|
+
) {
|
|
348
|
+
if let end {
|
|
349
|
+
NSLog(
|
|
350
|
+
"AGENT_DEVICE_RUNNER_SYNTHESIZED_DISPATCH kind=%@ start=(%.1f,%.1f) end=(%.1f,%.1f) reference=(%.1f,%.1f,%.1f,%.1f) orientation=%d",
|
|
351
|
+
kind,
|
|
352
|
+
Double(start.x),
|
|
353
|
+
Double(start.y),
|
|
354
|
+
Double(end.x),
|
|
355
|
+
Double(end.y),
|
|
356
|
+
Double(referenceFrame.origin.x),
|
|
357
|
+
Double(referenceFrame.origin.y),
|
|
358
|
+
Double(referenceFrame.width),
|
|
359
|
+
Double(referenceFrame.height),
|
|
360
|
+
orientation
|
|
361
|
+
)
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
NSLog(
|
|
365
|
+
"AGENT_DEVICE_RUNNER_SYNTHESIZED_DISPATCH kind=%@ point=(%.1f,%.1f) reference=(%.1f,%.1f,%.1f,%.1f) orientation=%d",
|
|
366
|
+
kind,
|
|
367
|
+
Double(start.x),
|
|
368
|
+
Double(start.y),
|
|
369
|
+
Double(referenceFrame.origin.x),
|
|
370
|
+
Double(referenceFrame.origin.y),
|
|
371
|
+
Double(referenceFrame.width),
|
|
372
|
+
Double(referenceFrame.height),
|
|
373
|
+
orientation
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
func synthesizedCoordinateContext(
|
|
378
|
+
app: XCUIApplication,
|
|
379
|
+
policy: SynthesizedGesturePolicy
|
|
380
|
+
) -> SynthesizedCoordinateContext? {
|
|
381
|
+
#if os(iOS)
|
|
382
|
+
let health = runnerAccessibilityHealth
|
|
383
|
+
let resolved = resolveRunnerWindow(app: app)
|
|
384
|
+
guard let window = resolved.window else {
|
|
385
|
+
return nil
|
|
386
|
+
}
|
|
387
|
+
let referenceFrame = resolved.frame
|
|
388
|
+
guard referenceFrame.width.isFinite, referenceFrame.height.isFinite,
|
|
389
|
+
referenceFrame.width > 0, referenceFrame.height > 0
|
|
390
|
+
else {
|
|
391
|
+
return nil
|
|
392
|
+
}
|
|
393
|
+
return SynthesizedCoordinateContext(
|
|
394
|
+
referenceFrame: referenceFrame,
|
|
395
|
+
resolvedWindow: window,
|
|
396
|
+
keyboardPolicy: policy.keyboardPolicy,
|
|
397
|
+
fallbackPolicy: policy.fallbackPolicy,
|
|
398
|
+
accessibilityHealth: health
|
|
399
|
+
)
|
|
400
|
+
#else
|
|
401
|
+
return nil
|
|
402
|
+
#endif
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
func keyboardAvoidingSynthesizedDragPoints(
|
|
407
|
+
app: XCUIApplication,
|
|
408
|
+
x: Double,
|
|
409
|
+
y: Double,
|
|
410
|
+
x2: Double,
|
|
411
|
+
y2: Double,
|
|
412
|
+
context: SynthesizedCoordinateContext
|
|
413
|
+
) -> DragPoints {
|
|
414
|
+
#if os(iOS)
|
|
415
|
+
guard context.allowsKeyboardProbe else {
|
|
416
|
+
return DragPoints(x: x, y: y, x2: x2, y2: y2)
|
|
417
|
+
}
|
|
418
|
+
return keyboardAvoidingDragPoints(app: app, x: x, y: y, x2: x2, y2: y2)
|
|
419
|
+
#else
|
|
420
|
+
return DragPoints(x: x, y: y, x2: x2, y2: y2)
|
|
421
|
+
#endif
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
func swipe(app: XCUIApplication, direction: String) -> DragVisualizationFrame? {
|
|
425
|
+
if performTvRemoteSwipeIfAvailable(direction: direction) {
|
|
426
|
+
let frame = resolvedTouchReferenceFrame(app: app, appFrame: app.frame)
|
|
427
|
+
let midX = frame.midX
|
|
428
|
+
let midY = frame.midY
|
|
429
|
+
return DragVisualizationFrame(
|
|
430
|
+
x: midX,
|
|
431
|
+
y: midY,
|
|
432
|
+
x2: midX,
|
|
433
|
+
y2: midY,
|
|
434
|
+
referenceWidth: frame.width,
|
|
435
|
+
referenceHeight: frame.height
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
return nil
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private func performTvRemoteSwipeIfAvailable(direction: String) -> Bool {
|
|
442
|
+
switch direction {
|
|
443
|
+
case "up":
|
|
444
|
+
return pressTvRemote(.up)
|
|
445
|
+
case "down":
|
|
446
|
+
return pressTvRemote(.down)
|
|
447
|
+
case "left":
|
|
448
|
+
return pressTvRemote(.left)
|
|
449
|
+
case "right":
|
|
450
|
+
return pressTvRemote(.right)
|
|
451
|
+
default:
|
|
452
|
+
return false
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
func plannedGestureValidationError(_ plan: RunnerGesturePlan) -> String? {
|
|
457
|
+
guard plan.topology == "single" || plan.topology == "two" else {
|
|
458
|
+
return "planned gesture topology must be single or two"
|
|
459
|
+
}
|
|
460
|
+
let supportedIntent = plan.topology == "single"
|
|
461
|
+
? plan.intent == "fling" || plan.intent == "pan"
|
|
462
|
+
: plan.intent == "pan" || plan.intent == "pinch" || plan.intent == "rotate"
|
|
463
|
+
|| plan.intent == "transform"
|
|
464
|
+
guard supportedIntent else { return "planned gesture has unsupported intent for its topology" }
|
|
465
|
+
if plan.topology == "single" {
|
|
466
|
+
guard plan.executionProfile == "endpoint-hold" || plan.executionProfile == "timed-pan" else {
|
|
467
|
+
return "single-pointer gesture requires a supported execution profile"
|
|
468
|
+
}
|
|
469
|
+
} else if plan.executionProfile != nil {
|
|
470
|
+
return "multi-touch gesture cannot define a single-pointer execution profile"
|
|
471
|
+
}
|
|
472
|
+
guard plan.durationMs.isFinite, plan.durationMs >= 16, plan.durationMs <= 10_000 else {
|
|
473
|
+
return "planned gesture durationMs must be between 16 and 10000"
|
|
474
|
+
}
|
|
475
|
+
let viewport = plan.viewport
|
|
476
|
+
guard viewport.x.isFinite, viewport.y.isFinite, viewport.width.isFinite,
|
|
477
|
+
viewport.height.isFinite, viewport.width > 0, viewport.height > 0
|
|
478
|
+
else {
|
|
479
|
+
return "planned gesture viewport must be finite and positive"
|
|
480
|
+
}
|
|
481
|
+
let expectedPointerCount = plan.topology == "single" ? 1 : 2
|
|
482
|
+
guard plan.pointers.count == expectedPointerCount else {
|
|
483
|
+
return "planned gesture pointer count does not match topology"
|
|
484
|
+
}
|
|
485
|
+
for (index, pointer) in plan.pointers.enumerated() where pointer.pointerId != index {
|
|
486
|
+
return "planned gesture requires ordered pointer ids"
|
|
487
|
+
}
|
|
488
|
+
let firstSamples = plan.pointers[0].samples
|
|
489
|
+
guard firstSamples.count >= 2 else { return "planned pointer paths require at least two samples" }
|
|
490
|
+
for pointer in plan.pointers {
|
|
491
|
+
guard pointer.samples.count == firstSamples.count else {
|
|
492
|
+
return "planned pointer paths require matching samples"
|
|
493
|
+
}
|
|
494
|
+
var previousOffset = -1.0
|
|
495
|
+
for (index, sample) in pointer.samples.enumerated() {
|
|
496
|
+
guard sample.offsetMs.isFinite,
|
|
497
|
+
sample.offsetMs == firstSamples[index].offsetMs,
|
|
498
|
+
sample.offsetMs > previousOffset
|
|
499
|
+
else {
|
|
500
|
+
return "planned pointer sample offsets must match and strictly increase"
|
|
501
|
+
}
|
|
502
|
+
let point = sample.point
|
|
503
|
+
guard point.x.isFinite, point.y.isFinite,
|
|
504
|
+
point.x >= viewport.x,
|
|
505
|
+
point.x <= viewport.x + viewport.width,
|
|
506
|
+
point.y >= viewport.y,
|
|
507
|
+
point.y <= viewport.y + viewport.height
|
|
508
|
+
else {
|
|
509
|
+
return "planned pointer sample lies outside the viewport"
|
|
510
|
+
}
|
|
511
|
+
previousOffset = sample.offsetMs
|
|
512
|
+
}
|
|
513
|
+
guard pointer.samples.first?.offsetMs == 0,
|
|
514
|
+
pointer.samples.last?.offsetMs == plan.durationMs
|
|
515
|
+
else {
|
|
516
|
+
return "planned pointer paths must start at 0 and end at durationMs"
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if plan.topology == "two" {
|
|
520
|
+
guard let firstStart = firstSamples.first?.point,
|
|
521
|
+
let secondStart = plan.pointers[1].samples.first?.point,
|
|
522
|
+
hypot(firstStart.x - secondStart.x, firstStart.y - secondStart.y) > 0
|
|
523
|
+
else {
|
|
524
|
+
return "planned pointer paths require a positive initial span"
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return nil
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
func plannedGestureExecution(for plan: RunnerGesturePlan) -> PlannedGestureExecution {
|
|
531
|
+
plan.topology == "single" && plan.executionProfile == "endpoint-hold"
|
|
532
|
+
? .fastSwipe
|
|
533
|
+
: .sampled
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
func sampledPlannedGesture(
|
|
537
|
+
app: XCUIApplication,
|
|
538
|
+
plan: RunnerGesturePlan
|
|
539
|
+
) -> RunnerInteractionOutcome {
|
|
540
|
+
#if os(iOS)
|
|
541
|
+
let orientation = Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
let frame = CGRect(
|
|
545
|
+
x: plan.viewport.x,
|
|
546
|
+
y: plan.viewport.y,
|
|
547
|
+
width: plan.viewport.width,
|
|
548
|
+
height: plan.viewport.height
|
|
549
|
+
)
|
|
550
|
+
let pointerSamples: [[[String: NSNumber]]] = plan.pointers.map { pointer in
|
|
551
|
+
pointer.samples.map { sample in
|
|
552
|
+
let point = CoordinateSpaceRotation.native(
|
|
553
|
+
point: CGPoint(x: sample.point.x, y: sample.point.y),
|
|
554
|
+
in: frame,
|
|
555
|
+
interfaceOrientation: orientation
|
|
556
|
+
)
|
|
557
|
+
return [
|
|
558
|
+
"x": NSNumber(value: Double(point.x)),
|
|
559
|
+
"y": NSNumber(value: Double(point.y)),
|
|
560
|
+
"offsetMs": NSNumber(value: sample.offsetMs),
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
let resolvedWindow = resolveRunnerWindow(app: app).window
|
|
565
|
+
if let message = RunnerSynthesizedGesture.synthesizeGesture(
|
|
566
|
+
withApplication: app,
|
|
567
|
+
resolvedWindow: resolvedWindow,
|
|
568
|
+
pointerSamples: pointerSamples
|
|
569
|
+
) {
|
|
570
|
+
return .unsupported(
|
|
571
|
+
message: message,
|
|
572
|
+
hint: "This gesture uses private XCTest event-synthesis APIs; rebuild the runner with a supported Xcode if this persists."
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
return .performed
|
|
576
|
+
#elseif os(tvOS)
|
|
577
|
+
return .unsupported(
|
|
578
|
+
message: "two-finger gestures are not supported on tvOS",
|
|
579
|
+
hint: "tvOS has no touch input; use remote-driven navigation."
|
|
580
|
+
)
|
|
581
|
+
#elseif os(visionOS)
|
|
582
|
+
return .unsupported(
|
|
583
|
+
message: "two-finger touch gestures are not supported on visionOS",
|
|
584
|
+
hint: "The current XCTest synthesizer supports iOS and iPadOS touch simulators only."
|
|
585
|
+
)
|
|
586
|
+
#else
|
|
587
|
+
return .unsupported(
|
|
588
|
+
message: "two-finger gestures are not supported on macOS",
|
|
589
|
+
hint: "macOS automation has no multi-touch input; run on an iOS simulator."
|
|
590
|
+
)
|
|
591
|
+
#endif
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
}
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.h
CHANGED
|
@@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
10
10
|
//
|
|
11
11
|
// Each caller additionally resolves whichever XCSynthesizedEventRecord init
|
|
12
12
|
// overload and XCPointerEventPath factory/mutator selectors its own event kind
|
|
13
|
-
// needs: gesture uses
|
|
13
|
+
// needs: gesture uses `initWithName:displayID:interfaceOrientation:` plus
|
|
14
14
|
// touch-path selectors (`initForTouchAtPoint:offset:`, `moveToPoint:atOffset:`,
|
|
15
15
|
// `liftUpAtOffset:`); text entry uses the 1-arg `initWithName:` plus text-input
|
|
16
16
|
// selectors (`initForTextInput`, `typeText:atOffset:typingSpeed:shouldRedact:`,
|
|
@@ -30,6 +30,16 @@ typedef void (*RunnerMsgSendSetInteger)(id, SEL, NSInteger);
|
|
|
30
30
|
typedef void (*RunnerMsgSendAddPath)(id, SEL, id);
|
|
31
31
|
typedef BOOL (*RunnerMsgSendSynthesize)(id, SEL, NSError **);
|
|
32
32
|
|
|
33
|
+
// Why the display hosting the target app could not be named. Callers branch on this rather than
|
|
34
|
+
// on the message, so a refusal keeps its reason across the language boundary (#2728).
|
|
35
|
+
typedef NS_ENUM(NSUInteger, RunnerApplicationScreenFailure) {
|
|
36
|
+
RunnerApplicationScreenFailureNone,
|
|
37
|
+
// No window with a usable frame, so nothing resolves and its screen would only report main.
|
|
38
|
+
RunnerApplicationScreenFailureUnresolvedWindow,
|
|
39
|
+
// A resolved window whose screen or display identity the XCTest runtime would not answer.
|
|
40
|
+
RunnerApplicationScreenFailureUnresolvedScreen,
|
|
41
|
+
};
|
|
42
|
+
|
|
33
43
|
// Resolves the shared core described above against the live XCTest runtime.
|
|
34
44
|
// Returns nil and populates `bridge` on success. Returns a non-nil
|
|
35
45
|
// "private XCTest <surface> synthesis unavailable: ..." message and leaves
|
|
@@ -43,6 +53,30 @@ FOUNDATION_EXPORT NSString * _Nullable RunnerResolveXCTestEventBridge(
|
|
|
43
53
|
RunnerXCTestEventBridge *bridge
|
|
44
54
|
);
|
|
45
55
|
|
|
56
|
+
// Resolves the `XCUIScreen` hosting the application's window and that screen's `displayID`, which
|
|
57
|
+
// is the only display identity that follows a foldable app to whichever panel it currently lights.
|
|
58
|
+
// Reading the window frame must precede reading its screen: an unresolved element still reports the
|
|
59
|
+
// main screen. Never selects a screen by number or by position in `XCUIScreen.screens`.
|
|
60
|
+
// Returns YES and writes the screen to capture into `screen` and its identity into `displayID`.
|
|
61
|
+
// Otherwise returns NO, leaves both empty, and names the reason in `failure` so callers branch on
|
|
62
|
+
// the reason rather than on message text (#2728). Private KVC raises rather than returns nil when
|
|
63
|
+
// XCTest has no such value, so both reads are guarded here and surface as a typed failure instead
|
|
64
|
+
// of an exception reaching the caller.
|
|
65
|
+
FOUNDATION_EXPORT BOOL RunnerResolveApplicationScreen(
|
|
66
|
+
id application,
|
|
67
|
+
id _Nullable *screen,
|
|
68
|
+
NSUInteger *displayID,
|
|
69
|
+
RunnerApplicationScreenFailure *failure
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Reads the display ID off an already-resolved window. When a caller has resolved the app window
|
|
73
|
+
// for geometry, routing the synthesized gesture by this window keeps the record's display and the
|
|
74
|
+
// booked reference frame on the same window instead of re-walking `windows.firstMatch`.
|
|
75
|
+
FOUNDATION_EXPORT NSString * _Nullable RunnerResolveWindowDisplayID(
|
|
76
|
+
id window,
|
|
77
|
+
NSUInteger *displayID
|
|
78
|
+
);
|
|
79
|
+
|
|
46
80
|
FOUNDATION_EXPORT NSString * _Nullable RunnerRequireClass(
|
|
47
81
|
Class cls,
|
|
48
82
|
NSString *className,
|