Skip to content

Commit 7e30a83

Browse files
authored
fix: tune iOS runner response retention (#665)
* fix: tune iOS runner response retention * fix: require explicit runner response retention
1 parent 3ae2472 commit 7e30a83

1 file changed

Lines changed: 145 additions & 3 deletions

File tree

ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import XCTest
23

34
enum RunnerCommandLifecycleState: String {
45
case notAccepted
@@ -50,7 +51,7 @@ final class RunnerCommandJournal {
5051
command: command,
5152
state: response.ok ? .completed : .failed,
5253
responseOk: response.ok,
53-
responseJson: encodeResponseJson(response),
54+
responseJson: encodeResponseJson(command: command, response: response),
5455
error: response.error
5556
)
5657
}
@@ -131,10 +132,151 @@ final class RunnerCommandJournal {
131132
return trimmed.isEmpty ? nil : trimmed
132133
}
133134

134-
private func encodeResponseJson(_ response: Response) -> String? {
135-
guard response.data?.nodes == nil else { return nil }
135+
private func encodeResponseJson(command: Command, response: Response) -> String? {
136+
guard shouldRetainResponseJson(command: command) else { return nil }
136137
guard let data = try? JSONEncoder().encode(response) else { return nil }
137138
guard data.count <= maxResponseJsonBytes else { return nil }
138139
return String(data: data, encoding: .utf8)
139140
}
141+
142+
private func shouldRetainResponseJson(command: Command) -> Bool {
143+
switch command.command {
144+
case .snapshot, .screenshot:
145+
return false
146+
case .tap, .mouseClick, .tapSeries, .longPress, .interactionFrame, .drag, .dragSeries,
147+
.remotePress, .type, .swipe, .findText, .querySelector, .readText, .back, .backInApp,
148+
.backSystem, .home, .rotate, .appSwitcher, .keyboardDismiss, .keyboardReturn, .alert,
149+
.pinch, .rotateGesture, .transformGesture, .recordStart, .recordStop, .status, .uptime,
150+
.shutdown:
151+
return true
152+
}
153+
}
154+
}
155+
156+
extension RunnerTests {
157+
func testCommandJournalRetentionPolicy() throws {
158+
let journal = RunnerCommandJournal()
159+
160+
let uptime = runnerJournalCommand("uptime", id: "small-scalar")
161+
journal.accept(command: uptime)
162+
journal.finish(
163+
command: uptime,
164+
response: Response(ok: true, data: DataPayload(currentUptimeMs: 12.5))
165+
)
166+
167+
let scalarStatus = journal.status(commandId: "small-scalar")
168+
XCTAssertEqual(scalarStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
169+
XCTAssertEqual(scalarStatus.lifecycleResponseOk, true)
170+
XCTAssertNotNil(scalarStatus.lifecycleResponseJson)
171+
let scalarResponse = try decodeRunnerJournalResponse(scalarStatus.lifecycleResponseJson)
172+
XCTAssertEqual(scalarResponse.data?.currentUptimeMs, 12.5)
173+
174+
let querySelector = runnerJournalCommand("querySelector", id: "small-object")
175+
journal.accept(command: querySelector)
176+
journal.finish(
177+
command: querySelector,
178+
response: Response(ok: true, data: DataPayload(found: true, nodes: [runnerJournalNode()]))
179+
)
180+
181+
let objectStatus = journal.status(commandId: "small-object")
182+
XCTAssertNotNil(objectStatus.lifecycleResponseJson)
183+
let objectResponse = try decodeRunnerJournalResponse(objectStatus.lifecycleResponseJson)
184+
XCTAssertEqual(objectResponse.data?.found, true)
185+
XCTAssertEqual(objectResponse.data?.nodes?.count, 1)
186+
187+
let snapshot = runnerJournalCommand("snapshot", id: "snapshot-tree")
188+
journal.accept(command: snapshot)
189+
journal.finish(
190+
command: snapshot,
191+
response: Response(ok: true, data: DataPayload(nodes: [runnerJournalNode()], truncated: false))
192+
)
193+
194+
let snapshotStatus = journal.status(commandId: "snapshot-tree")
195+
XCTAssertEqual(snapshotStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
196+
XCTAssertEqual(snapshotStatus.lifecycleResponseOk, true)
197+
XCTAssertNil(snapshotStatus.lifecycleResponseJson)
198+
199+
let screenshot = runnerJournalCommand("screenshot", id: "screenshot-artifact")
200+
journal.accept(command: screenshot)
201+
journal.finish(
202+
command: screenshot,
203+
response: Response(ok: true, data: DataPayload(message: "tmp/screenshot-1.png"))
204+
)
205+
206+
let screenshotStatus = journal.status(commandId: "screenshot-artifact")
207+
XCTAssertEqual(screenshotStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
208+
XCTAssertEqual(screenshotStatus.lifecycleResponseOk, true)
209+
XCTAssertNil(screenshotStatus.lifecycleResponseJson)
210+
211+
let largeRead = runnerJournalCommand("readText", id: "large-read")
212+
journal.accept(command: largeRead)
213+
journal.finish(
214+
command: largeRead,
215+
response: Response(ok: true, data: DataPayload(text: String(repeating: "x", count: 17 * 1024)))
216+
)
217+
218+
let largeReadStatus = journal.status(commandId: "large-read")
219+
XCTAssertEqual(largeReadStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
220+
XCTAssertEqual(largeReadStatus.lifecycleResponseOk, true)
221+
XCTAssertNil(largeReadStatus.lifecycleResponseJson)
222+
}
223+
224+
func testCommandJournalKeepsErrorMetadataWhenResponseJsonIsDropped() {
225+
let journal = RunnerCommandJournal()
226+
let snapshot = runnerJournalCommand("snapshot", id: "snapshot-error")
227+
let hint = "Try a smaller read such as snapshot -s <visible label or id> -d 8."
228+
229+
journal.accept(command: snapshot)
230+
journal.finish(
231+
command: snapshot,
232+
response: Response(
233+
ok: false,
234+
error: ErrorPayload(
235+
code: "IOS_AX_SNAPSHOT_FAILED",
236+
message: "iOS XCTest snapshot failed while serializing the accessibility tree.",
237+
hint: hint
238+
)
239+
)
240+
)
241+
242+
let status = journal.status(commandId: "snapshot-error")
243+
XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.failed.rawValue)
244+
XCTAssertEqual(status.lifecycleResponseOk, false)
245+
XCTAssertNil(status.lifecycleResponseJson)
246+
XCTAssertEqual(status.lifecycleErrorCode, "IOS_AX_SNAPSHOT_FAILED")
247+
XCTAssertEqual(
248+
status.lifecycleErrorMessage,
249+
"iOS XCTest snapshot failed while serializing the accessibility tree."
250+
)
251+
XCTAssertEqual(status.lifecycleErrorHint, hint)
252+
}
253+
254+
private func runnerJournalCommand(_ command: String, id: String) -> Command {
255+
let json = #"{"command":"\#(command)","commandId":"\#(id)"}"#
256+
return try! JSONDecoder().decode(Command.self, from: Data(json.utf8))
257+
}
258+
259+
private func runnerJournalNode() -> SnapshotNode {
260+
SnapshotNode(
261+
index: 0,
262+
type: "button",
263+
label: "Continue",
264+
identifier: "continue",
265+
value: nil,
266+
rect: SnapshotRect(x: 10, y: 20, width: 100, height: 44),
267+
enabled: true,
268+
focused: nil,
269+
selected: nil,
270+
hittable: true,
271+
depth: 0,
272+
parentIndex: nil,
273+
hiddenContentAbove: nil,
274+
hiddenContentBelow: nil
275+
)
276+
}
277+
278+
private func decodeRunnerJournalResponse(_ responseJson: String?) throws -> Response {
279+
let responseJson = try XCTUnwrap(responseJson)
280+
return try JSONDecoder().decode(Response.self, from: Data(responseJson.utf8))
281+
}
140282
}

0 commit comments

Comments
 (0)