agent-device 0.17.1 → 0.17.3
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 +3 -3
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.1.apk → agent-device-android-multitouch-helper-0.17.3.apk} +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.3.apk.sha256 +1 -0
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.1.manifest.json → agent-device-android-multitouch-helper-0.17.3.manifest.json} +4 -4
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.1.apk → agent-device-android-snapshot-helper-0.17.3.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.3.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.1.manifest.json → agent-device-android-snapshot-helper-0.17.3.manifest.json} +6 -6
- package/dist/src/123.js +1 -0
- package/dist/src/1231.js +1 -1
- package/dist/src/1352.js +1 -1
- package/dist/src/221.js +2 -2
- package/dist/src/2403.js +3 -0
- package/dist/src/2415.js +36 -30
- package/dist/src/4778.js +1 -1
- package/dist/src/5611.js +1 -0
- package/dist/src/5827.js +1 -0
- package/dist/src/6088.js +1 -0
- package/dist/src/6133.js +1 -0
- package/dist/src/7556.js +1 -1
- package/dist/src/7847.js +1 -1
- package/dist/src/8806.js +3 -3
- package/dist/src/9010.js +1 -0
- package/dist/src/9238.js +3 -3
- package/dist/src/9471.js +1 -1
- package/dist/src/9542.js +6 -3
- package/dist/src/9616.js +2 -0
- package/dist/src/9818.js +1 -1
- package/dist/src/android-adb.d.ts +12 -6
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-snapshot-helper.d.ts +15 -8
- package/dist/src/android.js +5 -5
- package/dist/src/apple.js +1 -1
- package/dist/src/apps.js +10 -10
- package/dist/src/args.js +53 -14
- package/dist/src/batch.d.ts +16 -4
- package/dist/src/batch.js +1 -1
- package/dist/src/cli-output.js +29 -0
- package/dist/src/cli.js +17 -42
- package/dist/src/command-surface.js +1 -1
- package/dist/src/contracts.d.ts +80 -4
- package/dist/src/contracts.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/finders.d.ts +3 -1
- package/dist/src/generic.js +7 -7
- package/dist/src/index.d.ts +250 -79
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +1 -1
- package/dist/src/interaction.js +1 -1
- package/dist/src/internal/png-worker.d.ts +26 -0
- package/dist/src/internal/png-worker.js +1 -0
- package/dist/src/metro.d.ts +3 -1
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-trace-recording.js +3 -3
- package/dist/src/remote-config.d.ts +33 -7
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selectors.d.ts +17 -3
- package/dist/src/selectors.js +1 -1
- package/dist/src/server.js +2 -2
- package/dist/src/session.js +8 -11
- package/dist/src/snapshot.js +2 -2
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +14 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +297 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +328 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +121 -172
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +153 -5
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +171 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +0 -10
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +64 -18
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +200 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +406 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +82 -169
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +425 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +77 -6
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +10 -1
- package/ios-runner/README.md +5 -5
- package/ios-runner/RUNNER_PROTOCOL.md +0 -1
- package/package.json +8 -5
- package/server.json +2 -2
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.1.apk.sha256 +0 -1
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.1.apk.sha256 +0 -1
- package/dist/src/1998.js +0 -1
- package/dist/src/2805.js +0 -1
- package/dist/src/4057.js +0 -1
- package/dist/src/5310.js +0 -1
- package/dist/src/5792.js +0 -1
- package/dist/src/6085.js +0 -1
- package/dist/src/6232.js +0 -1
- package/dist/src/7455.js +0 -1
- package/dist/src/8020.js +0 -1
- package/dist/src/8502.js +0 -1
- package/dist/src/8699.js +0 -1
- package/dist/src/940.js +0 -1
- package/dist/src/9404.js +0 -1
- package/dist/src/9533.js +0 -1
- package/dist/src/command-metadata.js +0 -1
- /package/dist/src/{1393.js → 3675.js} +0 -0
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift
CHANGED
|
@@ -143,11 +143,11 @@ final class RunnerCommandJournal {
|
|
|
143
143
|
switch command.command {
|
|
144
144
|
case .snapshot, .screenshot:
|
|
145
145
|
return false
|
|
146
|
-
case .tap, .mouseClick, .
|
|
147
|
-
.remotePress, .type, .swipe, .findText, .querySelector, .readText, .back,
|
|
148
|
-
.backSystem, .home, .rotate, .appSwitcher, .keyboardDismiss, .keyboardReturn,
|
|
149
|
-
.
|
|
150
|
-
.shutdown:
|
|
146
|
+
case .tap, .mouseClick, .longPress, .drag,
|
|
147
|
+
.remotePress, .type, .swipe, .scroll, .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
151
|
return true
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -165,6 +165,46 @@ extension RunnerTests {
|
|
|
165
165
|
XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.notAccepted.rawValue)
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
func testStampingCurrentUptimePreservesPayload() {
|
|
169
|
+
let stamped = Response(ok: true, data: DataPayload(message: "recording started"))
|
|
170
|
+
.stampingCurrentUptimeMs(123.5)
|
|
171
|
+
|
|
172
|
+
XCTAssertEqual(stamped.ok, true)
|
|
173
|
+
XCTAssertEqual(stamped.data?.message, "recording started")
|
|
174
|
+
XCTAssertEqual(stamped.data?.currentUptimeMs, 123.5)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
func testStampingCurrentUptimeCreatesPayloadWhenNil() {
|
|
178
|
+
let stamped = Response(ok: true).stampingCurrentUptimeMs(456.0)
|
|
179
|
+
|
|
180
|
+
XCTAssertEqual(stamped.ok, true)
|
|
181
|
+
XCTAssertEqual(stamped.data?.currentUptimeMs, 456.0)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
func testStampingCurrentUptimeSkipsErrorResponses() {
|
|
185
|
+
let response = Response(ok: false, error: ErrorPayload(message: "boom"))
|
|
186
|
+
let stamped = response.stampingCurrentUptimeMs(789.0)
|
|
187
|
+
|
|
188
|
+
XCTAssertEqual(stamped.ok, false)
|
|
189
|
+
XCTAssertNil(stamped.data)
|
|
190
|
+
XCTAssertEqual(stamped.error?.message, "boom")
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
func testJournalStoredResponseStaysUnstamped() throws {
|
|
194
|
+
let journal = RunnerCommandJournal()
|
|
195
|
+
let recordStart = runnerJournalCommand("recordStart", id: "record-start-anchor")
|
|
196
|
+
|
|
197
|
+
journal.accept(command: recordStart)
|
|
198
|
+
journal.finish(
|
|
199
|
+
command: recordStart,
|
|
200
|
+
response: Response(ok: true, data: DataPayload(message: "recording started"))
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
let status = journal.status(commandId: "record-start-anchor")
|
|
204
|
+
let responseJson = try XCTUnwrap(status.lifecycleResponseJson)
|
|
205
|
+
XCTAssertFalse(responseJson.contains("currentUptimeMs"))
|
|
206
|
+
}
|
|
207
|
+
|
|
168
208
|
func testCommandJournalRetentionPolicy() throws {
|
|
169
209
|
let journal = RunnerCommandJournal()
|
|
170
210
|
|
|
@@ -219,6 +259,38 @@ extension RunnerTests {
|
|
|
219
259
|
XCTAssertEqual(screenshotStatus.lifecycleResponseOk, true)
|
|
220
260
|
XCTAssertNil(screenshotStatus.lifecycleResponseJson)
|
|
221
261
|
|
|
262
|
+
let scroll = runnerJournalCommand("scroll", id: "scroll-drag")
|
|
263
|
+
journal.accept(command: scroll)
|
|
264
|
+
journal.finish(
|
|
265
|
+
command: scroll,
|
|
266
|
+
response: Response(
|
|
267
|
+
ok: true,
|
|
268
|
+
data: DataPayload(
|
|
269
|
+
message: "scrolled",
|
|
270
|
+
gestureStartUptimeMs: 1,
|
|
271
|
+
gestureEndUptimeMs: 2,
|
|
272
|
+
x: 155,
|
|
273
|
+
y: 420,
|
|
274
|
+
x2: 155,
|
|
275
|
+
y2: 301,
|
|
276
|
+
referenceWidth: 300,
|
|
277
|
+
referenceHeight: 600
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
let scrollStatus = journal.status(commandId: "scroll-drag")
|
|
283
|
+
XCTAssertEqual(scrollStatus.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
|
|
284
|
+
XCTAssertEqual(scrollStatus.lifecycleResponseOk, true)
|
|
285
|
+
XCTAssertNotNil(scrollStatus.lifecycleResponseJson)
|
|
286
|
+
let scrollResponse = try decodeRunnerJournalResponse(scrollStatus.lifecycleResponseJson)
|
|
287
|
+
XCTAssertEqual(scrollResponse.data?.x, 155)
|
|
288
|
+
XCTAssertEqual(scrollResponse.data?.y, 420)
|
|
289
|
+
XCTAssertEqual(scrollResponse.data?.x2, 155)
|
|
290
|
+
XCTAssertEqual(scrollResponse.data?.y2, 301)
|
|
291
|
+
XCTAssertEqual(scrollResponse.data?.referenceWidth, 300)
|
|
292
|
+
XCTAssertEqual(scrollResponse.data?.referenceHeight, 600)
|
|
293
|
+
|
|
222
294
|
let largeRead = runnerJournalCommand("readText", id: "large-read")
|
|
223
295
|
journal.accept(command: largeRead)
|
|
224
296
|
journal.finish(
|
|
@@ -262,6 +334,82 @@ extension RunnerTests {
|
|
|
262
334
|
XCTAssertEqual(status.lifecycleErrorHint, hint)
|
|
263
335
|
}
|
|
264
336
|
|
|
337
|
+
func testCommandJournalRetainsCompletedSequenceResults() throws {
|
|
338
|
+
let journal = RunnerCommandJournal()
|
|
339
|
+
let sequence = runnerJournalCommand("sequence", id: "sequence-completed")
|
|
340
|
+
let results = (0..<20).map { _ in
|
|
341
|
+
SequenceStepResult(
|
|
342
|
+
ok: true,
|
|
343
|
+
kind: "tap",
|
|
344
|
+
errorCode: nil,
|
|
345
|
+
errorMessage: nil,
|
|
346
|
+
gestureStartUptimeMs: 100,
|
|
347
|
+
gestureEndUptimeMs: 120
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
journal.accept(command: sequence)
|
|
352
|
+
journal.finish(
|
|
353
|
+
command: sequence,
|
|
354
|
+
response: Response(
|
|
355
|
+
ok: true,
|
|
356
|
+
data: DataPayload(
|
|
357
|
+
message: "sequence",
|
|
358
|
+
completedSteps: 20,
|
|
359
|
+
failedStepIndex: nil,
|
|
360
|
+
sequenceResults: results
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
let status = journal.status(commandId: "sequence-completed")
|
|
366
|
+
XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
|
|
367
|
+
XCTAssertEqual(status.lifecycleResponseOk, true)
|
|
368
|
+
let json = try XCTUnwrap(status.lifecycleResponseJson)
|
|
369
|
+
// Worst-case 20-step response must stay under the 16KB journal retention cap.
|
|
370
|
+
XCTAssertLessThan(json.utf8.count, 16 * 1024)
|
|
371
|
+
let decoded = try decodeRunnerJournalResponse(status.lifecycleResponseJson)
|
|
372
|
+
XCTAssertEqual(decoded.data?.completedSteps, 20)
|
|
373
|
+
XCTAssertEqual(decoded.data?.sequenceResults?.count, 20)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
func testCommandJournalRetainsFailedSequenceResults() throws {
|
|
377
|
+
let journal = RunnerCommandJournal()
|
|
378
|
+
let sequence = runnerJournalCommand("sequence", id: "sequence-failed")
|
|
379
|
+
let longError = String(repeating: "z", count: 200)
|
|
380
|
+
let results: [SequenceStepResult] = [
|
|
381
|
+
SequenceStepResult(ok: true, kind: "tap", errorCode: nil, errorMessage: nil,
|
|
382
|
+
gestureStartUptimeMs: 100, gestureEndUptimeMs: 120),
|
|
383
|
+
SequenceStepResult(ok: true, kind: "tap", errorCode: nil, errorMessage: nil,
|
|
384
|
+
gestureStartUptimeMs: 130, gestureEndUptimeMs: 150),
|
|
385
|
+
SequenceStepResult(ok: false, kind: "drag", errorCode: "UNSUPPORTED_OPERATION",
|
|
386
|
+
errorMessage: longError, gestureStartUptimeMs: 160, gestureEndUptimeMs: 180),
|
|
387
|
+
]
|
|
388
|
+
|
|
389
|
+
journal.accept(command: sequence)
|
|
390
|
+
journal.finish(
|
|
391
|
+
command: sequence,
|
|
392
|
+
response: Response(
|
|
393
|
+
ok: true,
|
|
394
|
+
data: DataPayload(
|
|
395
|
+
message: "sequence",
|
|
396
|
+
completedSteps: 2,
|
|
397
|
+
failedStepIndex: 2,
|
|
398
|
+
sequenceResults: results
|
|
399
|
+
)
|
|
400
|
+
)
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
let status = journal.status(commandId: "sequence-failed")
|
|
404
|
+
XCTAssertEqual(status.lifecycleState, RunnerCommandLifecycleState.completed.rawValue)
|
|
405
|
+
let decoded = try decodeRunnerJournalResponse(status.lifecycleResponseJson)
|
|
406
|
+
XCTAssertEqual(decoded.data?.completedSteps, 2)
|
|
407
|
+
XCTAssertEqual(decoded.data?.failedStepIndex, 2)
|
|
408
|
+
XCTAssertEqual(decoded.data?.sequenceResults?.count, 3)
|
|
409
|
+
XCTAssertEqual(decoded.data?.sequenceResults?[2].ok, false)
|
|
410
|
+
XCTAssertEqual(decoded.data?.sequenceResults?[2].errorCode, "UNSUPPORTED_OPERATION")
|
|
411
|
+
}
|
|
412
|
+
|
|
265
413
|
private func runnerJournalCommand(_ command: String, id: String) -> Command {
|
|
266
414
|
let json = #"{"command":"\#(command)","commandId":"\#(id)"}"#
|
|
267
415
|
return try! JSONDecoder().decode(Command.self, from: Data(json.utf8))
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
var hasContent: Bool {
|
|
11
|
+
return !label.isEmpty || !identifier.isEmpty || valueText != nil
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func matchesScope(_ scope: String) -> Bool {
|
|
15
|
+
let haystack = [label, identifier, valueText ?? ""].joined(separator: "\n")
|
|
16
|
+
return haystack.localizedCaseInsensitiveContains(scope)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct FlatSnapshotFilterDecision {
|
|
21
|
+
let include: Bool
|
|
22
|
+
let insideMatchedScope: Bool
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extension RunnerTests {
|
|
26
|
+
func flatSnapshotFilterDecision(
|
|
27
|
+
_ node: FlatSnapshotFilterNode,
|
|
28
|
+
options: SnapshotOptions,
|
|
29
|
+
insideMatchedScope: Bool
|
|
30
|
+
) -> FlatSnapshotFilterDecision {
|
|
31
|
+
let scope = options.scope?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
32
|
+
let scopeActive = scope?.isEmpty == false
|
|
33
|
+
let matchesScope: Bool
|
|
34
|
+
if scopeActive, let scope {
|
|
35
|
+
matchesScope = node.matchesScope(scope)
|
|
36
|
+
} else {
|
|
37
|
+
matchesScope = false
|
|
38
|
+
}
|
|
39
|
+
let nowInsideScope = insideMatchedScope || matchesScope
|
|
40
|
+
|
|
41
|
+
let include: Bool
|
|
42
|
+
if node.isRoot {
|
|
43
|
+
include = true
|
|
44
|
+
} else if scopeActive && !nowInsideScope {
|
|
45
|
+
include = false
|
|
46
|
+
} else if options.interactiveOnly && !node.visible {
|
|
47
|
+
include = false
|
|
48
|
+
} else {
|
|
49
|
+
include = true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return FlatSnapshotFilterDecision(include: include, insideMatchedScope: nowInsideScope)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func privateAXInteractiveCandidate(rawElementType: Int) -> Bool {
|
|
56
|
+
guard let type = flatSnapshotElementType(rawElementType: rawElementType) else {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
return interactiveTypes.contains(type) || Self.scrollContainerTypes.contains(type)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
func flatSnapshotElementType(rawElementType: Int) -> XCUIElement.ElementType? {
|
|
63
|
+
guard let raw = UInt(exactly: rawElementType),
|
|
64
|
+
let type = XCUIElement.ElementType(rawValue: raw)
|
|
65
|
+
else {
|
|
66
|
+
return nil
|
|
67
|
+
}
|
|
68
|
+
return type
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func testFlatSnapshotFilterDecisionMatrixCoversOptions() {
|
|
72
|
+
let visibleContent = FlatSnapshotFilterNode(
|
|
73
|
+
isRoot: false,
|
|
74
|
+
label: "Welcome back",
|
|
75
|
+
identifier: "",
|
|
76
|
+
valueText: nil,
|
|
77
|
+
visible: true
|
|
78
|
+
)
|
|
79
|
+
let hiddenInteractive = FlatSnapshotFilterNode(
|
|
80
|
+
isRoot: false,
|
|
81
|
+
label: "Hidden menu",
|
|
82
|
+
identifier: "",
|
|
83
|
+
valueText: nil,
|
|
84
|
+
visible: false
|
|
85
|
+
)
|
|
86
|
+
let decorative = FlatSnapshotFilterNode(
|
|
87
|
+
isRoot: false,
|
|
88
|
+
label: "",
|
|
89
|
+
identifier: "",
|
|
90
|
+
valueText: nil,
|
|
91
|
+
visible: true
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
XCTAssertTrue(
|
|
95
|
+
flatSnapshotFilterDecision(
|
|
96
|
+
visibleContent,
|
|
97
|
+
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
|
|
98
|
+
insideMatchedScope: false
|
|
99
|
+
).include
|
|
100
|
+
)
|
|
101
|
+
XCTAssertFalse(
|
|
102
|
+
flatSnapshotFilterDecision(
|
|
103
|
+
hiddenInteractive,
|
|
104
|
+
options: SnapshotOptions(interactiveOnly: true, depth: nil, scope: nil, raw: false),
|
|
105
|
+
insideMatchedScope: false
|
|
106
|
+
).include
|
|
107
|
+
)
|
|
108
|
+
XCTAssertTrue(
|
|
109
|
+
flatSnapshotFilterDecision(
|
|
110
|
+
decorative,
|
|
111
|
+
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
|
|
112
|
+
insideMatchedScope: false
|
|
113
|
+
).include
|
|
114
|
+
)
|
|
115
|
+
XCTAssertTrue(
|
|
116
|
+
flatSnapshotFilterDecision(
|
|
117
|
+
decorative,
|
|
118
|
+
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
|
|
119
|
+
insideMatchedScope: false
|
|
120
|
+
).include
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func testFlatSnapshotFilterDecisionCarriesSubtreeScopeState() {
|
|
125
|
+
let scopeRoot = FlatSnapshotFilterNode(
|
|
126
|
+
isRoot: false,
|
|
127
|
+
label: "",
|
|
128
|
+
identifier: "homeScreen",
|
|
129
|
+
valueText: nil,
|
|
130
|
+
visible: true
|
|
131
|
+
)
|
|
132
|
+
let unmatchedDescendant = FlatSnapshotFilterNode(
|
|
133
|
+
isRoot: false,
|
|
134
|
+
label: "Post body without the scope text",
|
|
135
|
+
identifier: "",
|
|
136
|
+
valueText: nil,
|
|
137
|
+
visible: true
|
|
138
|
+
)
|
|
139
|
+
let options = SnapshotOptions(interactiveOnly: false, depth: nil, scope: "homeScreen", raw: false)
|
|
140
|
+
|
|
141
|
+
let rootDecision = flatSnapshotFilterDecision(
|
|
142
|
+
scopeRoot,
|
|
143
|
+
options: options,
|
|
144
|
+
insideMatchedScope: false
|
|
145
|
+
)
|
|
146
|
+
XCTAssertTrue(rootDecision.include)
|
|
147
|
+
XCTAssertTrue(rootDecision.insideMatchedScope)
|
|
148
|
+
|
|
149
|
+
XCTAssertTrue(
|
|
150
|
+
flatSnapshotFilterDecision(
|
|
151
|
+
unmatchedDescendant,
|
|
152
|
+
options: options,
|
|
153
|
+
insideMatchedScope: rootDecision.insideMatchedScope
|
|
154
|
+
).include
|
|
155
|
+
)
|
|
156
|
+
XCTAssertFalse(
|
|
157
|
+
flatSnapshotFilterDecision(
|
|
158
|
+
unmatchedDescendant,
|
|
159
|
+
options: options,
|
|
160
|
+
insideMatchedScope: false
|
|
161
|
+
).include
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
func testPrivateAXInteractiveCandidatesPreserveBackendInputs() {
|
|
166
|
+
XCTAssertTrue(
|
|
167
|
+
privateAXInteractiveCandidate(rawElementType: Int(XCUIElement.ElementType.scrollView.rawValue)),
|
|
168
|
+
"private AX marks scroll containers as interactive candidates"
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -841,16 +841,6 @@ extension RunnerTests {
|
|
|
841
841
|
#endif
|
|
842
842
|
}
|
|
843
843
|
|
|
844
|
-
func runSeries(count: Int, pauseMs: Double, operation: (Int) -> Void) {
|
|
845
|
-
let total = max(count, 1)
|
|
846
|
-
let pause = max(pauseMs, 0)
|
|
847
|
-
for idx in 0..<total {
|
|
848
|
-
operation(idx)
|
|
849
|
-
if idx < total - 1 && pause > 0 {
|
|
850
|
-
sleepFor(pause / 1000.0)
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
844
|
|
|
855
845
|
func swipe(app: XCUIApplication, direction: String) -> DragVisualizationFrame? {
|
|
856
846
|
if performTvRemoteSwipeIfAvailable(direction: direction) {
|
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
enum CommandType: String, Codable {
|
|
4
4
|
case tap
|
|
5
5
|
case mouseClick
|
|
6
|
-
case tapSeries
|
|
7
6
|
case longPress
|
|
8
|
-
case interactionFrame
|
|
9
7
|
case drag
|
|
10
|
-
case dragSeries
|
|
11
8
|
case remotePress
|
|
12
9
|
case type
|
|
13
10
|
case swipe
|
|
11
|
+
case scroll
|
|
14
12
|
case findText
|
|
15
13
|
case querySelector
|
|
16
14
|
case readText
|
|
@@ -26,6 +24,7 @@ enum CommandType: String, Codable {
|
|
|
26
24
|
case keyboardReturn
|
|
27
25
|
case alert
|
|
28
26
|
case pinch
|
|
27
|
+
case sequence
|
|
29
28
|
case rotateGesture
|
|
30
29
|
case transformGesture
|
|
31
30
|
case recordStart
|
|
@@ -68,16 +67,16 @@ extension CommandType {
|
|
|
68
67
|
var traits: CommandTraits {
|
|
69
68
|
switch self {
|
|
70
69
|
// Interaction commands: require the foreground-guard + stabilization preflight.
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
case .tap, .
|
|
70
|
+
// keyboardReturn is the sibling of keyboardDismiss (missing from the historical switch —
|
|
71
|
+
// drift the table now prevents). .scroll is the fused frame-resolve + drag scroll; same
|
|
72
|
+
// classification as .drag. .sequence is the fused multi-step gesture batch.
|
|
73
|
+
case .tap, .longPress, .drag, .remotePress, .type, .swipe, .scroll,
|
|
75
74
|
.back, .backInApp, .backSystem, .rotate, .appSwitcher,
|
|
76
|
-
.keyboardDismiss, .keyboardReturn, .pinch, .rotateGesture, .transformGesture:
|
|
75
|
+
.keyboardDismiss, .keyboardReturn, .pinch, .sequence, .rotateGesture, .transformGesture:
|
|
77
76
|
return CommandTraits(isInteraction: true, readOnly: .never, isLifecycle: false)
|
|
78
77
|
|
|
79
78
|
// Read-only reads: eligible for the session-invalidating retry.
|
|
80
|
-
case .
|
|
79
|
+
case .findText, .readText, .snapshot:
|
|
81
80
|
return CommandTraits(isInteraction: false, readOnly: .always, isLifecycle: false)
|
|
82
81
|
|
|
83
82
|
// Screenshot is both a read and a runner-lifecycle command (skips app-activation preflight).
|
|
@@ -123,17 +122,14 @@ struct Command: Codable {
|
|
|
123
122
|
let y: Double?
|
|
124
123
|
let button: String?
|
|
125
124
|
let remoteButton: String?
|
|
126
|
-
let count: Double?
|
|
127
|
-
let intervalMs: Double?
|
|
128
|
-
let doubleTap: Bool?
|
|
129
|
-
let pauseMs: Double?
|
|
130
|
-
let pattern: String?
|
|
131
125
|
let x2: Double?
|
|
132
126
|
let y2: Double?
|
|
133
127
|
let dx: Double?
|
|
134
128
|
let dy: Double?
|
|
135
129
|
let durationMs: Double?
|
|
136
130
|
let direction: String?
|
|
131
|
+
let amount: Double?
|
|
132
|
+
let pixels: Double?
|
|
137
133
|
let orientation: String?
|
|
138
134
|
let scale: Double?
|
|
139
135
|
let degrees: Double?
|
|
@@ -142,12 +138,39 @@ struct Command: Codable {
|
|
|
142
138
|
let fps: Int?
|
|
143
139
|
let quality: Int?
|
|
144
140
|
let interactiveOnly: Bool?
|
|
145
|
-
let compact: Bool?
|
|
146
141
|
let depth: Int?
|
|
147
142
|
let scope: String?
|
|
148
143
|
let raw: Bool?
|
|
149
144
|
let fullscreen: Bool?
|
|
150
145
|
let synthesized: Bool?
|
|
146
|
+
let steps: [SequenceStep]?
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// One allowlisted coordinate gesture step inside a fused `sequence` command.
|
|
150
|
+
/// `kind` is decoded as a raw String (not an enum) so the runner can return a clear
|
|
151
|
+
/// INVALID_ARGS for an unknown kind instead of a generic decode failure.
|
|
152
|
+
struct SequenceStep: Codable {
|
|
153
|
+
let kind: String
|
|
154
|
+
let x: Double?
|
|
155
|
+
let y: Double?
|
|
156
|
+
let x2: Double?
|
|
157
|
+
let y2: Double?
|
|
158
|
+
let durationMs: Double?
|
|
159
|
+
let pauseMs: Double?
|
|
160
|
+
/// For `tap` steps on iOS non-tv: use the synthesized HID tap fast path (synthesizedTapAt)
|
|
161
|
+
/// instead of the drag-based XCUICoordinate tapAt, matching the individual `tap` command.
|
|
162
|
+
let synthesized: Bool?
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Per-step result for a `sequence` response. `ok:false` carries the failing step's
|
|
166
|
+
/// errorCode/errorMessage; execution stops at the first failed step.
|
|
167
|
+
struct SequenceStepResult: Codable {
|
|
168
|
+
let ok: Bool
|
|
169
|
+
let kind: String
|
|
170
|
+
let errorCode: String?
|
|
171
|
+
let errorMessage: String?
|
|
172
|
+
let gestureStartUptimeMs: Double?
|
|
173
|
+
let gestureEndUptimeMs: Double?
|
|
151
174
|
}
|
|
152
175
|
|
|
153
176
|
struct Response: Codable {
|
|
@@ -162,6 +185,18 @@ struct Response: Codable {
|
|
|
162
185
|
}
|
|
163
186
|
}
|
|
164
187
|
|
|
188
|
+
extension Response {
|
|
189
|
+
// The daemon pairs this gesture-clock anchor with its own receipt time to map
|
|
190
|
+
// gesture uptimes onto wall-clock for the recording touch overlay. Error responses
|
|
191
|
+
// carry no anchor so the daemon falls back instead of pairing a stale value.
|
|
192
|
+
func stampingCurrentUptimeMs(_ value: Double) -> Response {
|
|
193
|
+
guard ok else { return self }
|
|
194
|
+
var payload = data ?? DataPayload()
|
|
195
|
+
payload.currentUptimeMs = value
|
|
196
|
+
return Response(ok: ok, data: payload, error: error)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
165
200
|
struct DataPayload: Codable {
|
|
166
201
|
let message: String?
|
|
167
202
|
let text: String?
|
|
@@ -169,6 +204,7 @@ struct DataPayload: Codable {
|
|
|
169
204
|
let items: [String]?
|
|
170
205
|
let nodes: [SnapshotNode]?
|
|
171
206
|
let truncated: Bool?
|
|
207
|
+
let snapshotQuality: SnapshotQuality?
|
|
172
208
|
let gestureStartUptimeMs: Double?
|
|
173
209
|
let gestureEndUptimeMs: Double?
|
|
174
210
|
let x: Double?
|
|
@@ -177,7 +213,7 @@ struct DataPayload: Codable {
|
|
|
177
213
|
let y2: Double?
|
|
178
214
|
let referenceWidth: Double?
|
|
179
215
|
let referenceHeight: Double?
|
|
180
|
-
|
|
216
|
+
var currentUptimeMs: Double?
|
|
181
217
|
let commandId: String?
|
|
182
218
|
let lifecycleState: String?
|
|
183
219
|
let lifecycleCommand: String?
|
|
@@ -195,6 +231,9 @@ struct DataPayload: Codable {
|
|
|
195
231
|
let gestureFallbackHint: String?
|
|
196
232
|
let runnerFatal: Bool?
|
|
197
233
|
let runnerFatalReason: String?
|
|
234
|
+
let completedSteps: Int?
|
|
235
|
+
let failedStepIndex: Int?
|
|
236
|
+
let sequenceResults: [SequenceStepResult]?
|
|
198
237
|
|
|
199
238
|
init(
|
|
200
239
|
message: String? = nil,
|
|
@@ -203,6 +242,7 @@ struct DataPayload: Codable {
|
|
|
203
242
|
items: [String]? = nil,
|
|
204
243
|
nodes: [SnapshotNode]? = nil,
|
|
205
244
|
truncated: Bool? = nil,
|
|
245
|
+
snapshotQuality: SnapshotQuality? = nil,
|
|
206
246
|
gestureStartUptimeMs: Double? = nil,
|
|
207
247
|
gestureEndUptimeMs: Double? = nil,
|
|
208
248
|
x: Double? = nil,
|
|
@@ -228,7 +268,10 @@ struct DataPayload: Codable {
|
|
|
228
268
|
gestureFallbackMessage: String? = nil,
|
|
229
269
|
gestureFallbackHint: String? = nil,
|
|
230
270
|
runnerFatal: Bool? = nil,
|
|
231
|
-
runnerFatalReason: String? = nil
|
|
271
|
+
runnerFatalReason: String? = nil,
|
|
272
|
+
completedSteps: Int? = nil,
|
|
273
|
+
failedStepIndex: Int? = nil,
|
|
274
|
+
sequenceResults: [SequenceStepResult]? = nil
|
|
232
275
|
) {
|
|
233
276
|
self.message = message
|
|
234
277
|
self.text = text
|
|
@@ -236,6 +279,7 @@ struct DataPayload: Codable {
|
|
|
236
279
|
self.items = items
|
|
237
280
|
self.nodes = nodes
|
|
238
281
|
self.truncated = truncated
|
|
282
|
+
self.snapshotQuality = snapshotQuality
|
|
239
283
|
self.gestureStartUptimeMs = gestureStartUptimeMs
|
|
240
284
|
self.gestureEndUptimeMs = gestureEndUptimeMs
|
|
241
285
|
self.x = x
|
|
@@ -262,6 +306,9 @@ struct DataPayload: Codable {
|
|
|
262
306
|
self.gestureFallbackHint = gestureFallbackHint
|
|
263
307
|
self.runnerFatal = runnerFatal
|
|
264
308
|
self.runnerFatalReason = runnerFatalReason
|
|
309
|
+
self.completedSteps = completedSteps
|
|
310
|
+
self.failedStepIndex = failedStepIndex
|
|
311
|
+
self.sequenceResults = sequenceResults
|
|
265
312
|
}
|
|
266
313
|
}
|
|
267
314
|
|
|
@@ -303,7 +350,6 @@ struct SnapshotNode: Codable {
|
|
|
303
350
|
|
|
304
351
|
struct SnapshotOptions {
|
|
305
352
|
let interactiveOnly: Bool
|
|
306
|
-
let compact: Bool
|
|
307
353
|
let depth: Int?
|
|
308
354
|
let scope: String?
|
|
309
355
|
let raw: Bool
|