agent-device 0.18.2 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.18.2.apk → agent-device-android-multitouch-helper-0.19.0.apk} +0 -0
  2. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.19.0.apk.sha256 +1 -0
  3. package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.18.2.manifest.json → agent-device-android-multitouch-helper-0.19.0.manifest.json} +4 -4
  4. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.18.2.apk → agent-device-android-snapshot-helper-0.19.0.apk} +0 -0
  5. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.19.0.apk.sha256 +1 -0
  6. package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.18.2.manifest.json → agent-device-android-snapshot-helper-0.19.0.manifest.json} +6 -6
  7. package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTapPointPolicy.swift +28 -0
  8. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +9 -120
  9. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +132 -66
  10. package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +155 -0
  11. package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +67 -0
  12. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +20 -74
  13. package/dist/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +64 -0
  14. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +0 -177
  15. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +81 -48
  16. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +102 -129
  17. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +208 -46
  18. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +82 -11
  19. package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +72 -0
  20. package/dist/src/agent-browser-tool.js +1 -0
  21. package/dist/src/android-adb.d.ts +64 -234
  22. package/dist/src/android-adb.js +1 -1
  23. package/dist/src/android.js +1 -5
  24. package/dist/src/app-lifecycle.js +7 -0
  25. package/dist/src/apple-runner-options.js +1 -0
  26. package/dist/src/apps.js +5 -11
  27. package/dist/src/artifacts.d.ts +4 -3
  28. package/dist/src/artifacts.js +1 -1
  29. package/dist/src/audio-probe.js +1 -1
  30. package/dist/src/batch.d.ts +2 -177
  31. package/dist/src/batch.js +1 -1
  32. package/dist/src/cli-help.js +64 -38
  33. package/dist/src/cli-output.js +1 -1
  34. package/dist/src/cli-runner.js +2 -0
  35. package/dist/src/cli.js +12 -41
  36. package/dist/src/client-metro-companion.js +1 -0
  37. package/dist/src/client.js +3 -0
  38. package/dist/src/command-metadata.js +1 -0
  39. package/dist/src/command-schema.js +22 -0
  40. package/dist/src/command-surface.js +1 -1
  41. package/dist/src/contracts.d.ts +2 -213
  42. package/dist/src/contracts.js +1 -1
  43. package/dist/src/debug-symbols.js +5 -3
  44. package/dist/src/device-input-state.js +2 -0
  45. package/dist/src/device-rotation.js +14 -0
  46. package/dist/src/devices.js +1 -1
  47. package/dist/src/devices2.js +1 -0
  48. package/dist/src/devices3.js +1 -0
  49. package/dist/src/find.js +1 -1
  50. package/dist/src/finders.d.ts +2 -81
  51. package/dist/src/finders.js +1 -1
  52. package/dist/src/generic.js +1 -1
  53. package/dist/src/handler-utils.js +1 -1
  54. package/dist/src/helper.js +2 -0
  55. package/dist/src/host-process.js +2 -0
  56. package/dist/src/index.d.ts +1265 -1768
  57. package/dist/src/index.js +1 -1
  58. package/dist/src/input-actions.js +3 -1
  59. package/dist/src/input-actions2.js +1 -0
  60. package/dist/src/install-source.d.ts +13 -14
  61. package/dist/src/install-source.js +1 -1
  62. package/dist/src/install-source2.js +1 -0
  63. package/dist/src/interaction-snapshot.js +1 -0
  64. package/dist/src/interaction.js +1 -1
  65. package/dist/src/interactor.js +2 -1
  66. package/dist/src/internal/bin.d.ts +1 -1
  67. package/dist/src/internal/bin.js +2 -5
  68. package/dist/src/internal/companion-tunnel.d.ts +1 -1
  69. package/dist/src/internal/companion-tunnel.js +1 -1
  70. package/dist/src/internal/daemon.d.ts +1 -1
  71. package/dist/src/internal/daemon.js +16 -1
  72. package/dist/src/internal/png-worker.d.ts +29 -26
  73. package/dist/src/internal/png-worker.js +1 -1
  74. package/dist/src/internal/update-check-entry.d.ts +1 -1
  75. package/dist/src/internal/update-check-entry.js +1 -1
  76. package/dist/src/io.d.ts +2 -85
  77. package/dist/src/io.js +1 -1
  78. package/dist/src/lease.js +1 -1
  79. package/dist/src/linux.js +1 -1
  80. package/dist/src/logcat.js +6 -0
  81. package/dist/src/manifest.js +1 -0
  82. package/dist/src/metro.d.ts +70 -113
  83. package/dist/src/metro.js +1 -1
  84. package/dist/src/metro2.js +1 -0
  85. package/dist/src/notifications.js +1 -1
  86. package/dist/src/output-path.js +1 -0
  87. package/dist/src/path-resolution.js +1 -0
  88. package/dist/src/png-worker-contract.js +1 -0
  89. package/dist/src/points.js +4 -0
  90. package/dist/src/project-runtime.js +1 -0
  91. package/dist/src/provider.js +1 -1
  92. package/dist/src/providers.js +1 -0
  93. package/dist/src/react-native.js +1 -1
  94. package/dist/src/record-trace-recording.js +2 -5
  95. package/dist/src/record-trace.js +1 -1
  96. package/dist/src/recording-provider.js +1 -1
  97. package/dist/src/registry.js +37 -0
  98. package/dist/src/remote-config.d.ts +2 -83
  99. package/dist/src/reporting.js +9 -0
  100. package/dist/src/rolldown-runtime.js +1 -0
  101. package/dist/src/runner-client.js +1 -0
  102. package/dist/src/runner-provider.js +1 -0
  103. package/dist/src/runner-sequence.js +1 -1
  104. package/dist/src/runtime-transport.js +1 -0
  105. package/dist/src/runtime.js +3 -0
  106. package/dist/src/scoped-provider.js +1 -0
  107. package/dist/src/sdk-android-adb.d.ts +144 -0
  108. package/dist/src/sdk-android-adb.js +4 -0
  109. package/dist/src/sdk-app-inventory.d.ts +4 -0
  110. package/dist/src/sdk-app-inventory.js +1 -0
  111. package/dist/src/sdk-batch-runner.d.ts +40 -0
  112. package/dist/src/sdk-batch-runner.js +2 -0
  113. package/dist/src/sdk-batch.js +2 -0
  114. package/dist/src/sdk-contracts.d.ts +386 -0
  115. package/dist/src/sdk-contracts.js +2 -0
  116. package/dist/src/sdk-finders.d.ts +61 -0
  117. package/dist/src/sdk-finders.js +1 -0
  118. package/dist/src/sdk-io.d.ts +79 -0
  119. package/dist/src/sdk-io.js +1 -0
  120. package/dist/src/sdk-remote-config.d.ts +140 -0
  121. package/dist/src/sdk-remote-config.js +1 -0
  122. package/dist/src/sdk-selectors.js +1 -0
  123. package/dist/src/selector-runtime.js +1 -1
  124. package/dist/src/selectors.d.ts +39 -137
  125. package/dist/src/selectors.js +1 -1
  126. package/dist/src/selectors2.js +1 -0
  127. package/dist/src/server.js +3 -2
  128. package/dist/src/session.js +10 -11
  129. package/dist/src/setting-state.js +1 -0
  130. package/dist/src/simctl.js +1 -0
  131. package/dist/src/simulator.js +1 -2
  132. package/dist/src/snapshot-diagnostics.js +3 -0
  133. package/dist/src/snapshot-helper.js +5 -0
  134. package/dist/src/snapshot-quality.js +1 -0
  135. package/dist/src/snapshot.js +1 -2
  136. package/dist/src/snapshot2.js +2 -0
  137. package/dist/src/{5560.js → timeouts.js} +1 -1
  138. package/dist/src/tool-provider.js +3 -0
  139. package/dist/src/tool-provider2.js +1 -0
  140. package/dist/src/ttl-memo.js +1 -0
  141. package/dist/src/update-check.js +1 -0
  142. package/dist/src/url.js +1 -0
  143. package/dist/src/version.js +1 -0
  144. package/dist/src/web.js +1 -1
  145. package/dist/src/xml.js +2 -0
  146. package/package.json +12 -19
  147. package/server.json +2 -2
  148. package/skills/agent-device/SKILL.md +2 -0
  149. package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.18.2.apk.sha256 +0 -1
  150. package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.18.2.apk.sha256 +0 -1
  151. package/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +0 -443
  152. package/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +0 -169
  153. package/apple-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +0 -220
  154. package/dist/src/1404.js +0 -1
  155. package/dist/src/1986.js +0 -1
  156. package/dist/src/2748.js +0 -1
  157. package/dist/src/2948.js +0 -30
  158. package/dist/src/3006.js +0 -1
  159. package/dist/src/314.js +0 -1
  160. package/dist/src/3340.js +0 -8
  161. package/dist/src/4418.js +0 -1
  162. package/dist/src/4428.js +0 -3
  163. package/dist/src/485.js +0 -1
  164. package/dist/src/4917.js +0 -1
  165. package/dist/src/495.js +0 -5
  166. package/dist/src/5448.js +0 -3
  167. package/dist/src/5484.js +0 -1
  168. package/dist/src/5643.js +0 -1
  169. package/dist/src/587.js +0 -1
  170. package/dist/src/5913.js +0 -4
  171. package/dist/src/6849.js +0 -1
  172. package/dist/src/6918.js +0 -1
  173. package/dist/src/6967.js +0 -1
  174. package/dist/src/760.js +0 -1
  175. package/dist/src/7871.js +0 -1
  176. package/dist/src/8184.js +0 -1
  177. package/dist/src/8225.js +0 -1
  178. package/dist/src/8391.js +0 -1
  179. package/dist/src/8404.js +0 -2
  180. package/dist/src/8414.js +0 -2
  181. package/dist/src/8875.js +0 -4
  182. package/dist/src/893.js +0 -1
  183. package/dist/src/8936.js +0 -1
  184. package/dist/src/9414.js +0 -1
  185. package/dist/src/9581.js +0 -22
  186. package/dist/src/9612.js +0 -1
  187. package/dist/src/9624.js +0 -1
  188. package/dist/src/9722.js +0 -38
  189. package/dist/src/devices~1.js +0 -1
  190. package/dist/src/devices~2.js +0 -1
  191. package/dist/src/input-actions~1.js +0 -1
  192. package/dist/src/rslib-runtime.js +0 -1
  193. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +0 -0
  194. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.pbxproj +0 -0
  195. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  196. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunner.xcodeproj/xcshareddata/xcschemes/AgentDeviceRunner.xcscheme +0 -0
  197. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +0 -0
  198. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests.entitlements +0 -0
  199. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +0 -0
  200. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +0 -0
  201. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.h +0 -0
  202. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerObjCExceptionCatcher.m +0 -0
  203. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +0 -0
  204. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +0 -0
  205. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +0 -0
  206. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Environment.swift +0 -0
  207. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +0 -0
  208. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +0 -0
  209. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +0 -0
  210. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +0 -0
  211. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +0 -0
  212. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TvRemote.swift +0 -0
  213. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/AgentDeviceRunnerUITests.xctestplan +0 -0
  214. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +0 -0
  215. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/RecordingScripts/recording-resize.swift +0 -0
  216. /package/{apple-runner → dist/apple-runner}/AgentDeviceRunner/RecordingScripts/recording-trim.swift +0 -0
