agent-device 0.18.3 → 0.19.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 +5 -1
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.18.3.apk → agent-device-android-multitouch-helper-0.19.1.apk} +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.1.apk.sha256 +1 -0
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.18.3.manifest.json → agent-device-android-multitouch-helper-0.19.1.manifest.json} +4 -4
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.18.3.apk → agent-device-android-snapshot-helper-0.19.1.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.1.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.18.3.manifest.json → agent-device-android-snapshot-helper-0.19.1.manifest.json} +6 -6
- package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +28 -0
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +33 -127
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +630 -177
- package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +155 -0
- package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +67 -0
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +173 -122
- package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +172 -0
- package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +64 -0
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +76 -182
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +92 -51
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +213 -137
- package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +142 -0
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +208 -46
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +82 -11
- package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +74 -0
- package/dist/src/agent-browser-tool.js +1 -0
- package/dist/src/android-adb.d.ts +63 -234
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-input-ownership.js +6 -0
- package/dist/src/android.js +1 -5
- package/dist/src/app-lifecycle.js +7 -0
- package/dist/src/apple-runner-options.js +1 -0
- package/dist/src/apps.js +5 -11
- package/dist/src/artifacts.d.ts +4 -3
- package/dist/src/artifacts.js +1 -1
- package/dist/src/audio-probe.js +1 -1
- package/dist/src/batch.d.ts +2 -177
- package/dist/src/batch.js +1 -1
- package/dist/src/cli-help.js +178 -60
- package/dist/src/cli-output.js +1 -1
- package/dist/src/cli-runner.js +2 -0
- package/dist/src/cli.js +12 -41
- package/dist/src/client-metro-companion.js +1 -0
- package/dist/src/client.js +3 -0
- package/dist/src/command-metadata.js +1 -0
- package/dist/src/command-schema.js +22 -0
- package/dist/src/command-surface.js +1 -1
- package/dist/src/contracts.d.ts +2 -213
- package/dist/src/contracts.js +1 -1
- package/dist/src/debug-symbols.js +5 -3
- package/dist/src/device-input-state.js +2 -0
- package/dist/src/devices.js +1 -1
- package/dist/src/devices2.js +1 -0
- package/dist/src/devices3.js +1 -0
- package/dist/src/find.js +1 -1
- package/dist/src/finders.d.ts +2 -81
- package/dist/src/finders.js +1 -1
- package/dist/src/generic.js +1 -1
- package/dist/src/handler-utils.js +1 -1
- package/dist/src/helper.js +2 -0
- package/dist/src/host-process.js +2 -0
- package/dist/src/index.d.ts +1444 -1786
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +3 -1
- package/dist/src/input-actions2.js +1 -0
- package/dist/src/install-source.d.ts +13 -14
- package/dist/src/install-source.js +1 -1
- package/dist/src/install-source2.js +1 -0
- package/dist/src/interaction-snapshot.js +1 -0
- package/dist/src/interaction.js +1 -1
- package/dist/src/interactor.js +2 -1
- package/dist/src/internal/bin.d.ts +1 -1
- package/dist/src/internal/bin.js +2 -5
- package/dist/src/internal/companion-tunnel.d.ts +1 -1
- package/dist/src/internal/companion-tunnel.js +1 -1
- package/dist/src/internal/daemon.d.ts +1 -1
- package/dist/src/internal/daemon.js +18 -1
- package/dist/src/internal/png-worker.d.ts +29 -26
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/internal/update-check-entry.d.ts +1 -1
- package/dist/src/internal/update-check-entry.js +1 -1
- package/dist/src/io.d.ts +2 -85
- package/dist/src/io.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/manifest.js +1 -0
- package/dist/src/metro.d.ts +69 -113
- package/dist/src/metro.js +1 -1
- package/dist/src/metro2.js +1 -0
- package/dist/src/notifications.js +1 -1
- package/dist/src/output-path.js +1 -0
- package/dist/src/path-resolution.js +1 -0
- package/dist/src/png-worker-contract.js +1 -0
- package/dist/src/points.js +4 -0
- package/dist/src/project-runtime.js +1 -0
- package/dist/src/provider.js +1 -1
- package/dist/src/providers.js +1 -0
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-trace-recording.js +2 -5
- package/dist/src/record-trace.js +1 -1
- package/dist/src/recording-provider.js +1 -1
- package/dist/src/registry.js +38 -0
- package/dist/src/remote-config.d.ts +2 -83
- package/dist/src/reporting.js +9 -0
- package/dist/src/rolldown-runtime.js +1 -0
- package/dist/src/runner-client.js +1 -0
- package/dist/src/runner-provider.js +1 -0
- package/dist/src/runner-sequence.js +1 -1
- package/dist/src/runtime-transport.js +1 -0
- package/dist/src/runtime.js +3 -0
- package/dist/src/scoped-provider.js +1 -0
- package/dist/src/sdk-android-adb.d.ts +144 -0
- package/dist/src/sdk-android-adb.js +4 -0
- package/dist/src/sdk-app-inventory.d.ts +4 -0
- package/dist/src/sdk-app-inventory.js +1 -0
- package/dist/src/sdk-batch-runner.d.ts +39 -0
- package/dist/src/sdk-batch-runner.js +2 -0
- package/dist/src/sdk-batch.js +2 -0
- package/dist/src/sdk-contracts.d.ts +386 -0
- package/dist/src/sdk-contracts.js +2 -0
- package/dist/src/sdk-finders.d.ts +60 -0
- package/dist/src/sdk-finders.js +1 -0
- package/dist/src/sdk-io.d.ts +78 -0
- package/dist/src/sdk-io.js +1 -0
- package/dist/src/sdk-remote-config.d.ts +139 -0
- package/dist/src/sdk-remote-config.js +1 -0
- package/dist/src/sdk-selectors.js +1 -0
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selectors.d.ts +38 -137
- package/dist/src/selectors.js +1 -1
- package/dist/src/selectors2.js +1 -0
- package/dist/src/server.js +3 -2
- package/dist/src/session.js +10 -11
- package/dist/src/setting-state.js +1 -0
- package/dist/src/simctl.js +1 -0
- package/dist/src/simulator.js +1 -2
- package/dist/src/snapshot-diagnostics.js +3 -0
- package/dist/src/snapshot-helper.js +5 -0
- package/dist/src/snapshot-quality.js +1 -0
- package/dist/src/snapshot.js +1 -2
- package/dist/src/snapshot2.js +2 -0
- package/dist/src/{5560.js → timeouts.js} +1 -1
- package/dist/src/tool-provider.js +3 -0
- package/dist/src/tool-provider2.js +1 -0
- package/dist/src/ttl-memo.js +1 -0
- package/dist/src/tv-remote.js +14 -0
- package/dist/src/update-check.js +1 -0
- package/dist/src/url.js +1 -0
- package/dist/src/version.js +1 -0
- package/dist/src/web.js +1 -1
- package/dist/src/xml.js +2 -0
- package/package.json +13 -19
- package/server.json +2 -2
- package/skills/agent-device/SKILL.md +8 -7
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.18.3.apk.sha256 +0 -1
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.18.3.apk.sha256 +0 -1
- package/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +0 -443
- package/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -169
- package/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +0 -220
- package/dist/src/1404.js +0 -1
- package/dist/src/1986.js +0 -1
- package/dist/src/2748.js +0 -1
- package/dist/src/2948.js +0 -30
- package/dist/src/3006.js +0 -1
- package/dist/src/314.js +0 -1
- package/dist/src/3340.js +0 -8
- package/dist/src/4418.js +0 -1
- package/dist/src/4428.js +0 -3
- package/dist/src/485.js +0 -1
- package/dist/src/4917.js +0 -1
- package/dist/src/495.js +0 -5
- package/dist/src/5448.js +0 -3
- package/dist/src/5484.js +0 -1
- package/dist/src/5643.js +0 -1
- package/dist/src/587.js +0 -1
- package/dist/src/5913.js +0 -4
- package/dist/src/6849.js +0 -1
- package/dist/src/6918.js +0 -1
- package/dist/src/6967.js +0 -1
- package/dist/src/760.js +0 -1
- package/dist/src/7871.js +0 -1
- package/dist/src/8184.js +0 -1
- package/dist/src/8225.js +0 -1
- package/dist/src/8391.js +0 -1
- package/dist/src/8404.js +0 -2
- package/dist/src/8414.js +0 -2
- package/dist/src/8875.js +0 -4
- package/dist/src/893.js +0 -1
- package/dist/src/8936.js +0 -1
- package/dist/src/9414.js +0 -1
- package/dist/src/9581.js +0 -22
- package/dist/src/9612.js +0 -1
- package/dist/src/9624.js +0 -1
- package/dist/src/9722.js +0 -39
- package/dist/src/devices~1.js +0 -1
- package/dist/src/devices~2.js +0 -1
- package/dist/src/input-actions~1.js +0 -1
- package/dist/src/rslib-runtime.js +0 -1
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
- /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
extension RunnerTests {
|
|
4
|
+
static let navigationBackKeywords = ["back", "close", "cancel"]
|
|
5
|
+
static let navigationFallbackVerificationDelay: TimeInterval = 0.25
|
|
6
|
+
|
|
7
|
+
func tapInAppBackControl(app: XCUIApplication) -> Bool {
|
|
8
|
+
#if os(macOS)
|
|
9
|
+
if let back = macOSNavigationBackElement(app: app) {
|
|
10
|
+
tapElementCenter(app: app, element: back)
|
|
11
|
+
return true
|
|
12
|
+
}
|
|
13
|
+
return false
|
|
14
|
+
#elseif os(tvOS)
|
|
15
|
+
_ = pressTvRemote(.menu)
|
|
16
|
+
return true
|
|
17
|
+
#else
|
|
18
|
+
let buttons = app.navigationBars.buttons.allElementsBoundByIndex
|
|
19
|
+
if let back = buttons.first(where: { $0.isHittable }) {
|
|
20
|
+
back.tap()
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
if isSnapshotXCTestChannelPenalized(bundleId: currentBundleId) {
|
|
24
|
+
NSLog("AGENT_DEVICE_RUNNER_IN_APP_BACK_SKIPPED_XCTEST_ENUMERATION bundle=%@", currentBundleId ?? "")
|
|
25
|
+
} else if let back = topNavigationBackElement(app: app) {
|
|
26
|
+
tapElementCenter(app: app, element: back)
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
return tapTopLeadingNavigationFallback(app: app)
|
|
30
|
+
#endif
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func tapElementCenter(app: XCUIApplication, element: XCUIElement) {
|
|
34
|
+
let frame = element.frame
|
|
35
|
+
if !frame.isEmpty {
|
|
36
|
+
_ = tapAt(app: app, x: frame.midX, y: frame.midY)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
#if !os(tvOS)
|
|
40
|
+
element.tap()
|
|
41
|
+
#endif
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private func topNavigationBackElement(app: XCUIApplication) -> XCUIElement? {
|
|
45
|
+
#if os(iOS)
|
|
46
|
+
let frame = onScreenWindowFrame(app: app)
|
|
47
|
+
let candidates = app.buttons.matching(Self.navigationBackPredicate()).allElementsBoundByIndex.compactMap {
|
|
48
|
+
element -> (XCUIElement, Int)? in
|
|
49
|
+
guard element.exists, element.isHittable else { return nil }
|
|
50
|
+
guard Self.isTopNavigationControlFrame(element.frame, in: frame) else { return nil }
|
|
51
|
+
guard let rank = Self.navigationBackControlRank(
|
|
52
|
+
label: element.label,
|
|
53
|
+
identifier: element.identifier
|
|
54
|
+
) else {
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
return (element, rank)
|
|
58
|
+
}
|
|
59
|
+
return candidates.sorted { lhs, rhs in
|
|
60
|
+
if lhs.1 != rhs.1 { return lhs.1 < rhs.1 }
|
|
61
|
+
let leftFrame = lhs.0.frame
|
|
62
|
+
let rightFrame = rhs.0.frame
|
|
63
|
+
if leftFrame.minY != rightFrame.minY { return leftFrame.minY < rightFrame.minY }
|
|
64
|
+
return leftFrame.minX < rightFrame.minX
|
|
65
|
+
}.first?.0
|
|
66
|
+
#else
|
|
67
|
+
return nil
|
|
68
|
+
#endif
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static func navigationBackPredicate() -> NSPredicate {
|
|
72
|
+
let clauses = navigationBackKeywords.flatMap { _ in
|
|
73
|
+
["label CONTAINS[c] %@", "identifier CONTAINS[c] %@"]
|
|
74
|
+
}.joined(separator: " OR ")
|
|
75
|
+
let arguments = navigationBackKeywords.flatMap { [$0, $0] }
|
|
76
|
+
return NSPredicate(format: clauses, argumentArray: arguments)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static func navigationBackControlRank(label: String, identifier: String) -> Int? {
|
|
80
|
+
let text = "\(label) \(identifier)".lowercased()
|
|
81
|
+
return navigationBackKeywords.firstIndex { text.contains($0) }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static func isTopNavigationControlFrame(_ candidate: CGRect, in window: CGRect) -> Bool {
|
|
85
|
+
guard
|
|
86
|
+
candidate.width.isFinite,
|
|
87
|
+
candidate.height.isFinite,
|
|
88
|
+
window.width.isFinite,
|
|
89
|
+
window.height.isFinite,
|
|
90
|
+
candidate.width > 0,
|
|
91
|
+
candidate.height > 0,
|
|
92
|
+
window.width > 0,
|
|
93
|
+
window.height > 0
|
|
94
|
+
else {
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
// Accept the compact navigation/search header band without matching deep content controls.
|
|
98
|
+
let maxY = window.minY + min(max(window.height * 0.22, 96), 180)
|
|
99
|
+
return candidate.midY >= window.minY && candidate.midY <= maxY
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
static func topLeadingNavigationFallbackPoint(in frame: CGRect) -> CGPoint? {
|
|
103
|
+
guard frame.width.isFinite, frame.height.isFinite, frame.width > 0, frame.height > 0 else {
|
|
104
|
+
return nil
|
|
105
|
+
}
|
|
106
|
+
// Aim at the standard leading navigation slot, bounded for compact and tablet widths.
|
|
107
|
+
let xOffset = min(max(frame.width * 0.08, 28), 44)
|
|
108
|
+
// Sit below the status/dynamic-island region and inside common custom RN search headers.
|
|
109
|
+
let yOffset = min(max(frame.height * 0.155, 56), 132)
|
|
110
|
+
return CGPoint(x: frame.minX + xOffset, y: frame.minY + yOffset)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private func tapTopLeadingNavigationFallback(app: XCUIApplication) -> Bool {
|
|
114
|
+
#if os(iOS)
|
|
115
|
+
let frame = onScreenWindowFrame(app: app)
|
|
116
|
+
guard let point = Self.topLeadingNavigationFallbackPoint(in: frame) else {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
let before = captureNavigationFallbackVisualState()
|
|
120
|
+
let context = synthesizedCoordinateContext(
|
|
121
|
+
policy: synthesizedGesturePolicy(.coordinateTap)
|
|
122
|
+
)?.withReferenceFrame(frame)
|
|
123
|
+
let synthesized = performGesture(app, idleTimeout: false) {
|
|
124
|
+
synthesizedTapAt(app: app, x: point.x, y: point.y, context: context)
|
|
125
|
+
}
|
|
126
|
+
if case .performed = synthesized.outcome {
|
|
127
|
+
return didNavigationFallbackChangeVisualState(before: before)
|
|
128
|
+
}
|
|
129
|
+
let fallback = performGesture(app) {
|
|
130
|
+
tapAt(app: app, x: point.x, y: point.y)
|
|
131
|
+
}
|
|
132
|
+
if case .performed = fallback.outcome {
|
|
133
|
+
return didNavigationFallbackChangeVisualState(before: before)
|
|
134
|
+
}
|
|
135
|
+
#endif
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private func captureNavigationFallbackVisualState() -> Data? {
|
|
140
|
+
#if os(iOS)
|
|
141
|
+
runnerPngData(for: XCUIScreen.main.screenshot().image)
|
|
142
|
+
#else
|
|
143
|
+
return nil
|
|
144
|
+
#endif
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private func didNavigationFallbackChangeVisualState(before: Data?) -> Bool {
|
|
148
|
+
sleepFor(Self.navigationFallbackVerificationDelay)
|
|
149
|
+
let after = captureNavigationFallbackVisualState()
|
|
150
|
+
let changed = Self.didNavigationFallbackChangeVisualState(before: before, after: after)
|
|
151
|
+
if !changed {
|
|
152
|
+
NSLog("AGENT_DEVICE_RUNNER_IN_APP_BACK_FALLBACK_NO_STATE_CHANGE")
|
|
153
|
+
}
|
|
154
|
+
return changed
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
static func didNavigationFallbackChangeVisualState(before: Data?, after: Data?) -> Bool {
|
|
158
|
+
guard let before, let after else { return false }
|
|
159
|
+
return before != after
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private func macOSNavigationBackElement(app: XCUIApplication) -> XCUIElement? {
|
|
163
|
+
let predicate = NSPredicate(
|
|
164
|
+
format: "identifier == %@ OR label == %@",
|
|
165
|
+
"go back",
|
|
166
|
+
"Back"
|
|
167
|
+
)
|
|
168
|
+
let element = app.descendants(matching: .any).matching(predicate).firstMatch
|
|
169
|
+
return element.exists ? element : nil
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
}
|
package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
// Swift port of buildScrollGesturePlan from src/core/scroll-gesture.ts.
|
|
4
|
+
//
|
|
5
|
+
// This is a deliberate two-place invariant: the daemon keeps the TS implementation (for Android,
|
|
6
|
+
// recording, and reported-pixels), and the runner places the gesture with this Swift copy. The
|
|
7
|
+
// parity test vectors at the bottom of this file mirror src/core/__tests__/scroll-gesture.test.ts —
|
|
8
|
+
// if you change the math in either language, update the other and both vector sets.
|
|
9
|
+
//
|
|
10
|
+
// All inputs here are positive (reference dims, travel, center), so Swift's `.rounded()`
|
|
11
|
+
// (half away from zero) matches JS `Math.round` (half up) on every value computed below.
|
|
12
|
+
|
|
13
|
+
struct RunnerScrollGesturePlan {
|
|
14
|
+
let x1: Double
|
|
15
|
+
let y1: Double
|
|
16
|
+
let x2: Double
|
|
17
|
+
let y2: Double
|
|
18
|
+
let travelPixels: Double
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private let runnerDefaultScrollAmount = 0.6
|
|
22
|
+
private let runnerDefaultEdgePaddingFraction = 0.05
|
|
23
|
+
|
|
24
|
+
func runnerScrollGesturePlan(
|
|
25
|
+
direction: String,
|
|
26
|
+
amount: Double?,
|
|
27
|
+
pixels: Double?,
|
|
28
|
+
referenceWidth: Double,
|
|
29
|
+
referenceHeight: Double
|
|
30
|
+
) -> RunnerScrollGesturePlan? {
|
|
31
|
+
// Mirror the TS INVALID_ARGS contract: non-positive or non-finite amount/pixels are rejected
|
|
32
|
+
// rather than clamped into a journaled 1px scroll. The daemon validates before sending, so
|
|
33
|
+
// this only triggers for non-daemon wire clients.
|
|
34
|
+
if let amount, !(amount.isFinite && amount > 0) { return nil }
|
|
35
|
+
if let pixels, !(pixels.isFinite && pixels > 0) { return nil }
|
|
36
|
+
let axisLength = (direction == "up" || direction == "down") ? referenceHeight : referenceWidth
|
|
37
|
+
let requestedAmount = amount ?? runnerDefaultScrollAmount
|
|
38
|
+
let requestedPixels: Double =
|
|
39
|
+
pixels.map { max(1, $0.rounded()) } ?? (axisLength * requestedAmount).rounded()
|
|
40
|
+
let edgePadding = max(1, (axisLength * runnerDefaultEdgePaddingFraction).rounded())
|
|
41
|
+
let maxTravelPixels = max(1, axisLength - edgePadding * 2)
|
|
42
|
+
let travelPixels = max(1, min(requestedPixels, maxTravelPixels))
|
|
43
|
+
let halfTravel = (travelPixels / 2).rounded()
|
|
44
|
+
let centerX = (referenceWidth / 2).rounded()
|
|
45
|
+
let centerY = (referenceHeight / 2).rounded()
|
|
46
|
+
|
|
47
|
+
func plan(_ x1: Double, _ y1: Double, _ x2: Double, _ y2: Double) -> RunnerScrollGesturePlan {
|
|
48
|
+
RunnerScrollGesturePlan(x1: x1, y1: y1, x2: x2, y2: y2, travelPixels: travelPixels)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
switch direction {
|
|
52
|
+
case "up":
|
|
53
|
+
return plan(centerX, centerY - halfTravel, centerX, centerY + halfTravel)
|
|
54
|
+
case "down":
|
|
55
|
+
return plan(centerX, centerY + halfTravel, centerX, centerY - halfTravel)
|
|
56
|
+
case "left":
|
|
57
|
+
return plan(centerX - halfTravel, centerY, centerX + halfTravel, centerY)
|
|
58
|
+
case "right":
|
|
59
|
+
return plan(centerX + halfTravel, centerY, centerX - halfTravel, centerY)
|
|
60
|
+
default:
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
@@ -40,8 +40,10 @@ extension RunnerTests {
|
|
|
40
40
|
? resolvedTouchVisualizationFrame(app: activeApp, x: firstStep.x!, y: firstStep.y!)
|
|
41
41
|
: nil
|
|
42
42
|
|
|
43
|
+
let synthesizedContext = synthesizedSequenceCoordinateContext(steps: steps)
|
|
44
|
+
|
|
43
45
|
let execution = assembleSequenceExecution(steps: steps) { _, step in
|
|
44
|
-
performSequenceStep(step, activeApp: activeApp)
|
|
46
|
+
performSequenceStep(step, activeApp: activeApp, synthesizedContext: synthesizedContext)
|
|
45
47
|
}
|
|
46
48
|
return sequenceResponse(execution: execution, touchFrame: firstFrame)
|
|
47
49
|
}
|
|
@@ -141,7 +143,8 @@ extension RunnerTests {
|
|
|
141
143
|
|
|
142
144
|
private func performSequenceStep(
|
|
143
145
|
_ step: SequenceStep,
|
|
144
|
-
activeApp: XCUIApplication
|
|
146
|
+
activeApp: XCUIApplication,
|
|
147
|
+
synthesizedContext: SynthesizedCoordinateContext? = nil
|
|
145
148
|
) -> SequenceStepOutcome {
|
|
146
149
|
let x = step.x ?? 0
|
|
147
150
|
let y = step.y ?? 0
|
|
@@ -149,10 +152,26 @@ extension RunnerTests {
|
|
|
149
152
|
// a tapAt fallback when synthesis is unsupported), so fusing a jittered tap series does not
|
|
150
153
|
// change the touch mechanism for these inputs.
|
|
151
154
|
if step.kind == "tap", step.synthesized == true {
|
|
155
|
+
let policyKind = SynthesizedGesturePolicyKind.synthesizedDrag
|
|
156
|
+
#if os(iOS)
|
|
157
|
+
guard let synthesizedContext else {
|
|
158
|
+
let nowMs = ProcessInfo.processInfo.systemUptime * 1000
|
|
159
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: nil, fallbackAttempted: false)
|
|
160
|
+
return SequenceStepOutcome(
|
|
161
|
+
outcome: .unsupported(
|
|
162
|
+
message: "synthesized coordinate tap could not resolve a finite coordinate frame",
|
|
163
|
+
hint: "Retry after the app is foregrounded, or use a plain screenshot to choose coordinates."
|
|
164
|
+
),
|
|
165
|
+
gestureStartUptimeMs: nowMs,
|
|
166
|
+
gestureEndUptimeMs: nowMs
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
#endif
|
|
152
170
|
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
153
|
-
synthesizedTapAt(app: activeApp, x: x, y: y)
|
|
171
|
+
synthesizedTapAt(app: activeApp, x: x, y: y, context: synthesizedContext)
|
|
154
172
|
}
|
|
155
173
|
if case .performed = outcome {
|
|
174
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: synthesizedContext, fallbackAttempted: false)
|
|
156
175
|
if let pauseMs = step.pauseMs, pauseMs > 0 {
|
|
157
176
|
sleepFor(min(max(pauseMs, 0), 10000) / 1000.0)
|
|
158
177
|
}
|
|
@@ -162,11 +181,50 @@ extension RunnerTests {
|
|
|
162
181
|
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
163
182
|
)
|
|
164
183
|
}
|
|
184
|
+
#if os(iOS)
|
|
185
|
+
guard synthesizedContext.allowsXCTestCoordinateFallback else {
|
|
186
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: synthesizedContext, fallbackAttempted: false)
|
|
187
|
+
return SequenceStepOutcome(
|
|
188
|
+
outcome: outcome,
|
|
189
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
190
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: synthesizedContext, fallbackAttempted: true)
|
|
194
|
+
#endif
|
|
165
195
|
// Synthesis unsupported (e.g. macOS) — fall through to the drag-based tapAt below.
|
|
166
196
|
}
|
|
167
197
|
if step.kind == "drag", step.synthesized == true {
|
|
168
|
-
let
|
|
198
|
+
let policyKind = SynthesizedGesturePolicyKind.synthesizedDrag
|
|
199
|
+
let dragPoints: DragPoints
|
|
200
|
+
let dragContext: SynthesizedCoordinateContext?
|
|
201
|
+
#if os(iOS)
|
|
202
|
+
guard let dragPlan = axFreeSynthesizedDragPlan(
|
|
203
|
+
app: activeApp,
|
|
204
|
+
x: x,
|
|
205
|
+
y: y,
|
|
206
|
+
x2: step.x2 ?? x,
|
|
207
|
+
y2: step.y2 ?? y,
|
|
208
|
+
context: synthesizedContext
|
|
209
|
+
) else {
|
|
210
|
+
let nowMs = ProcessInfo.processInfo.systemUptime * 1000
|
|
211
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: synthesizedContext, fallbackAttempted: false)
|
|
212
|
+
return SequenceStepOutcome(
|
|
213
|
+
outcome: .unsupported(
|
|
214
|
+
message: "synthesized coordinate drag could not resolve a finite coordinate frame",
|
|
215
|
+
hint: "Retry after the app is foregrounded, or use a plain screenshot to choose coordinates."
|
|
216
|
+
),
|
|
217
|
+
gestureStartUptimeMs: nowMs,
|
|
218
|
+
gestureEndUptimeMs: nowMs
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
dragPoints = dragPlan.points
|
|
222
|
+
dragContext = dragPlan.context
|
|
223
|
+
#else
|
|
224
|
+
dragPoints = keyboardAvoidingDragPoints(
|
|
169
225
|
app: activeApp, x: x, y: y, x2: step.x2 ?? x, y2: step.y2 ?? y)
|
|
226
|
+
dragContext = nil
|
|
227
|
+
#endif
|
|
170
228
|
let durationMs = min(max(step.durationMs ?? 250, 16), 10000)
|
|
171
229
|
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
172
230
|
synthesizedDragAt(
|
|
@@ -175,10 +233,12 @@ extension RunnerTests {
|
|
|
175
233
|
y: dragPoints.y,
|
|
176
234
|
x2: dragPoints.x2,
|
|
177
235
|
y2: dragPoints.y2,
|
|
178
|
-
durationMs: durationMs
|
|
236
|
+
durationMs: durationMs,
|
|
237
|
+
context: dragContext
|
|
179
238
|
)
|
|
180
239
|
}
|
|
181
240
|
if case .performed = outcome {
|
|
241
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: dragContext, fallbackAttempted: false)
|
|
182
242
|
if let pauseMs = step.pauseMs, pauseMs > 0 {
|
|
183
243
|
sleepFor(min(max(pauseMs, 0), 10000) / 1000.0)
|
|
184
244
|
}
|
|
@@ -188,6 +248,17 @@ extension RunnerTests {
|
|
|
188
248
|
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
189
249
|
)
|
|
190
250
|
}
|
|
251
|
+
#if os(iOS)
|
|
252
|
+
guard dragContext?.allowsXCTestCoordinateFallback == true else {
|
|
253
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: dragContext, fallbackAttempted: false)
|
|
254
|
+
return SequenceStepOutcome(
|
|
255
|
+
outcome: outcome,
|
|
256
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
257
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
#endif
|
|
261
|
+
logSynthesizedGesturePolicyDecision(kind: policyKind, context: dragContext, fallbackAttempted: true)
|
|
191
262
|
let fallbackHoldDuration = synthesizedSwipeFallbackHoldDuration(durationMs: step.durationMs ?? 250)
|
|
192
263
|
let (fallbackTiming, fallbackOutcome) = performGesture(activeApp) {
|
|
193
264
|
dragAt(
|
|
@@ -273,180 +344,3 @@ extension RunnerTests {
|
|
|
273
344
|
}
|
|
274
345
|
}
|
|
275
346
|
|
|
276
|
-
#if AGENT_DEVICE_RUNNER_UNIT_TESTS
|
|
277
|
-
// MARK: - In-bundle unit tests (device-free)
|
|
278
|
-
|
|
279
|
-
extension RunnerTests {
|
|
280
|
-
func testSequenceDecodesStepsFromWire() throws {
|
|
281
|
-
let json = """
|
|
282
|
-
{"command":"sequence","commandId":"seq-1","steps":[
|
|
283
|
-
{"kind":"tap","x":100,"y":200},
|
|
284
|
-
{"kind":"doubleTap","x":101,"y":200},
|
|
285
|
-
{"kind":"longPress","x":102,"y":200,"durationMs":300},
|
|
286
|
-
{"kind":"drag","x":10,"y":600,"x2":10,"y2":200,"durationMs":250,"pauseMs":50}
|
|
287
|
-
]}
|
|
288
|
-
"""
|
|
289
|
-
let command = try JSONDecoder().decode(Command.self, from: Data(json.utf8))
|
|
290
|
-
XCTAssertEqual(command.command, .sequence)
|
|
291
|
-
XCTAssertEqual(command.steps?.count, 4)
|
|
292
|
-
XCTAssertEqual(command.steps?[0].kind, "tap")
|
|
293
|
-
XCTAssertEqual(command.steps?[1].kind, "doubleTap")
|
|
294
|
-
XCTAssertEqual(command.steps?[3].x2, 10)
|
|
295
|
-
XCTAssertEqual(command.steps?[3].pauseMs, 50)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
func testSequenceAcceptsDoubleTapKind() {
|
|
299
|
-
// A doubleTap step missing coords must fail on the coords check, not the kind allowlist —
|
|
300
|
-
// proving "doubleTap" passes validateSequenceStep without needing a device to execute on.
|
|
301
|
-
let response = executeSequenceForTest(steps: [
|
|
302
|
-
sequenceStep(kind: "doubleTap", x: nil)
|
|
303
|
-
])
|
|
304
|
-
XCTAssertEqual(response.ok, false)
|
|
305
|
-
XCTAssertEqual(response.error?.code, "INVALID_ARGS")
|
|
306
|
-
XCTAssertTrue(response.error?.message.contains("requires finite x and y") ?? false)
|
|
307
|
-
XCTAssertFalse(response.error?.message.contains("unsupported kind") ?? true)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
func testSequenceRejectsUnknownKind() throws {
|
|
311
|
-
let response = executeSequenceForTest(steps: [
|
|
312
|
-
sequenceStep(kind: "tap", x: 1, y: 2),
|
|
313
|
-
sequenceStep(kind: "pinch", x: 3, y: 4),
|
|
314
|
-
])
|
|
315
|
-
XCTAssertEqual(response.ok, false)
|
|
316
|
-
XCTAssertEqual(response.error?.code, "INVALID_ARGS")
|
|
317
|
-
XCTAssertTrue(response.error?.message.contains("step 1") ?? false)
|
|
318
|
-
XCTAssertTrue(response.error?.message.contains("pinch") ?? false)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
func testSequenceRejectsEmpty() {
|
|
322
|
-
let response = executeSequenceForTest(steps: [])
|
|
323
|
-
XCTAssertEqual(response.ok, false)
|
|
324
|
-
XCTAssertEqual(response.error?.code, "INVALID_ARGS")
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
func testSequenceRejectsTooManySteps() {
|
|
328
|
-
let steps = (0..<21).map { _ in sequenceStep(kind: "tap", x: 1, y: 2) }
|
|
329
|
-
let response = executeSequenceForTest(steps: steps)
|
|
330
|
-
XCTAssertEqual(response.ok, false)
|
|
331
|
-
XCTAssertEqual(response.error?.code, "INVALID_ARGS")
|
|
332
|
-
XCTAssertTrue(response.error?.message.contains("at most 20") ?? false)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
func testSequenceRejectsDragMissingSecondPoint() {
|
|
336
|
-
let response = executeSequenceForTest(steps: [
|
|
337
|
-
sequenceStep(kind: "tap", x: 1, y: 2),
|
|
338
|
-
sequenceStep(kind: "drag", x: 3, y: 4),
|
|
339
|
-
])
|
|
340
|
-
XCTAssertEqual(response.ok, false)
|
|
341
|
-
XCTAssertEqual(response.error?.code, "INVALID_ARGS")
|
|
342
|
-
XCTAssertTrue(response.error?.message.contains("step 1") ?? false)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
func testAssembleSequencePreservesOrderOnSuccess() {
|
|
346
|
-
let steps = [
|
|
347
|
-
sequenceStep(kind: "tap", x: 1, y: 1),
|
|
348
|
-
sequenceStep(kind: "longPress", x: 2, y: 2),
|
|
349
|
-
sequenceStep(kind: "tap", x: 3, y: 3),
|
|
350
|
-
]
|
|
351
|
-
var calls: [Int] = []
|
|
352
|
-
let execution = assembleSequenceExecution(steps: steps) { index, _ in
|
|
353
|
-
calls.append(index)
|
|
354
|
-
return SequenceStepOutcome(
|
|
355
|
-
outcome: .performed,
|
|
356
|
-
gestureStartUptimeMs: Double(index * 10),
|
|
357
|
-
gestureEndUptimeMs: Double(index * 10 + 5)
|
|
358
|
-
)
|
|
359
|
-
}
|
|
360
|
-
XCTAssertEqual(calls, [0, 1, 2])
|
|
361
|
-
XCTAssertEqual(execution.completedSteps, 3)
|
|
362
|
-
XCTAssertNil(execution.failedStepIndex)
|
|
363
|
-
XCTAssertEqual(execution.results.map { $0.kind }, ["tap", "longPress", "tap"])
|
|
364
|
-
XCTAssertEqual(execution.gestureStartUptimeMs, 0)
|
|
365
|
-
XCTAssertEqual(execution.gestureEndUptimeMs, 25)
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
func testAssembleSequenceStopsAtFirstFailure() {
|
|
369
|
-
let steps = [
|
|
370
|
-
sequenceStep(kind: "tap", x: 1, y: 1),
|
|
371
|
-
sequenceStep(kind: "drag", x: 2, y: 2),
|
|
372
|
-
sequenceStep(kind: "tap", x: 3, y: 3),
|
|
373
|
-
]
|
|
374
|
-
var calls: [Int] = []
|
|
375
|
-
let execution = assembleSequenceExecution(steps: steps) { index, _ in
|
|
376
|
-
calls.append(index)
|
|
377
|
-
if index == 1 {
|
|
378
|
-
return SequenceStepOutcome(
|
|
379
|
-
outcome: .unsupported(message: "drag unsupported", hint: nil),
|
|
380
|
-
gestureStartUptimeMs: 10,
|
|
381
|
-
gestureEndUptimeMs: 15
|
|
382
|
-
)
|
|
383
|
-
}
|
|
384
|
-
return SequenceStepOutcome(outcome: .performed, gestureStartUptimeMs: 0, gestureEndUptimeMs: 5)
|
|
385
|
-
}
|
|
386
|
-
// Step 2 is never invoked.
|
|
387
|
-
XCTAssertEqual(calls, [0, 1])
|
|
388
|
-
XCTAssertEqual(execution.completedSteps, 1)
|
|
389
|
-
XCTAssertEqual(execution.failedStepIndex, 1)
|
|
390
|
-
// results.count == completedSteps + 1 (the failed step).
|
|
391
|
-
XCTAssertEqual(execution.results.count, 2)
|
|
392
|
-
XCTAssertEqual(execution.results[1].ok, false)
|
|
393
|
-
XCTAssertEqual(execution.results[1].errorCode, "UNSUPPORTED_OPERATION")
|
|
394
|
-
XCTAssertEqual(execution.results[1].errorMessage, "drag unsupported")
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
func testSequenceWorstCaseResponseStaysUnderJournalCap() throws {
|
|
398
|
-
let longMessage = String(repeating: "e", count: 200)
|
|
399
|
-
let results = (0..<20).map { index in
|
|
400
|
-
SequenceStepResult(
|
|
401
|
-
ok: index < 19,
|
|
402
|
-
kind: "drag",
|
|
403
|
-
errorCode: index < 19 ? nil : "UNSUPPORTED_OPERATION",
|
|
404
|
-
errorMessage: index < 19 ? nil : longMessage,
|
|
405
|
-
gestureStartUptimeMs: 123456.789,
|
|
406
|
-
gestureEndUptimeMs: 123466.789
|
|
407
|
-
)
|
|
408
|
-
}
|
|
409
|
-
let response = Response(
|
|
410
|
-
ok: true,
|
|
411
|
-
data: DataPayload(
|
|
412
|
-
message: "sequence",
|
|
413
|
-
completedSteps: 19,
|
|
414
|
-
failedStepIndex: 19,
|
|
415
|
-
sequenceResults: results
|
|
416
|
-
)
|
|
417
|
-
)
|
|
418
|
-
let encoded = try JSONEncoder().encode(response)
|
|
419
|
-
XCTAssertLessThan(encoded.count, 16 * 1024)
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
private func sequenceStep(
|
|
423
|
-
kind: String,
|
|
424
|
-
x: Double?,
|
|
425
|
-
y: Double? = nil,
|
|
426
|
-
x2: Double? = nil,
|
|
427
|
-
y2: Double? = nil
|
|
428
|
-
) -> SequenceStep {
|
|
429
|
-
SequenceStep(
|
|
430
|
-
kind: kind, x: x, y: y, x2: x2, y2: y2, durationMs: nil, pauseMs: nil, synthesized: nil)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/// Validation runs before any executor call, so the INVALID_ARGS paths are exercised without
|
|
434
|
-
/// reaching the device executor (which is never invoked when validation rejects).
|
|
435
|
-
private func executeSequenceForTest(steps: [SequenceStep]) -> Response {
|
|
436
|
-
let command = makeSequenceCommand(steps: steps)
|
|
437
|
-
return executeSequence(command: command, activeApp: app)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/// Build a sequence Command via JSON so the test does not depend on the memberwise init's
|
|
441
|
-
/// parameter order.
|
|
442
|
-
private func makeSequenceCommand(steps: [SequenceStep]) -> Command {
|
|
443
|
-
struct SequenceCommandFixture: Encodable {
|
|
444
|
-
let command = "sequence"
|
|
445
|
-
let commandId = "seq-test"
|
|
446
|
-
let steps: [SequenceStep]
|
|
447
|
-
}
|
|
448
|
-
let data = try! JSONEncoder().encode(SequenceCommandFixture(steps: steps))
|
|
449
|
-
return try! JSONDecoder().decode(Command.self, from: data)
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
#endif
|