agent-device 0.19.3 → 0.20.1
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/README.md +62 -70
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.1.apk +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.1.apk.sha256 +1 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.1.manifest.json +10 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.1.apk +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.1.apk.sha256 +1 -0
- package/{android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.manifest.json → android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.1.manifest.json} +6 -6
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +2 -0
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +5 -0
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +5 -10
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +74 -174
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +113 -19
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+BlockingSystemModalResolution.swift +82 -0
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +325 -123
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +2 -2
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +149 -105
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +56 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+LifecycleCacheTests.swift +73 -0
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +51 -26
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +2 -2
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +2 -107
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +130 -13
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +51 -8
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotTraversalIdentityTests.swift +24 -0
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +1 -1
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +43 -35
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +1 -1
- package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +16 -1
- package/dist/src/agent-browser-tool.js +1 -1
- package/dist/src/agent-device-client.js +3 -0
- package/dist/src/android-adb.d.ts +1 -1
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-input-method-overlays.js +1 -0
- package/dist/src/android-input-ownership.js +1 -6
- package/dist/src/android-system-chrome.js +1 -0
- package/dist/src/android.js +1 -1
- package/dist/src/app-lifecycle.js +5 -5
- package/dist/src/app-log-request-scope.js +1 -0
- package/dist/src/app-resolution.js +1 -0
- package/dist/src/apple-multitouch-support.js +1 -0
- package/dist/src/apps.js +5 -5
- package/dist/src/boot-diagnostics.js +2 -0
- package/dist/src/cli-config.js +1 -0
- package/dist/src/cli-help.js +131 -40
- package/dist/src/cli-runner.js +1 -1
- package/dist/src/cli.js +15 -11
- package/dist/src/command-metadata.js +1 -1
- package/dist/src/command-schema.js +3 -3
- package/dist/src/compatibility-policy.js +1 -0
- package/dist/src/context.js +1 -0
- package/dist/src/contracts.d.ts +2 -2
- package/dist/src/debug-symbols.js +1 -1
- package/dist/src/device-input-state.js +1 -1
- package/dist/src/devices2.js +1 -1
- package/dist/src/devices4.js +1 -0
- package/dist/src/find.js +1 -1
- package/dist/src/finders.js +1 -1
- package/dist/src/generic.js +1 -1
- package/dist/src/helper.js +1 -1
- package/dist/src/index.d.ts +533 -279
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +4 -3
- package/dist/src/install-artifact.js +1 -0
- package/dist/src/install-source.d.ts +5 -1
- package/dist/src/install-source.js +1 -1
- package/dist/src/install-source2.js +1 -1
- package/dist/src/interaction-snapshot.js +1 -1
- package/dist/src/interaction.d.ts +230 -0
- package/dist/src/interaction.js +1 -1
- package/dist/src/interactor.js +2 -2
- package/dist/src/interactor2.js +1 -0
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +42 -20
- package/dist/src/internal/png-worker.d.ts +22 -0
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/launch-console.js +1 -0
- package/dist/src/lease-context.js +1 -0
- package/dist/src/lease.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/logcat.js +5 -0
- package/dist/src/method-scope.js +1 -0
- package/dist/src/metro-types.d.ts +52 -0
- package/dist/src/metro.d.ts +3 -3
- package/dist/src/notifications.js +1 -1
- package/dist/src/png-worker-contract.js +1 -1
- package/dist/src/process-lock.js +1 -0
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-trace.js +1 -1
- package/dist/src/recording-export-quality.js +15 -0
- package/dist/src/register-builtins.js +1 -0
- package/dist/src/registry.js +24 -24
- package/dist/src/remote-config.d.ts +1 -1
- package/dist/src/reporting.js +8 -7
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-disposal.js +2 -0
- package/dist/src/runner-lease.js +1 -0
- package/dist/src/runner-provider.js +1 -1
- package/dist/src/runner-sequence.js +1 -1
- package/dist/src/runtime.js +3 -3
- package/dist/src/runtime2.js +1 -0
- package/dist/src/sdk-android-adb.d.ts +97 -33
- package/dist/src/sdk-android-adb.js +4 -4
- package/dist/src/sdk-batch-runner.d.ts +2 -1
- package/dist/src/sdk-batch-runner.js +3 -2
- package/dist/src/sdk-contracts.d.ts +70 -149
- package/dist/src/sdk-contracts.js +1 -1
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-finders.d.ts +5 -4
- package/dist/src/sdk-finders.js +1 -1
- package/dist/src/sdk-io.d.ts +1 -1
- package/dist/src/sdk-remote-config.d.ts +19 -96
- package/dist/src/sdk-remote-config.js +1 -1
- package/dist/src/sdk-selectors.js +1 -1
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selector-vocabulary.js +1 -0
- package/dist/src/selectors.d.ts +12 -3
- package/dist/src/selectors.js +1 -1
- package/dist/src/server.js +3 -3
- package/dist/src/session-target-evidence.js +1 -0
- package/dist/src/session.js +8 -10
- package/dist/src/simctl.js +1 -1
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot-diagnostics.js +5 -2
- package/dist/src/snapshot-quality.js +1 -1
- package/dist/src/snapshot.js +1 -1
- package/dist/src/snapshot2.js +1 -2
- package/dist/src/tool-provider3.js +1 -0
- package/dist/src/unsupported-interactor.js +1 -0
- package/dist/src/url.js +1 -1
- package/dist/src/web.js +1 -1
- package/package.json +34 -27
- package/server.json +2 -2
- package/skills/agent-device/SKILL.md +5 -3
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.apk +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.apk.sha256 +0 -1
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.3.manifest.json +0 -10
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.apk +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.3.apk.sha256 +0 -1
- package/dist/src/apple-runner-options.js +0 -1
- package/dist/src/cli-output.js +0 -1
- package/dist/src/client.js +0 -3
- package/dist/src/handler-utils.js +0 -1
- package/dist/src/output-path.js +0 -1
- package/dist/src/points.js +0 -4
- package/dist/src/provider.js +0 -1
- package/dist/src/providers.js +0 -1
- package/dist/src/record-trace-recording.js +0 -23
- package/dist/src/recording-provider.js +0 -1
- package/dist/src/sdk-batch.js +0 -2
- package/dist/src/selectors2.js +0 -1
- package/dist/src/ttl-memo.js +0 -1
- package/dist/src/tv-remote.js +0 -14
- package/dist/src/xml.js +0 -2
- /package/{macos-helper → apple/macos-helper}/Package.swift +0 -0
- /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/AudioProbe.swift +0 -0
- /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +0 -0
- /package/{macos-helper → apple/macos-helper}/Sources/AgentDeviceMacOSHelper/main.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
- /package/dist/{apple-runner → apple/runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
- /package/{src/platforms/linux → linux}/atspi-dump.py +0 -0
|
@@ -146,8 +146,8 @@ final class RunnerCommandJournal {
|
|
|
146
146
|
case .tap, .mouseClick, .longPress, .drag,
|
|
147
147
|
.remotePress, .type, .swipe, .scroll, .desktopScroll, .findText, .querySelector, .readText, .back,
|
|
148
148
|
.backInApp, .backSystem, .home, .rotate, .appSwitcher, .keyboardDismiss, .keyboardReturn,
|
|
149
|
-
.alert, .
|
|
150
|
-
.status, .uptime, .shutdown:
|
|
149
|
+
.alert, .sequence, .gesture, .gestureViewport, .recordStart, .recordStop,
|
|
150
|
+
.status, .uptime, .activate, .terminate, .targetReset, .shutdown:
|
|
151
151
|
return true
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -13,6 +13,16 @@ private enum RunnerInterfaceOrientation {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
extension RunnerTests {
|
|
16
|
+
enum PlannedGestureExecution: Equatable {
|
|
17
|
+
case fastSwipe
|
|
18
|
+
case sampled
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
enum SynthesizedDragProfile {
|
|
22
|
+
case continuous
|
|
23
|
+
case fastSwipe
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
struct TouchVisualizationFrame {
|
|
17
27
|
let x: Double
|
|
18
28
|
let y: Double
|
|
@@ -108,9 +118,9 @@ extension RunnerTests {
|
|
|
108
118
|
#endif
|
|
109
119
|
}
|
|
110
120
|
|
|
111
|
-
func rotateDevice(to orientationName: String) ->
|
|
121
|
+
func rotateDevice(to orientationName: String) -> String? {
|
|
112
122
|
#if os(macOS) || os(tvOS) || os(visionOS)
|
|
113
|
-
return
|
|
123
|
+
return nil
|
|
114
124
|
#else
|
|
115
125
|
switch orientationName {
|
|
116
126
|
case "portrait":
|
|
@@ -122,10 +132,21 @@ extension RunnerTests {
|
|
|
122
132
|
case "landscape-right":
|
|
123
133
|
XCUIDevice.shared.orientation = .landscapeRight
|
|
124
134
|
default:
|
|
125
|
-
return
|
|
135
|
+
return nil
|
|
126
136
|
}
|
|
127
137
|
sleepFor(0.2)
|
|
128
|
-
|
|
138
|
+
switch XCUIDevice.shared.orientation {
|
|
139
|
+
case .portrait:
|
|
140
|
+
return "portrait"
|
|
141
|
+
case .portraitUpsideDown:
|
|
142
|
+
return "portrait-upside-down"
|
|
143
|
+
case .landscapeLeft:
|
|
144
|
+
return "landscape-left"
|
|
145
|
+
case .landscapeRight:
|
|
146
|
+
return "landscape-right"
|
|
147
|
+
default:
|
|
148
|
+
return nil
|
|
149
|
+
}
|
|
129
150
|
#endif
|
|
130
151
|
}
|
|
131
152
|
|
|
@@ -139,7 +160,8 @@ extension RunnerTests {
|
|
|
139
160
|
app: XCUIApplication,
|
|
140
161
|
selectorKey: String,
|
|
141
162
|
selectorValue: String,
|
|
142
|
-
allowNonHittableFallback: Bool = false
|
|
163
|
+
allowNonHittableFallback: Bool = false,
|
|
164
|
+
expectedPoint: CGPoint? = nil
|
|
143
165
|
) -> SelectorElementMatch {
|
|
144
166
|
let value = selectorValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
145
167
|
guard !value.isEmpty else {
|
|
@@ -163,6 +185,9 @@ extension RunnerTests {
|
|
|
163
185
|
var nonHittableElement: XCUIElement?
|
|
164
186
|
let matches = app.descendants(matching: .any).matching(predicate).allElementsBoundByIndex
|
|
165
187
|
for element in matches where element.exists {
|
|
188
|
+
if let expectedPoint, !element.frame.contains(expectedPoint) {
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
166
191
|
if !element.isHittable {
|
|
167
192
|
if allowNonHittableFallback && hasTappableFrame(app: app, element: element) {
|
|
168
193
|
guard nonHittableElement == nil else {
|
|
@@ -775,7 +800,7 @@ extension RunnerTests {
|
|
|
775
800
|
x2: Double,
|
|
776
801
|
y2: Double,
|
|
777
802
|
durationMs: Double,
|
|
778
|
-
|
|
803
|
+
profile: SynthesizedDragProfile = .continuous,
|
|
779
804
|
context: SynthesizedCoordinateContext? = nil
|
|
780
805
|
) -> RunnerInteractionOutcome {
|
|
781
806
|
#if os(iOS)
|
|
@@ -795,8 +820,9 @@ extension RunnerTests {
|
|
|
795
820
|
let frame = context.referenceFrame
|
|
796
821
|
let start = nativeSynthesizedPoint(orientedX: x, orientedY: y, in: frame, interfaceOrientation: orientation)
|
|
797
822
|
let end = nativeSynthesizedPoint(orientedX: x2, orientedY: y2, in: frame, interfaceOrientation: orientation)
|
|
798
|
-
let message =
|
|
799
|
-
|
|
823
|
+
let message = switch profile {
|
|
824
|
+
case .continuous:
|
|
825
|
+
RunnerSynthesizedGesture.synthesizeContinuousDrag(
|
|
800
826
|
withApplication: app,
|
|
801
827
|
x: Double(start.x),
|
|
802
828
|
y: Double(start.y),
|
|
@@ -804,8 +830,8 @@ extension RunnerTests {
|
|
|
804
830
|
y2: Double(end.y),
|
|
805
831
|
durationMs: durationMs
|
|
806
832
|
)
|
|
807
|
-
|
|
808
|
-
RunnerSynthesizedGesture.
|
|
833
|
+
case .fastSwipe:
|
|
834
|
+
RunnerSynthesizedGesture.synthesizeSwipe(
|
|
809
835
|
withApplication: app,
|
|
810
836
|
x: Double(start.x),
|
|
811
837
|
y: Double(start.y),
|
|
@@ -1166,125 +1192,143 @@ extension RunnerTests {
|
|
|
1166
1192
|
}
|
|
1167
1193
|
}
|
|
1168
1194
|
|
|
1169
|
-
func
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
durationMs
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1195
|
+
func plannedGestureValidationError(_ plan: RunnerGesturePlan) -> String? {
|
|
1196
|
+
guard plan.topology == "single" || plan.topology == "two" else {
|
|
1197
|
+
return "planned gesture topology must be single or two"
|
|
1198
|
+
}
|
|
1199
|
+
let supportedIntent = plan.topology == "single"
|
|
1200
|
+
? plan.intent == "fling" || plan.intent == "pan"
|
|
1201
|
+
: plan.intent == "pan" || plan.intent == "pinch" || plan.intent == "rotate"
|
|
1202
|
+
|| plan.intent == "transform"
|
|
1203
|
+
guard supportedIntent else { return "planned gesture has unsupported intent for its topology" }
|
|
1204
|
+
if plan.topology == "single" {
|
|
1205
|
+
guard plan.executionProfile == "endpoint-hold" || plan.executionProfile == "timed-pan" else {
|
|
1206
|
+
return "single-pointer gesture requires a supported execution profile"
|
|
1207
|
+
}
|
|
1208
|
+
} else if plan.executionProfile != nil {
|
|
1209
|
+
return "multi-touch gesture cannot define a single-pointer execution profile"
|
|
1210
|
+
}
|
|
1211
|
+
guard plan.durationMs.isFinite, plan.durationMs >= 16, plan.durationMs <= 10_000 else {
|
|
1212
|
+
return "planned gesture durationMs must be between 16 and 10000"
|
|
1213
|
+
}
|
|
1214
|
+
let viewport = plan.viewport
|
|
1215
|
+
guard viewport.x.isFinite, viewport.y.isFinite, viewport.width.isFinite,
|
|
1216
|
+
viewport.height.isFinite, viewport.width > 0, viewport.height > 0
|
|
1217
|
+
else {
|
|
1218
|
+
return "planned gesture viewport must be finite and positive"
|
|
1219
|
+
}
|
|
1220
|
+
let expectedPointerCount = plan.topology == "single" ? 1 : 2
|
|
1221
|
+
guard plan.pointers.count == expectedPointerCount else {
|
|
1222
|
+
return "planned gesture pointer count does not match topology"
|
|
1223
|
+
}
|
|
1224
|
+
for (index, pointer) in plan.pointers.enumerated() where pointer.pointerId != index {
|
|
1225
|
+
return "planned gesture requires ordered pointer ids"
|
|
1226
|
+
}
|
|
1227
|
+
let firstSamples = plan.pointers[0].samples
|
|
1228
|
+
guard firstSamples.count >= 2 else { return "planned pointer paths require at least two samples" }
|
|
1229
|
+
for pointer in plan.pointers {
|
|
1230
|
+
guard pointer.samples.count == firstSamples.count else {
|
|
1231
|
+
return "planned pointer paths require matching samples"
|
|
1232
|
+
}
|
|
1233
|
+
var previousOffset = -1.0
|
|
1234
|
+
for (index, sample) in pointer.samples.enumerated() {
|
|
1235
|
+
guard sample.offsetMs.isFinite,
|
|
1236
|
+
sample.offsetMs == firstSamples[index].offsetMs,
|
|
1237
|
+
sample.offsetMs > previousOffset
|
|
1238
|
+
else {
|
|
1239
|
+
return "planned pointer sample offsets must match and strictly increase"
|
|
1240
|
+
}
|
|
1241
|
+
let point = sample.point
|
|
1242
|
+
guard point.x.isFinite, point.y.isFinite,
|
|
1243
|
+
point.x >= viewport.x,
|
|
1244
|
+
point.x <= viewport.x + viewport.width,
|
|
1245
|
+
point.y >= viewport.y,
|
|
1246
|
+
point.y <= viewport.y + viewport.height
|
|
1247
|
+
else {
|
|
1248
|
+
return "planned pointer sample lies outside the viewport"
|
|
1249
|
+
}
|
|
1250
|
+
previousOffset = sample.offsetMs
|
|
1251
|
+
}
|
|
1252
|
+
guard pointer.samples.first?.offsetMs == 0,
|
|
1253
|
+
pointer.samples.last?.offsetMs == plan.durationMs
|
|
1254
|
+
else {
|
|
1255
|
+
return "planned pointer paths must start at 0 and end at durationMs"
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if plan.topology == "two" {
|
|
1259
|
+
guard let firstStart = firstSamples.first?.point,
|
|
1260
|
+
let secondStart = plan.pointers[1].samples.first?.point,
|
|
1261
|
+
hypot(firstStart.x - secondStart.x, firstStart.y - secondStart.y) > 0
|
|
1262
|
+
else {
|
|
1263
|
+
return "planned pointer paths require a positive initial span"
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
return nil
|
|
1199
1267
|
}
|
|
1200
1268
|
|
|
1201
|
-
func
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
// XCUIElement.rotate(withVelocity:) injects a single synthetic rotation that RN's gesture
|
|
1206
|
-
// handler does not read reliably — the same class of problem #629/#634 fixed for pinch.
|
|
1207
|
-
// velocity is unused on iOS (synthesis speed is governed by durationMs); the wire contract
|
|
1208
|
-
// keeps it for compatibility and direction is carried entirely by the sign of `degrees`.
|
|
1209
|
-
let frame = interactionRoot(app: app).frame
|
|
1210
|
-
let centerX = x ?? Double(frame.midX)
|
|
1211
|
-
let centerY = y ?? Double(frame.midY)
|
|
1212
|
-
return transformGesture(
|
|
1213
|
-
app: app,
|
|
1214
|
-
x: centerX,
|
|
1215
|
-
y: centerY,
|
|
1216
|
-
dx: 0,
|
|
1217
|
-
dy: 0,
|
|
1218
|
-
scale: 1,
|
|
1219
|
-
degrees: degrees,
|
|
1220
|
-
durationMs: 300
|
|
1221
|
-
)
|
|
1222
|
-
#elseif os(tvOS)
|
|
1223
|
-
return .unsupported(
|
|
1224
|
-
message: "rotate-gesture is not supported on tvOS",
|
|
1225
|
-
hint: "tvOS has no touch input; rotation gestures require a touchscreen (run on iOS)."
|
|
1226
|
-
)
|
|
1227
|
-
#else
|
|
1228
|
-
return .unsupported(
|
|
1229
|
-
message: "rotate-gesture is not supported on macOS",
|
|
1230
|
-
hint: "macOS automation has no multi-touch input; rotation gestures require a touchscreen (run on iOS)."
|
|
1231
|
-
)
|
|
1232
|
-
#endif
|
|
1269
|
+
func plannedGestureExecution(for plan: RunnerGesturePlan) -> PlannedGestureExecution {
|
|
1270
|
+
plan.topology == "single" && plan.executionProfile == "endpoint-hold"
|
|
1271
|
+
? .fastSwipe
|
|
1272
|
+
: .sampled
|
|
1233
1273
|
}
|
|
1234
1274
|
|
|
1235
|
-
func
|
|
1275
|
+
func sampledPlannedGesture(
|
|
1236
1276
|
app: XCUIApplication,
|
|
1237
|
-
|
|
1238
|
-
y: Double,
|
|
1239
|
-
dx: Double,
|
|
1240
|
-
dy: Double,
|
|
1241
|
-
scale: Double,
|
|
1242
|
-
degrees: Double,
|
|
1243
|
-
durationMs: Double
|
|
1277
|
+
plan: RunnerGesturePlan
|
|
1244
1278
|
) -> RunnerInteractionOutcome {
|
|
1245
1279
|
#if os(iOS)
|
|
1246
|
-
let target = interactionRoot(app: app)
|
|
1247
1280
|
let orientation = Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1281
|
+
// The portable planner and validation use this exact viewport. Using app.frame here can
|
|
1282
|
+
// diverge when XCTest unions transformed/off-screen descendants into the application frame.
|
|
1283
|
+
let frame = CGRect(
|
|
1284
|
+
x: plan.viewport.x,
|
|
1285
|
+
y: plan.viewport.y,
|
|
1286
|
+
width: plan.viewport.width,
|
|
1287
|
+
height: plan.viewport.height
|
|
1288
|
+
)
|
|
1289
|
+
let pointerSamples: [[[String: NSNumber]]] = plan.pointers.map { pointer in
|
|
1290
|
+
pointer.samples.map { sample in
|
|
1291
|
+
let point = nativeSynthesizedPoint(
|
|
1292
|
+
orientedX: sample.point.x,
|
|
1293
|
+
orientedY: sample.point.y,
|
|
1294
|
+
in: frame,
|
|
1295
|
+
interfaceOrientation: orientation
|
|
1296
|
+
)
|
|
1297
|
+
return [
|
|
1298
|
+
"x": NSNumber(value: Double(point.x)),
|
|
1299
|
+
"y": NSNumber(value: Double(point.y)),
|
|
1300
|
+
"offsetMs": NSNumber(value: sample.offsetMs),
|
|
1301
|
+
]
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if let message = RunnerSynthesizedGesture.synthesizeGesture(
|
|
1251
1305
|
withApplication: app,
|
|
1252
|
-
|
|
1253
|
-
y: Double(point.y),
|
|
1254
|
-
dx: Double(vector.dx),
|
|
1255
|
-
dy: Double(vector.dy),
|
|
1256
|
-
scale: scale,
|
|
1257
|
-
degrees: degrees,
|
|
1258
|
-
radius: transformGestureRadius(frame: target.frame, scale: scale),
|
|
1259
|
-
durationMs: durationMs
|
|
1306
|
+
pointerSamples: pointerSamples
|
|
1260
1307
|
) {
|
|
1261
1308
|
return .unsupported(
|
|
1262
1309
|
message: message,
|
|
1263
|
-
hint: "This gesture uses private XCTest event-synthesis APIs; rebuild the runner with a supported Xcode
|
|
1310
|
+
hint: "This gesture uses private XCTest event-synthesis APIs; rebuild the runner with a supported Xcode if this persists."
|
|
1264
1311
|
)
|
|
1265
1312
|
}
|
|
1266
1313
|
return .performed
|
|
1267
1314
|
#elseif os(tvOS)
|
|
1268
1315
|
return .unsupported(
|
|
1269
|
-
message: "
|
|
1270
|
-
hint: "tvOS has no touch input;
|
|
1316
|
+
message: "two-finger gestures are not supported on tvOS",
|
|
1317
|
+
hint: "tvOS has no touch input; use remote-driven navigation."
|
|
1318
|
+
)
|
|
1319
|
+
#elseif os(visionOS)
|
|
1320
|
+
return .unsupported(
|
|
1321
|
+
message: "two-finger touch gestures are not supported on visionOS",
|
|
1322
|
+
hint: "The current XCTest synthesizer supports iOS and iPadOS touch simulators only."
|
|
1271
1323
|
)
|
|
1272
1324
|
#else
|
|
1273
1325
|
return .unsupported(
|
|
1274
|
-
message: "
|
|
1275
|
-
hint: "macOS automation has no multi-touch input;
|
|
1326
|
+
message: "two-finger gestures are not supported on macOS",
|
|
1327
|
+
hint: "macOS automation has no multi-touch input; run on an iOS simulator."
|
|
1276
1328
|
)
|
|
1277
1329
|
#endif
|
|
1278
1330
|
}
|
|
1279
1331
|
|
|
1280
|
-
private func transformGestureRadius(frame: CGRect, scale: Double) -> Double {
|
|
1281
|
-
let shorterSide = Double(min(frame.width, frame.height))
|
|
1282
|
-
let frameRadius = shorterSide * 0.20
|
|
1283
|
-
let minimumEndRadius = shorterSide * 0.08
|
|
1284
|
-
let scaleAdjustedRadius = scale < 1.0 ? max(frameRadius, minimumEndRadius / scale) : frameRadius
|
|
1285
|
-
return min(max(scaleAdjustedRadius, 48.0), shorterSide * 0.35)
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
1332
|
private func interactionRoot(app: XCUIApplication) -> XCUIElement {
|
|
1289
1333
|
let windows = app.windows.allElementsBoundByIndex
|
|
1290
1334
|
if let window = windows.first(where: { $0.exists && !$0.frame.isEmpty }) {
|
|
@@ -85,6 +85,8 @@ extension RunnerTests {
|
|
|
85
85
|
}
|
|
86
86
|
currentApp = app
|
|
87
87
|
currentBundleId = nil
|
|
88
|
+
currentAppProcessIdentifier = nil
|
|
89
|
+
snapshotXCTestPenaltyWarmupExemptionPending = false
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
func invalidateCachedTarget(reason: String) {
|
|
@@ -93,6 +95,48 @@ extension RunnerTests {
|
|
|
93
95
|
}
|
|
94
96
|
currentApp = nil
|
|
95
97
|
currentBundleId = nil
|
|
98
|
+
currentAppProcessIdentifier = nil
|
|
99
|
+
snapshotXCTestPenaltyWarmupExemptionPending = false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func resetTargetAfterExternalRelaunch() -> Response {
|
|
103
|
+
invalidateCachedTarget(reason: "external_app_relaunch")
|
|
104
|
+
needsFirstInteractionDelay = true
|
|
105
|
+
return Response(ok: true, data: DataPayload(message: "target reset"))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func refreshCachedTargetIfProcessChanged(bundleId: String) {
|
|
109
|
+
guard currentBundleId == bundleId, currentApp != nil else { return }
|
|
110
|
+
let candidate = XCUIApplication(bundleIdentifier: bundleId)
|
|
111
|
+
let observedProcessIdentifier = Self.processIdentifier(of: candidate)
|
|
112
|
+
guard Self.shouldRefreshCachedTarget(
|
|
113
|
+
cachedProcessIdentifier: currentAppProcessIdentifier,
|
|
114
|
+
observedProcessIdentifier: observedProcessIdentifier
|
|
115
|
+
) else { return }
|
|
116
|
+
NSLog(
|
|
117
|
+
"AGENT_DEVICE_RUNNER_TARGET_CACHE_REFRESH bundle=%@ previousPid=%d currentPid=%d",
|
|
118
|
+
bundleId,
|
|
119
|
+
currentAppProcessIdentifier ?? 0,
|
|
120
|
+
observedProcessIdentifier ?? 0
|
|
121
|
+
)
|
|
122
|
+
currentApp = candidate
|
|
123
|
+
currentAppProcessIdentifier = observedProcessIdentifier
|
|
124
|
+
clearSnapshotXCTestChannelPenalty(reason: "target_process_changed")
|
|
125
|
+
snapshotXCTestPenaltyWarmupExemptionPending = true
|
|
126
|
+
needsFirstInteractionDelay = true
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static func processIdentifier(of target: XCUIApplication) -> Int? {
|
|
130
|
+
let value = RunnerAXSnapshotBridge.processIdentifier(for: target)
|
|
131
|
+
return value > 0 ? value : nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
static func shouldRefreshCachedTarget(
|
|
135
|
+
cachedProcessIdentifier: Int?,
|
|
136
|
+
observedProcessIdentifier: Int?
|
|
137
|
+
) -> Bool {
|
|
138
|
+
guard let cachedProcessIdentifier, let observedProcessIdentifier else { return false }
|
|
139
|
+
return cachedProcessIdentifier != observedProcessIdentifier
|
|
96
140
|
}
|
|
97
141
|
|
|
98
142
|
func targetNeedsActivation(_ target: XCUIApplication) -> Bool {
|
|
@@ -131,16 +175,26 @@ extension RunnerTests {
|
|
|
131
175
|
|
|
132
176
|
func activateTarget(bundleId: String, reason: String) -> XCUIApplication {
|
|
133
177
|
let target = XCUIApplication(bundleIdentifier: bundleId)
|
|
178
|
+
let initialState = target.state
|
|
134
179
|
NSLog(
|
|
135
180
|
"AGENT_DEVICE_RUNNER_ACTIVATE bundle=%@ state=%d reason=%@",
|
|
136
181
|
bundleId,
|
|
137
|
-
|
|
182
|
+
initialState.rawValue,
|
|
138
183
|
reason
|
|
139
184
|
)
|
|
140
185
|
// activate avoids terminating and relaunching the target app
|
|
141
|
-
|
|
186
|
+
if initialState == .runningForeground {
|
|
187
|
+
NSLog(
|
|
188
|
+
"AGENT_DEVICE_RUNNER_ACTIVATE_SKIPPED bundle=%@ reason=already_foreground",
|
|
189
|
+
bundleId
|
|
190
|
+
)
|
|
191
|
+
} else {
|
|
192
|
+
target.activate()
|
|
193
|
+
}
|
|
142
194
|
currentApp = target
|
|
143
195
|
currentBundleId = bundleId
|
|
196
|
+
currentAppProcessIdentifier = Self.processIdentifier(of: target)
|
|
197
|
+
snapshotXCTestPenaltyWarmupExemptionPending = false
|
|
144
198
|
needsFirstInteractionDelay = true
|
|
145
199
|
return target
|
|
146
200
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
extension RunnerTests {
|
|
5
|
+
|
|
6
|
+
func testCachedTargetRefreshRequiresChangedPositiveProcessIdentity() {
|
|
7
|
+
XCTAssertFalse(
|
|
8
|
+
Self.shouldRefreshCachedTarget(
|
|
9
|
+
cachedProcessIdentifier: nil,
|
|
10
|
+
observedProcessIdentifier: 42
|
|
11
|
+
)
|
|
12
|
+
)
|
|
13
|
+
XCTAssertFalse(
|
|
14
|
+
Self.shouldRefreshCachedTarget(
|
|
15
|
+
cachedProcessIdentifier: 42,
|
|
16
|
+
observedProcessIdentifier: 42
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
XCTAssertTrue(
|
|
20
|
+
Self.shouldRefreshCachedTarget(
|
|
21
|
+
cachedProcessIdentifier: 41,
|
|
22
|
+
observedProcessIdentifier: 42
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func testSnapshotPenaltyWarmupExemptionIsConsumedOnce() {
|
|
28
|
+
snapshotXCTestPenaltyWarmupExemptionPending = true
|
|
29
|
+
|
|
30
|
+
XCTAssertTrue(consumeSnapshotXCTestPenaltyWarmupExemption())
|
|
31
|
+
XCTAssertFalse(consumeSnapshotXCTestPenaltyWarmupExemption())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
func testSnapshotPenaltyCanBeClearedAcrossTargetProcessReplacement() {
|
|
35
|
+
penalizeSnapshotXCTestChannel(bundleId: "com.example.app", reason: "test")
|
|
36
|
+
XCTAssertTrue(isSnapshotXCTestChannelPenalized(bundleId: "com.example.app"))
|
|
37
|
+
|
|
38
|
+
clearSnapshotXCTestChannelPenalty(reason: "target_process_changed")
|
|
39
|
+
|
|
40
|
+
XCTAssertFalse(isSnapshotXCTestChannelPenalized(bundleId: "com.example.app"))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func testCachedTargetInvalidationClearsProcessBoundState() {
|
|
44
|
+
currentApp = app
|
|
45
|
+
currentBundleId = "com.example.app"
|
|
46
|
+
currentAppProcessIdentifier = 42
|
|
47
|
+
snapshotXCTestPenaltyWarmupExemptionPending = true
|
|
48
|
+
|
|
49
|
+
invalidateCachedTarget(reason: "unit_test")
|
|
50
|
+
|
|
51
|
+
XCTAssertNil(currentApp)
|
|
52
|
+
XCTAssertNil(currentBundleId)
|
|
53
|
+
XCTAssertNil(currentAppProcessIdentifier)
|
|
54
|
+
XCTAssertFalse(snapshotXCTestPenaltyWarmupExemptionPending)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func testTargetResetInvalidatesProcessBoundStateWithoutRestartingRunner() {
|
|
58
|
+
currentApp = app
|
|
59
|
+
currentBundleId = "com.example.app"
|
|
60
|
+
currentAppProcessIdentifier = 42
|
|
61
|
+
snapshotXCTestPenaltyWarmupExemptionPending = true
|
|
62
|
+
needsFirstInteractionDelay = false
|
|
63
|
+
|
|
64
|
+
let response = resetTargetAfterExternalRelaunch()
|
|
65
|
+
|
|
66
|
+
XCTAssertTrue(response.ok)
|
|
67
|
+
XCTAssertNil(currentApp)
|
|
68
|
+
XCTAssertNil(currentBundleId)
|
|
69
|
+
XCTAssertNil(currentAppProcessIdentifier)
|
|
70
|
+
XCTAssertFalse(snapshotXCTestPenaltyWarmupExemptionPending)
|
|
71
|
+
XCTAssertTrue(needsFirstInteractionDelay)
|
|
72
|
+
}
|
|
73
|
+
}
|