@@ -1,443 +0,0 @@
1
- import Foundation
2
- import XCTest
3
-
4
- enum RunnerCommandLifecycleState: String {
5
- case notAccepted
6
- case accepted
7
- case started
8
- case completed
9
- case failed
10
- }
11
-
12
- struct RunnerCommandJournalEntry {
13
- let commandId: String
14
- let command: String
15
- var state: RunnerCommandLifecycleState
16
- var responseOk: Bool?
17
- var responseJson: String?
18
- var error: ErrorPayload?
19
- }
20
-
21
- final class RunnerCommandJournal {
22
- private let lock = NSLock()
23
- private let maxEntries = 64
24
- private let maxResponseJsonBytes = 16 * 1024
25
- private var entries: [String: RunnerCommandJournalEntry] = [:]
26
- private var order: [String] = []
27
-
28
- func accept(command: Command) {
29
- guard let commandId = normalizedCommandId(command.commandId) else { return }
30
- lock.lock()
31
- defer { lock.unlock() }
32
- entries[commandId] = RunnerCommandJournalEntry(
33
- commandId: commandId,
34
- command: command.command.rawValue,
35
- state: .accepted,
36
- responseOk: nil,
37
- responseJson: nil,
38
- error: nil
39
- )
40
- order.removeAll { $0 == commandId }
41
- order.append(commandId)
42
- pruneIfNeeded()
43
- }
44
-
45
- func start(command: Command) {
46
- update(command: command, state: .started, responseOk: nil, responseJson: nil, error: nil)
47
- }
48
-
49
- func finish(command: Command, response: Response) {
50
- update(
51
- command: command,
52
- state: response.ok ? .completed : .failed,
53
- responseOk: response.ok,
54
- responseJson: encodeResponseJson(command: command, response: response),
55
- error: response.error
56
- )
57
- }
58
-
59
- func fail(command: Command, error: Error) {
60
- update(
61
- command: command,
62
- state: .failed,
63
- responseOk: nil,
64
- responseJson: nil,
65
- error: ErrorPayload(message: error.localizedDescription)
66
- )
67
- }
68
-
69
- func status(commandId: String) -> DataPayload {
70
- guard let normalized = normalizedCommandId(commandId) else {
71
- return DataPayload(lifecycleState: RunnerCommandLifecycleState.notAccepted.rawValue)
72
- }
73
- lock.lock()
74
- let entry = entries[normalized]
75
- lock.unlock()
76
- guard let entry else {
77
- return DataPayload(
78
- commandId: normalized,
79
- lifecycleState: RunnerCommandLifecycleState.notAccepted.rawValue
80
- )
81
- }
82
- return DataPayload(
83
- commandId: entry.commandId,
84
- lifecycleState: entry.state.rawValue,
85
- lifecycleCommand: entry.command,
86
- lifecycleResponseOk: entry.responseOk,
87
- lifecycleResponseJson: entry.responseJson,
88
- lifecycleErrorCode: entry.error?.code,
89
- lifecycleErrorMessage: entry.error?.message,
90
- lifecycleErrorHint: entry.error?.hint
91
- )
92
- }
93
-
94
- private func update(
95
- command: Command,
96
- state: RunnerCommandLifecycleState,
97
- responseOk: Bool?,
98
- responseJson: String?,
99
- error: ErrorPayload?
100
- ) {
101
- guard let commandId = normalizedCommandId(command.commandId) else { return }
102
- lock.lock()
103
- defer { lock.unlock() }
104
- var entry = entries[commandId] ?? RunnerCommandJournalEntry(
105
- commandId: commandId,
106
- command: command.command.rawValue,
107
- state: .accepted,
108
- responseOk: nil,
109
- responseJson: nil,
110
- error: nil
111
- )
112
- entry.state = state
113
- entry.responseOk = responseOk
114
- entry.responseJson = responseJson
115
- entry.error = error
116
- entries[commandId] = entry
117
- order.removeAll { $0 == commandId }
118
- order.append(commandId)
119
- pruneIfNeeded()
120
- }
121
-
122
- private func pruneIfNeeded() {
123
- while order.count > maxEntries {
124
- let removed = order.removeFirst()
125
- entries.removeValue(forKey: removed)
126
- }
127
- }
128
-
129
- private func normalizedCommandId(_ value: String?) -> String? {
130
- guard let value else { return nil }
131
- let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
132
- return trimmed.isEmpty ? nil : trimmed
133
- }
134
-
135
- private func encodeResponseJson(command: Command, response: Response) -> String? {
136
- guard shouldRetainResponseJson(command: command) else { return nil }
137
- guard let data = try? JSONEncoder().encode(response) else { return nil }
138
- guard data.count <= maxResponseJsonBytes else { return nil }
139
- return String(data: data, encoding: .utf8)
140
- }
141
-
142
- private func shouldRetainResponseJson(command: Command) -> Bool {
143
- switch command.command {
144
- case .snapshot, .screenshot:
145
- return false
146
- case .tap, .mouseClick, .longPress, .drag,
147
- .remotePress, .type, .swipe, .scroll, .desktopScroll, .findText, .querySelector, .readText, .back,
148
- .backInApp, .backSystem, .home, .rotate, .appSwitcher, .keyboardDismiss, .keyboardReturn,
149
- .alert, .pinch, .sequence, .rotateGesture, .transformGesture, .recordStart, .recordStop,
150
- .status, .uptime, .shutdown:
151
- return true
152
- }
153
- }
154
- }
155
-
156
- #if AGENT_DEVICE_RUNNER_UNIT_TESTS
157
- extension RunnerTests {
158
- func testUptimeBypassesCommandJournal() throws {
159
- let command = runnerJournalCommand("uptime", id: "uptime-probe")
160
-
161
- let response = try execute(command: command)
162
- let status = commandJournal.status(commandId: "uptime-probe")
163
-
164
- XCTAssertEqual(response.ok, true)
165
- XCTAssertNotNil(response.data?.currentUptimeMs)
166
- XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.notAccepted.rawValue)
167
- }
168
-
169
- func testStampingCurrentUptimePreservesPayload() {
170
- let stamped = Response(ok: true, data: DataPayload(message: "recording started"))
171
- .stampingCurrentUptimeMs(123.5)
172
-
173
- XCTAssertEqual(stamped.ok, true)
174
- XCTAssertEqual(stamped.data?.message, "recording started")
175
- XCTAssertEqual(stamped.data?.currentUptimeMs, 123.5)
176
- }
177
-
178
- func testStampingCurrentUptimeCreatesPayloadWhenNil() {
179
- let stamped = Response(ok: true).stampingCurrentUptimeMs(456.0)
180
-
181
- XCTAssertEqual(stamped.ok, true)
182
- XCTAssertEqual(stamped.data?.currentUptimeMs, 456.0)
183
- }
184
-
185
- func testStampingCurrentUptimeSkipsErrorResponses() {
186
- let response = Response(ok: false, error: ErrorPayload(message: "boom"))
187
- let stamped = response.stampingCurrentUptimeMs(789.0)
188
-
189
- XCTAssertEqual(stamped.ok, false)
190
- XCTAssertNil(stamped.data)
191
- XCTAssertEqual(stamped.error?.message, "boom")
192
- }
193
-
194
- func testJournalStoredResponseStaysUnstamped() throws {
195
- let journal = RunnerCommandJournal()
196
- let recordStart = runnerJournalCommand("recordStart", id: "record-start-anchor")
197
-
198
- journal.accept(command: recordStart)
199
- journal.finish(
200
- command: recordStart,
201
- response: Response(ok: true, data: DataPayload(message: "recording started"))
202
- )
203
-
204
- let status = journal.status(commandId: "record-start-anchor")
205
- let responseJson = try XCTUnwrap(status.lifecycleResponseJson)
206
- XCTAssertFalse(responseJson.contains("currentUptimeMs"))
207
- }
208
-
209
- func testCommandJournalRetentionPolicy() throws {
210
- let journal = RunnerCommandJournal()
211
-
212
- let uptime = runnerJournalCommand("uptime", id: "small-scalar")
213
- journal.accept(command: uptime)
214
- journal.finish(
215
- command: uptime,
216
- response: Response(ok: true, data: DataPayload(currentUptimeMs: 12.5))
217
- )
218
-
219
- let scalarStatus = journal.status(commandId: "small-scalar")
220
- XCTAssertEqual(scalarStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
221
- XCTAssertEqual(scalarStatus.lifecycleResponseOk, true)
222
- XCTAssertNotNil(scalarStatus.lifecycleResponseJson)
223
- let scalarResponse = try decodeRunnerJournalResponse(scalarStatus.lifecycleResponseJson)
224
- XCTAssertEqual(scalarResponse.data?.currentUptimeMs, 12.5)
225
-
226
- let querySelector = runnerJournalCommand("querySelector", id: "small-object")
227
- journal.accept(command: querySelector)
228
- journal.finish(
229
- command: querySelector,
230
- response: Response(ok: true, data: DataPayload(found: true, nodes: [runnerJournalNode()]))
231
- )
232
-
233
- let objectStatus = journal.status(commandId: "small-object")
234
- XCTAssertNotNil(objectStatus.lifecycleResponseJson)
235
- let objectResponse = try decodeRunnerJournalResponse(objectStatus.lifecycleResponseJson)
236
- XCTAssertEqual(objectResponse.data?.found, true)
237
- XCTAssertEqual(objectResponse.data?.nodes?.count, 1)
238
-
239
- let snapshot = runnerJournalCommand("snapshot", id: "snapshot-tree")
240
- journal.accept(command: snapshot)
241
- journal.finish(
242
- command: snapshot,
243
- response: Response(ok: true, data: DataPayload(nodes: [runnerJournalNode()], truncated: false))
244
- )
245
-
246
- let snapshotStatus = journal.status(commandId: "snapshot-tree")
247
- XCTAssertEqual(snapshotStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
248
- XCTAssertEqual(snapshotStatus.lifecycleResponseOk, true)
249
- XCTAssertNil(snapshotStatus.lifecycleResponseJson)
250
-
251
- let screenshot = runnerJournalCommand("screenshot", id: "screenshot-artifact")
252
- journal.accept(command: screenshot)
253
- journal.finish(
254
- command: screenshot,
255
- response: Response(ok: true, data: DataPayload(message: "tmp/screenshot-1.png"))
256
- )
257
-
258
- let screenshotStatus = journal.status(commandId: "screenshot-artifact")
259
- XCTAssertEqual(screenshotStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
260
- XCTAssertEqual(screenshotStatus.lifecycleResponseOk, true)
261
- XCTAssertNil(screenshotStatus.lifecycleResponseJson)
262
-
263
- let scroll = runnerJournalCommand("scroll", id: "scroll-drag")
264
- journal.accept(command: scroll)
265
- journal.finish(
266
- command: scroll,
267
- response: Response(
268
- ok: true,
269
- data: DataPayload(
270
- message: "scrolled",
271
- gestureStartUptimeMs: 1,
272
- gestureEndUptimeMs: 2,
273
- x: 155,
274
- y: 420,
275
- x2: 155,
276
- y2: 301,
277
- referenceWidth: 300,
278
- referenceHeight: 600
279
- )
280
- )
281
- )
282
-
283
- let scrollStatus = journal.status(commandId: "scroll-drag")
284
- XCTAssertEqual(scrollStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
285
- XCTAssertEqual(scrollStatus.lifecycleResponseOk, true)
286
- XCTAssertNotNil(scrollStatus.lifecycleResponseJson)
287
- let scrollResponse = try decodeRunnerJournalResponse(scrollStatus.lifecycleResponseJson)
288
- XCTAssertEqual(scrollResponse.data?.x, 155)
289
- XCTAssertEqual(scrollResponse.data?.y, 420)
290
- XCTAssertEqual(scrollResponse.data?.x2, 155)
291
- XCTAssertEqual(scrollResponse.data?.y2, 301)
292
- XCTAssertEqual(scrollResponse.data?.referenceWidth, 300)
293
- XCTAssertEqual(scrollResponse.data?.referenceHeight, 600)
294
-
295
- let largeRead = runnerJournalCommand("readText", id: "large-read")
296
- journal.accept(command: largeRead)
297
- journal.finish(
298
- command: largeRead,
299
- response: Response(ok: true, data: DataPayload(text: String(repeating: "x", count: 17 * 1024)))
300
- )
301
-
302
- let largeReadStatus = journal.status(commandId: "large-read")
303
- XCTAssertEqual(largeReadStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
304
- XCTAssertEqual(largeReadStatus.lifecycleResponseOk, true)
305
- XCTAssertNil(largeReadStatus.lifecycleResponseJson)
306
- }
307
-
308
- func testCommandJournalKeepsErrorMetadataWhenResponseJsonIsDropped() {
309
- let journal = RunnerCommandJournal()
310
- let snapshot = runnerJournalCommand("snapshot", id: "snapshot-error")
311
- let hint = "Try a smaller read such as snapshot -s <visible label or id> -d 8."
312
-
313
- journal.accept(command: snapshot)
314
- journal.finish(
315
- command: snapshot,
316
- response: Response(
317
- ok: false,
318
- error: ErrorPayload(
319
- code: "IOS_AX_SNAPSHOT_FAILED",
320
- message: "iOS XCTest snapshot failed while serializing the accessibility tree.",
321
- hint: hint
322
- )
323
- )
324
- )
325
-
326
- let status = journal.status(commandId: "snapshot-error")
327
- XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.failed.rawValue)
328
- XCTAssertEqual(status.lifecycleResponseOk, false)
329
- XCTAssertNil(status.lifecycleResponseJson)
330
- XCTAssertEqual(status.lifecycleErrorCode, "IOS_AX_SNAPSHOT_FAILED")
331
- XCTAssertEqual(
332
- status.lifecycleErrorMessage,
333
- "iOS XCTest snapshot failed while serializing the accessibility tree."
334
- )
335
- XCTAssertEqual(status.lifecycleErrorHint, hint)
336
- }
337
-
338
- func testCommandJournalRetainsCompletedSequenceResults() throws {
339
- let journal = RunnerCommandJournal()
340
- let sequence = runnerJournalCommand("sequence", id: "sequence-completed")
341
- let results = (0..<20).map { _ in
342
- SequenceStepResult(
343
- ok: true,
344
- kind: "tap",
345
- errorCode: nil,
346
- errorMessage: nil,
347
- gestureStartUptimeMs: 100,
348
- gestureEndUptimeMs: 120
349
- )
350
- }
351
-
352
- journal.accept(command: sequence)
353
- journal.finish(
354
- command: sequence,
355
- response: Response(
356
- ok: true,
357
- data: DataPayload(
358
- message: "sequence",
359
- completedSteps: 20,
360
- failedStepIndex: nil,
361
- sequenceResults: results
362
- )
363
- )
364
- )
365
-
366
- let status = journal.status(commandId: "sequence-completed")
367
- XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
368
- XCTAssertEqual(status.lifecycleResponseOk, true)
369
- let json = try XCTUnwrap(status.lifecycleResponseJson)
370
- // Worst-case 20-step response must stay under the 16KB journal retention cap.
371
- XCTAssertLessThan(json.utf8.count, 16 * 1024)
372
- let decoded = try decodeRunnerJournalResponse(status.lifecycleResponseJson)
373
- XCTAssertEqual(decoded.data?.completedSteps, 20)
374
- XCTAssertEqual(decoded.data?.sequenceResults?.count, 20)
375
- }
376
-
377
- func testCommandJournalRetainsFailedSequenceResults() throws {
378
- let journal = RunnerCommandJournal()
379
- let sequence = runnerJournalCommand("sequence", id: "sequence-failed")
380
- let longError = String(repeating: "z", count: 200)
381
- let results: [SequenceStepResult] = [
382
- SequenceStepResult(ok: true, kind: "tap", errorCode: nil, errorMessage: nil,
383
- gestureStartUptimeMs: 100, gestureEndUptimeMs: 120),
384
- SequenceStepResult(ok: true, kind: "tap", errorCode: nil, errorMessage: nil,
385
- gestureStartUptimeMs: 130, gestureEndUptimeMs: 150),
386
- SequenceStepResult(ok: false, kind: "drag", errorCode: "UNSUPPORTED_OPERATION",
387
- errorMessage: longError, gestureStartUptimeMs: 160, gestureEndUptimeMs: 180),
388
- ]
389
-
390
- journal.accept(command: sequence)
391
- journal.finish(
392
- command: sequence,
393
- response: Response(
394
- ok: true,
395
- data: DataPayload(
396
- message: "sequence",
397
- completedSteps: 2,
398
- failedStepIndex: 2,
399
- sequenceResults: results
400
- )
401
- )
402
- )
403
-
404
- let status = journal.status(commandId: "sequence-failed")
405
- XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
406
- let decoded = try decodeRunnerJournalResponse(status.lifecycleResponseJson)
407
- XCTAssertEqual(decoded.data?.completedSteps, 2)
408
- XCTAssertEqual(decoded.data?.failedStepIndex, 2)
409
- XCTAssertEqual(decoded.data?.sequenceResults?.count, 3)
410
- XCTAssertEqual(decoded.data?.sequenceResults?[2].ok, false)
411
- XCTAssertEqual(decoded.data?.sequenceResults?[2].errorCode, "UNSUPPORTED_OPERATION")
412
- }
413
-
414
- private func runnerJournalCommand(_ command: String, id: String) -> Command {
415
- let json = #"{"command":"\#(command)","commandId":"\#(id)"}"#
416
- return try! JSONDecoder().decode(Command.self, from: Data(json.utf8))
417
- }
418
-
419
- private func runnerJournalNode() -> SnapshotNode {
420
- SnapshotNode(
421
- index: 0,
422
- type: "button",
423
- label: "Continue",
424
- identifier: "continue",
425
- value: nil,
426
- rect: SnapshotRect(x: 10, y: 20, width: 100, height: 44),
427
- enabled: true,
428
- focused: nil,
429
- selected: nil,
430
- hittable: true,
431
- depth: 0,
432
- parentIndex: nil,
433
- hiddenContentAbove: nil,
434
- hiddenContentBelow: nil
435
- )
436
- }
437
-
438
- private func decodeRunnerJournalResponse(_ responseJson: String?) throws -> Response {
439
- let responseJson = try XCTUnwrap(responseJson)
440
- return try JSONDecoder().decode(Response.self, from: Data(responseJson.utf8))
441
- }
442
- }
443
- #endif
@@ -1,169 +0,0 @@
1
- import XCTest
2
-
3
- struct FlatSnapshotFilterNode {
4
- let isRoot: Bool
5
- let label: String
6
- let identifier: String
7
- let valueText: String?
8
- let visible: Bool
9
-
10
- func matchesScope(_ scope: String) -> Bool {
11
- let haystack = [label, identifier, valueText ?? ""].joined(separator: "\n")
12
- return haystack.localizedCaseInsensitiveContains(scope)
13
- }
14
- }
15
-
16
- struct FlatSnapshotFilterDecision {
17
- let include: Bool
18
- let insideMatchedScope: Bool
19
- }
20
-
21
- extension RunnerTests {
22
- func flatSnapshotFilterDecision(
23
- _ node: FlatSnapshotFilterNode,
24
- options: SnapshotOptions,
25
- insideMatchedScope: Bool
26
- ) -> FlatSnapshotFilterDecision {
27
- let scope = options.scope?.trimmingCharacters(in: .whitespacesAndNewlines)
28
- let scopeActive = scope?.isEmpty == false
29
- let matchesScope: Bool
30
- if scopeActive, let scope {
31
- matchesScope = node.matchesScope(scope)
32
- } else {
33
- matchesScope = false
34
- }
35
- let nowInsideScope = insideMatchedScope || matchesScope
36
-
37
- let include: Bool
38
- if node.isRoot {
39
- include = true
40
- } else if scopeActive && !nowInsideScope {
41
- include = false
42
- } else if options.interactiveOnly && !node.visible {
43
- include = false
44
- } else {
45
- include = true
46
- }
47
-
48
- return FlatSnapshotFilterDecision(include: include, insideMatchedScope: nowInsideScope)
49
- }
50
-
51
- func privateAXInteractiveCandidate(rawElementType: Int) -> Bool {
52
- guard let type = flatSnapshotElementType(rawElementType: rawElementType) else {
53
- return false
54
- }
55
- return interactiveTypes.contains(type) || Self.scrollContainerTypes.contains(type)
56
- }
57
-
58
- func flatSnapshotElementType(rawElementType: Int) -> XCUIElement.ElementType? {
59
- guard let raw = UInt(exactly: rawElementType),
60
- let type = XCUIElement.ElementType(rawValue: raw)
61
- else {
62
- return nil
63
- }
64
- return type
65
- }
66
-
67
- #if AGENT_DEVICE_RUNNER_UNIT_TESTS
68
- func testFlatSnapshotFilterDecisionMatrixCoversOptions() {
69
- let visibleContent = FlatSnapshotFilterNode(
70
- isRoot: false,
71
- label: "Welcome back",
72
- identifier: "",
73
- valueText: nil,
74
- visible: true
75
- )
76
- let hiddenInteractive = FlatSnapshotFilterNode(
77
- isRoot: false,
78
- label: "Hidden menu",
79
- identifier: "",
80
- valueText: nil,
81
- visible: false
82
- )
83
- let decorative = FlatSnapshotFilterNode(
84
- isRoot: false,
85
- label: "",
86
- identifier: "",
87
- valueText: nil,
88
- visible: true
89
- )
90
-
91
- XCTAssertTrue(
92
- flatSnapshotFilterDecision(
93
- visibleContent,
94
- options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
95
- insideMatchedScope: false
96
- ).include
97
- )
98
- XCTAssertFalse(
99
- flatSnapshotFilterDecision(
100
- hiddenInteractive,
101
- options: SnapshotOptions(interactiveOnly: true, depth: nil, scope: nil, raw: false),
102
- insideMatchedScope: false
103
- ).include
104
- )
105
- XCTAssertTrue(
106
- flatSnapshotFilterDecision(
107
- decorative,
108
- options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
109
- insideMatchedScope: false
110
- ).include
111
- )
112
- XCTAssertTrue(
113
- flatSnapshotFilterDecision(
114
- decorative,
115
- options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
116
- insideMatchedScope: false
117
- ).include
118
- )
119
- }
120
-
121
- func testFlatSnapshotFilterDecisionCarriesSubtreeScopeState() {
122
- let scopeRoot = FlatSnapshotFilterNode(
123
- isRoot: false,
124
- label: "",
125
- identifier: "homeScreen",
126
- valueText: nil,
127
- visible: true
128
- )
129
- let unmatchedDescendant = FlatSnapshotFilterNode(
130
- isRoot: false,
131
- label: "Post body without the scope text",
132
- identifier: "",
133
- valueText: nil,
134
- visible: true
135
- )
136
- let options = SnapshotOptions(interactiveOnly: false, depth: nil, scope: "homeScreen", raw: false)
137
-
138
- let rootDecision = flatSnapshotFilterDecision(
139
- scopeRoot,
140
- options: options,
141
- insideMatchedScope: false
142
- )
143
- XCTAssertTrue(rootDecision.include)
144
- XCTAssertTrue(rootDecision.insideMatchedScope)
145
-
146
- XCTAssertTrue(
147
- flatSnapshotFilterDecision(
148
- unmatchedDescendant,
149
- options: options,
150
- insideMatchedScope: rootDecision.insideMatchedScope
151
- ).include
152
- )
153
- XCTAssertFalse(
154
- flatSnapshotFilterDecision(
155
- unmatchedDescendant,
156
- options: options,
157
- insideMatchedScope: false
158
- ).include
159
- )
160
- }
161
-
162
- func testPrivateAXInteractiveCandidatesPreserveBackendInputs() {
163
- XCTAssertTrue(
164
- privateAXInteractiveCandidate(rawElementType: Int(XCUIElement.ElementType.scrollView.rawValue)),
165
- "private AX marks scroll containers as interactive candidates"
166
- )
167
- }
168
- #endif
169
- }