agent-device 0.16.9 → 0.16.11
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 +1 -0
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.16.9.apk → agent-device-android-multitouch-helper-0.16.11.apk} +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.11.apk.sha256 +1 -0
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.16.9.manifest.json → agent-device-android-multitouch-helper-0.16.11.manifest.json} +4 -4
- package/android-snapshot-helper/README.md +6 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.11.apk +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.11.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.16.9.manifest.json → agent-device-android-snapshot-helper-0.16.11.manifest.json} +6 -6
- package/dist/src/1352.js +1 -1
- package/dist/src/221.js +6 -6
- package/dist/src/2415.js +27 -27
- package/dist/src/2805.js +1 -1
- package/dist/src/4778.js +1 -0
- package/dist/src/5792.js +1 -1
- package/dist/src/6085.js +1 -1
- package/dist/src/6232.js +1 -1
- package/dist/src/8699.js +1 -1
- package/dist/src/9238.js +4 -0
- package/dist/src/9533.js +1 -1
- package/dist/src/9542.js +3 -3
- package/dist/src/apple.js +1 -1
- package/dist/src/apps.js +2 -2
- package/dist/src/args.js +54 -25
- package/dist/src/batch.d.ts +1 -0
- package/dist/src/cli.js +19 -19
- package/dist/src/command-metadata.js +1 -1
- package/dist/src/command-surface.js +1 -1
- package/dist/src/contracts.d.ts +1 -0
- package/dist/src/contracts.js +1 -1
- package/dist/src/generic.js +10 -10
- package/dist/src/index.d.ts +9 -0
- package/dist/src/input-actions.js +1 -1
- package/dist/src/record-trace.js +3 -3
- package/dist/src/session.js +9 -9
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +7 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +109 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +282 -226
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandJournal.swift +282 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Exceptions.swift +29 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +44 -34
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +41 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +2 -20
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +10 -50
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +3 -23
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Transport.swift +64 -22
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +7 -4
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/dogfood/SKILL.md +1 -1
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.9.apk.sha256 +0 -1
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.9.apk +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.9.apk.sha256 +0 -1
- package/dist/src/2842.js +0 -1
- package/dist/src/8114.js +0 -4
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -13,6 +13,10 @@ extension RunnerTests {
|
|
|
13
13
|
return (gestureStartUptimeMs, currentUptimeMs())
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
private func synthesizedSwipeFallbackHoldDuration(durationMs: Double) -> TimeInterval {
|
|
17
|
+
min(max((durationMs / 5.0) / 1000.0, 0.016), 0.120)
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
func unsupportedResponse(for outcome: RunnerInteractionOutcome) -> Response? {
|
|
17
21
|
switch outcome {
|
|
18
22
|
case .performed:
|
|
@@ -25,7 +29,163 @@ extension RunnerTests {
|
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
/// Optional visualization frame returned with a gesture response.
|
|
33
|
+
enum GestureFrame {
|
|
34
|
+
case none
|
|
35
|
+
case touch(TouchVisualizationFrame?)
|
|
36
|
+
case drag(DragVisualizationFrame)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
struct GestureFallback {
|
|
40
|
+
let strategy: String
|
|
41
|
+
let message: String
|
|
42
|
+
let hint: String?
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private func gestureFallback(strategy: String, from outcome: RunnerInteractionOutcome) -> GestureFallback? {
|
|
46
|
+
switch outcome {
|
|
47
|
+
case .performed:
|
|
48
|
+
return nil
|
|
49
|
+
case .unsupported(let message, let hint):
|
|
50
|
+
return GestureFallback(strategy: strategy, message: message, hint: hint)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private func performDragSeries(
|
|
55
|
+
count: Int,
|
|
56
|
+
pauseMs: Double,
|
|
57
|
+
pattern: String,
|
|
58
|
+
points: DragPoints,
|
|
59
|
+
_ drag: (_ x: Double, _ y: Double, _ x2: Double, _ y2: Double) -> RunnerInteractionOutcome
|
|
60
|
+
) -> RunnerInteractionOutcome {
|
|
61
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
62
|
+
runSeries(count: count, pauseMs: pauseMs) { idx in
|
|
63
|
+
guard case .performed = outcome else {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
let reverse = pattern == "ping-pong" && (idx % 2 == 1)
|
|
67
|
+
let startX = reverse ? points.x2 : points.x
|
|
68
|
+
let startY = reverse ? points.y2 : points.y
|
|
69
|
+
let endX = reverse ? points.x : points.x2
|
|
70
|
+
let endY = reverse ? points.y : points.y2
|
|
71
|
+
outcome = drag(startX, startY, endX, endY)
|
|
72
|
+
}
|
|
73
|
+
return outcome
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Runs a gesture action with uniform timing capture. Touch gestures pass `idleTimeout: true`
|
|
77
|
+
/// (the default) to run inside the scroll idle-timeout + quiescence-skip wrapper; synthesis
|
|
78
|
+
/// gestures (pinch/rotate/transform) pass `false` because RunnerSynthesizedGesture governs its
|
|
79
|
+
/// own timing. Returns the captured timing and the action's outcome.
|
|
80
|
+
///
|
|
81
|
+
/// NOTE: a new SYNTHESIS gesture must pass `idleTimeout: false` — the default `true` would wrap
|
|
82
|
+
/// it in the scroll idle-timeout/quiescence-skip path and change its runtime behavior.
|
|
83
|
+
private func performGesture(
|
|
84
|
+
_ app: XCUIApplication,
|
|
85
|
+
idleTimeout: Bool = true,
|
|
86
|
+
_ action: () -> RunnerInteractionOutcome
|
|
87
|
+
) -> (timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double), outcome: RunnerInteractionOutcome) {
|
|
88
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
89
|
+
let timing = measureGesture {
|
|
90
|
+
if idleTimeout {
|
|
91
|
+
withTemporaryScrollIdleTimeoutIfSupported(app) { outcome = action() }
|
|
92
|
+
} else {
|
|
93
|
+
outcome = action()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return (timing, outcome)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Single factory for the success payload every gesture returns (message + gesture timing +
|
|
100
|
+
/// an optional touch/drag visualization frame), so the field shape lives in one place.
|
|
101
|
+
private func gestureResponse(
|
|
102
|
+
message: String,
|
|
103
|
+
timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double),
|
|
104
|
+
frame: GestureFrame = .none,
|
|
105
|
+
fallback: GestureFallback? = nil
|
|
106
|
+
) -> Response {
|
|
107
|
+
let data: DataPayload
|
|
108
|
+
switch frame {
|
|
109
|
+
case .none:
|
|
110
|
+
data = DataPayload(
|
|
111
|
+
message: message,
|
|
112
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
113
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
114
|
+
gestureFallback: fallback?.strategy,
|
|
115
|
+
gestureFallbackMessage: fallback?.message,
|
|
116
|
+
gestureFallbackHint: fallback?.hint
|
|
117
|
+
)
|
|
118
|
+
case .touch(let f):
|
|
119
|
+
data = DataPayload(
|
|
120
|
+
message: message,
|
|
121
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
122
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
123
|
+
x: f?.x,
|
|
124
|
+
y: f?.y,
|
|
125
|
+
referenceWidth: f?.referenceWidth,
|
|
126
|
+
referenceHeight: f?.referenceHeight,
|
|
127
|
+
gestureFallback: fallback?.strategy,
|
|
128
|
+
gestureFallbackMessage: fallback?.message,
|
|
129
|
+
gestureFallbackHint: fallback?.hint
|
|
130
|
+
)
|
|
131
|
+
case .drag(let f):
|
|
132
|
+
data = DataPayload(
|
|
133
|
+
message: message,
|
|
134
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
135
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
136
|
+
x: f.x,
|
|
137
|
+
y: f.y,
|
|
138
|
+
x2: f.x2,
|
|
139
|
+
y2: f.y2,
|
|
140
|
+
referenceWidth: f.referenceWidth,
|
|
141
|
+
referenceHeight: f.referenceHeight,
|
|
142
|
+
gestureFallback: fallback?.strategy,
|
|
143
|
+
gestureFallbackMessage: fallback?.message,
|
|
144
|
+
gestureFallbackHint: fallback?.hint
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
return Response(ok: true, data: data)
|
|
148
|
+
}
|
|
149
|
+
|
|
28
150
|
func execute(command: Command) throws -> Response {
|
|
151
|
+
if command.command == .status {
|
|
152
|
+
return executeStatus(command: command)
|
|
153
|
+
}
|
|
154
|
+
commandJournal.accept(command: command)
|
|
155
|
+
return try executeAccepted(command: command)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
func executeAccepted(command: Command) throws -> Response {
|
|
159
|
+
commandJournal.start(command: command)
|
|
160
|
+
do {
|
|
161
|
+
let response = try executeDispatched(command: command)
|
|
162
|
+
commandJournal.finish(command: command, response: response)
|
|
163
|
+
return response
|
|
164
|
+
} catch {
|
|
165
|
+
commandJournal.fail(command: command, error: error)
|
|
166
|
+
throw error
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func executeStatus(command: Command) -> Response {
|
|
171
|
+
guard
|
|
172
|
+
let statusCommandId = command.statusCommandId?
|
|
173
|
+
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
174
|
+
!statusCommandId.isEmpty
|
|
175
|
+
else {
|
|
176
|
+
return Response(
|
|
177
|
+
ok: false,
|
|
178
|
+
error: ErrorPayload(
|
|
179
|
+
code: "INVALID_ARGS",
|
|
180
|
+
message: "status requires statusCommandId",
|
|
181
|
+
hint: "Set statusCommandId to the commandId of the runner command to inspect."
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
return Response(ok: true, data: commandJournal.status(commandId: statusCommandId))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func executeDispatched(command: Command) throws -> Response {
|
|
29
189
|
if Thread.isMainThread {
|
|
30
190
|
return try executeOnMainSafely(command: command)
|
|
31
191
|
}
|
|
@@ -183,6 +343,8 @@ extension RunnerTests {
|
|
|
183
343
|
}
|
|
184
344
|
|
|
185
345
|
switch command.command {
|
|
346
|
+
case .status:
|
|
347
|
+
return executeStatus(command: command)
|
|
186
348
|
case .shutdown:
|
|
187
349
|
stopRecordingIfNeeded()
|
|
188
350
|
return Response(ok: true, data: DataPayload(message: "shutdown"))
|
|
@@ -266,83 +428,46 @@ extension RunnerTests {
|
|
|
266
428
|
let touchFrame = frame.isEmpty
|
|
267
429
|
? nil
|
|
268
430
|
: resolvedTouchVisualizationFrame(app: activeApp, x: frame.midX, y: frame.midY)
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
// resolved bounds. Keep this behind the explicit replay-only flag.
|
|
276
|
-
outcome = tapAt(app: activeApp, x: frame.midX, y: frame.midY)
|
|
277
|
-
} else {
|
|
278
|
-
outcome = activateElement(app: activeApp, element: element, action: "tap by selector")
|
|
279
|
-
}
|
|
431
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
432
|
+
if match.usedNonHittableFallback {
|
|
433
|
+
// Maestro compatibility: RN E2E backdoor controls can be 1x1 and
|
|
434
|
+
// reported non-hittable by XCTest, while Maestro still taps their
|
|
435
|
+
// resolved bounds. Keep this behind the explicit replay-only flag.
|
|
436
|
+
return tapAt(app: activeApp, x: frame.midX, y: frame.midY)
|
|
280
437
|
}
|
|
438
|
+
return activateElement(app: activeApp, element: element, action: "tap by selector")
|
|
281
439
|
}
|
|
282
440
|
if let response = unsupportedResponse(for: outcome) {
|
|
283
441
|
return response
|
|
284
442
|
}
|
|
285
443
|
waitForTextEntryReadinessAfterTap(app: activeApp, element: element)
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
291
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
292
|
-
x: touchFrame?.x,
|
|
293
|
-
y: touchFrame?.y,
|
|
294
|
-
referenceWidth: touchFrame?.referenceWidth,
|
|
295
|
-
referenceHeight: touchFrame?.referenceHeight
|
|
296
|
-
)
|
|
444
|
+
return gestureResponse(
|
|
445
|
+
message: match.usedNonHittableFallback ? "tapped via non-hittable coordinate fallback" : "tapped",
|
|
446
|
+
timing: timing,
|
|
447
|
+
frame: .touch(touchFrame)
|
|
297
448
|
)
|
|
298
449
|
}
|
|
299
450
|
return Response(ok: false, error: ErrorPayload(code: "ELEMENT_NOT_FOUND", message: "element not found"))
|
|
300
451
|
}
|
|
301
452
|
if let text = command.text {
|
|
302
453
|
if let element = findElement(app: activeApp, text: text) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
306
|
-
outcome = activateElement(app: activeApp, element: element, action: "tap by text")
|
|
307
|
-
}
|
|
454
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
455
|
+
activateElement(app: activeApp, element: element, action: "tap by text")
|
|
308
456
|
}
|
|
309
457
|
if let response = unsupportedResponse(for: outcome) {
|
|
310
458
|
return response
|
|
311
459
|
}
|
|
312
|
-
return
|
|
313
|
-
ok: true,
|
|
314
|
-
data: DataPayload(
|
|
315
|
-
message: "tapped",
|
|
316
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
317
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
318
|
-
)
|
|
319
|
-
)
|
|
460
|
+
return gestureResponse(message: "tapped", timing: timing)
|
|
320
461
|
}
|
|
321
462
|
return Response(ok: false, error: ErrorPayload(message: "element not found"))
|
|
322
463
|
}
|
|
323
464
|
if let x = command.x, let y = command.y {
|
|
324
465
|
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
325
|
-
|
|
326
|
-
let timing = measureGesture {
|
|
327
|
-
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
328
|
-
outcome = tapAt(app: activeApp, x: x, y: y)
|
|
329
|
-
}
|
|
330
|
-
}
|
|
466
|
+
let (timing, outcome) = performGesture(activeApp) { tapAt(app: activeApp, x: x, y: y) }
|
|
331
467
|
if let response = unsupportedResponse(for: outcome) {
|
|
332
468
|
return response
|
|
333
469
|
}
|
|
334
|
-
return
|
|
335
|
-
ok: true,
|
|
336
|
-
data: DataPayload(
|
|
337
|
-
message: "tapped",
|
|
338
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
339
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
340
|
-
x: touchFrame.x,
|
|
341
|
-
y: touchFrame.y,
|
|
342
|
-
referenceWidth: touchFrame.referenceWidth,
|
|
343
|
-
referenceHeight: touchFrame.referenceHeight
|
|
344
|
-
)
|
|
345
|
-
)
|
|
470
|
+
return gestureResponse(message: "tapped", timing: timing, frame: .touch(touchFrame))
|
|
346
471
|
}
|
|
347
472
|
return Response(ok: false, error: ErrorPayload(message: "tap requires text or x/y"))
|
|
348
473
|
case .mouseClick:
|
|
@@ -351,6 +476,8 @@ extension RunnerTests {
|
|
|
351
476
|
}
|
|
352
477
|
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
353
478
|
do {
|
|
479
|
+
// mouseClick throws (it has no RunnerInteractionOutcome), so it keeps raw measureGesture
|
|
480
|
+
// and only routes the success payload through gestureResponse.
|
|
354
481
|
var clickError: Error?
|
|
355
482
|
let timing = measureGesture {
|
|
356
483
|
do {
|
|
@@ -362,18 +489,7 @@ extension RunnerTests {
|
|
|
362
489
|
if let clickError {
|
|
363
490
|
throw clickError
|
|
364
491
|
}
|
|
365
|
-
return
|
|
366
|
-
ok: true,
|
|
367
|
-
data: DataPayload(
|
|
368
|
-
message: "clicked",
|
|
369
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
370
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
371
|
-
x: touchFrame.x,
|
|
372
|
-
y: touchFrame.y,
|
|
373
|
-
referenceWidth: touchFrame.referenceWidth,
|
|
374
|
-
referenceHeight: touchFrame.referenceHeight
|
|
375
|
-
)
|
|
376
|
-
)
|
|
492
|
+
return gestureResponse(message: "clicked", timing: timing, frame: .touch(touchFrame))
|
|
377
493
|
} catch {
|
|
378
494
|
return Response(ok: false, error: ErrorPayload(message: error.localizedDescription))
|
|
379
495
|
}
|
|
@@ -386,89 +502,50 @@ extension RunnerTests {
|
|
|
386
502
|
let doubleTap = command.doubleTap ?? false
|
|
387
503
|
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
388
504
|
if doubleTap {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
outcome = doubleTapAt(app: activeApp, x: x, y: y)
|
|
395
|
-
}
|
|
505
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
506
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
507
|
+
runSeries(count: count, pauseMs: intervalMs) { _ in
|
|
508
|
+
if case .performed = outcome {
|
|
509
|
+
outcome = doubleTapAt(app: activeApp, x: x, y: y)
|
|
396
510
|
}
|
|
397
511
|
}
|
|
512
|
+
return outcome
|
|
398
513
|
}
|
|
399
514
|
if let response = unsupportedResponse(for: outcome) {
|
|
400
515
|
return response
|
|
401
516
|
}
|
|
402
|
-
return
|
|
403
|
-
ok: true,
|
|
404
|
-
data: DataPayload(
|
|
405
|
-
message: "tap series",
|
|
406
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
407
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
408
|
-
x: touchFrame.x,
|
|
409
|
-
y: touchFrame.y,
|
|
410
|
-
referenceWidth: touchFrame.referenceWidth,
|
|
411
|
-
referenceHeight: touchFrame.referenceHeight
|
|
412
|
-
)
|
|
413
|
-
)
|
|
517
|
+
return gestureResponse(message: "tap series", timing: timing, frame: .touch(touchFrame))
|
|
414
518
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
outcome = tapAt(app: activeApp, x: x, y: y)
|
|
421
|
-
}
|
|
519
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
520
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
521
|
+
runSeries(count: count, pauseMs: intervalMs) { _ in
|
|
522
|
+
if case .performed = outcome {
|
|
523
|
+
outcome = tapAt(app: activeApp, x: x, y: y)
|
|
422
524
|
}
|
|
423
525
|
}
|
|
526
|
+
return outcome
|
|
424
527
|
}
|
|
425
528
|
if let response = unsupportedResponse(for: outcome) {
|
|
426
529
|
return response
|
|
427
530
|
}
|
|
428
|
-
return
|
|
429
|
-
ok: true,
|
|
430
|
-
data: DataPayload(
|
|
431
|
-
message: "tap series",
|
|
432
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
433
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
434
|
-
x: touchFrame.x,
|
|
435
|
-
y: touchFrame.y,
|
|
436
|
-
referenceWidth: touchFrame.referenceWidth,
|
|
437
|
-
referenceHeight: touchFrame.referenceHeight
|
|
438
|
-
)
|
|
439
|
-
)
|
|
531
|
+
return gestureResponse(message: "tap series", timing: timing, frame: .touch(touchFrame))
|
|
440
532
|
case .longPress:
|
|
441
533
|
guard let x = command.x, let y = command.y else {
|
|
442
534
|
return Response(ok: false, error: ErrorPayload(message: "longPress requires x and y"))
|
|
443
535
|
}
|
|
444
536
|
let duration = (command.durationMs ?? 800) / 1000.0
|
|
445
537
|
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
449
|
-
outcome = longPressAt(app: activeApp, x: x, y: y, duration: duration)
|
|
450
|
-
}
|
|
538
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
539
|
+
longPressAt(app: activeApp, x: x, y: y, duration: duration)
|
|
451
540
|
}
|
|
452
541
|
if let response = unsupportedResponse(for: outcome) {
|
|
453
542
|
return response
|
|
454
543
|
}
|
|
455
|
-
return
|
|
456
|
-
ok: true,
|
|
457
|
-
data: DataPayload(
|
|
458
|
-
message: "long pressed",
|
|
459
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
460
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
461
|
-
x: touchFrame.x,
|
|
462
|
-
y: touchFrame.y,
|
|
463
|
-
referenceWidth: touchFrame.referenceWidth,
|
|
464
|
-
referenceHeight: touchFrame.referenceHeight
|
|
465
|
-
)
|
|
466
|
-
)
|
|
544
|
+
return gestureResponse(message: "long pressed", timing: timing, frame: .touch(touchFrame))
|
|
467
545
|
case .drag:
|
|
468
546
|
guard let x = command.x, let y = command.y, let x2 = command.x2, let y2 = command.y2 else {
|
|
469
547
|
return Response(ok: false, error: ErrorPayload(message: "drag requires x, y, x2, and y2"))
|
|
470
548
|
}
|
|
471
|
-
let holdDuration = min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
472
549
|
let dragPoints = keyboardAvoidingDragPoints(app: activeApp, x: x, y: y, x2: x2, y2: y2)
|
|
473
550
|
let dragFrame = resolvedDragVisualizationFrame(
|
|
474
551
|
app: activeApp,
|
|
@@ -477,35 +554,45 @@ extension RunnerTests {
|
|
|
477
554
|
x2: dragPoints.x2,
|
|
478
555
|
y2: dragPoints.y2
|
|
479
556
|
)
|
|
480
|
-
var
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
557
|
+
var fallback: GestureFallback?
|
|
558
|
+
if command.synthesized == true {
|
|
559
|
+
let durationMs = min(max(command.durationMs ?? 250, 16), 10000)
|
|
560
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
561
|
+
synthesizedDragAt(
|
|
484
562
|
app: activeApp,
|
|
485
563
|
x: dragPoints.x,
|
|
486
564
|
y: dragPoints.y,
|
|
487
565
|
x2: dragPoints.x2,
|
|
488
566
|
y2: dragPoints.y2,
|
|
489
|
-
|
|
567
|
+
durationMs: durationMs
|
|
490
568
|
)
|
|
491
569
|
}
|
|
570
|
+
if case .performed = outcome {
|
|
571
|
+
return gestureResponse(message: "dragged", timing: timing, frame: .drag(dragFrame))
|
|
572
|
+
}
|
|
573
|
+
fallback = gestureFallback(strategy: "xctest-coordinate-drag", from: outcome)
|
|
574
|
+
}
|
|
575
|
+
let holdDuration = command.synthesized == true
|
|
576
|
+
? synthesizedSwipeFallbackHoldDuration(durationMs: command.durationMs ?? 250)
|
|
577
|
+
: min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
578
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
579
|
+
dragAt(
|
|
580
|
+
app: activeApp,
|
|
581
|
+
x: dragPoints.x,
|
|
582
|
+
y: dragPoints.y,
|
|
583
|
+
x2: dragPoints.x2,
|
|
584
|
+
y2: dragPoints.y2,
|
|
585
|
+
holdDuration: holdDuration
|
|
586
|
+
)
|
|
492
587
|
}
|
|
493
588
|
if let response = unsupportedResponse(for: outcome) {
|
|
494
589
|
return response
|
|
495
590
|
}
|
|
496
|
-
return
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
502
|
-
x: dragFrame.x,
|
|
503
|
-
y: dragFrame.y,
|
|
504
|
-
x2: dragFrame.x2,
|
|
505
|
-
y2: dragFrame.y2,
|
|
506
|
-
referenceWidth: dragFrame.referenceWidth,
|
|
507
|
-
referenceHeight: dragFrame.referenceHeight
|
|
508
|
-
)
|
|
591
|
+
return gestureResponse(
|
|
592
|
+
message: "dragged",
|
|
593
|
+
timing: timing,
|
|
594
|
+
frame: .drag(dragFrame),
|
|
595
|
+
fallback: fallback
|
|
509
596
|
)
|
|
510
597
|
case .dragSeries:
|
|
511
598
|
guard let x = command.x, let y = command.y, let x2 = command.x2, let y2 = command.y2 else {
|
|
@@ -517,49 +604,56 @@ extension RunnerTests {
|
|
|
517
604
|
if pattern != "one-way" && pattern != "ping-pong" {
|
|
518
605
|
return Response(ok: false, error: ErrorPayload(message: "dragSeries pattern must be one-way or ping-pong"))
|
|
519
606
|
}
|
|
520
|
-
let holdDuration = min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
521
607
|
let dragPoints = keyboardAvoidingDragPoints(app: activeApp, x: x, y: y, x2: x2, y2: y2)
|
|
522
|
-
var
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
outcome = dragAt(
|
|
541
|
-
app: activeApp,
|
|
542
|
-
x: dragPoints.x,
|
|
543
|
-
y: dragPoints.y,
|
|
544
|
-
x2: dragPoints.x2,
|
|
545
|
-
y2: dragPoints.y2,
|
|
546
|
-
holdDuration: holdDuration
|
|
547
|
-
)
|
|
548
|
-
}
|
|
608
|
+
var fallback: GestureFallback?
|
|
609
|
+
if command.synthesized == true {
|
|
610
|
+
let durationMs = min(max(command.durationMs ?? 250, 16), 10000)
|
|
611
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
612
|
+
performDragSeries(
|
|
613
|
+
count: count,
|
|
614
|
+
pauseMs: pauseMs,
|
|
615
|
+
pattern: pattern,
|
|
616
|
+
points: dragPoints
|
|
617
|
+
) { startX, startY, endX, endY in
|
|
618
|
+
synthesizedDragAt(
|
|
619
|
+
app: activeApp,
|
|
620
|
+
x: startX,
|
|
621
|
+
y: startY,
|
|
622
|
+
x2: endX,
|
|
623
|
+
y2: endY,
|
|
624
|
+
durationMs: durationMs
|
|
625
|
+
)
|
|
549
626
|
}
|
|
550
627
|
}
|
|
628
|
+
if case .performed = outcome {
|
|
629
|
+
return gestureResponse(message: "drag series", timing: timing)
|
|
630
|
+
}
|
|
631
|
+
fallback = gestureFallback(strategy: "xctest-coordinate-drag-series", from: outcome)
|
|
632
|
+
}
|
|
633
|
+
let holdDuration = command.synthesized == true
|
|
634
|
+
? synthesizedSwipeFallbackHoldDuration(durationMs: command.durationMs ?? 250)
|
|
635
|
+
: min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
636
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
637
|
+
performDragSeries(
|
|
638
|
+
count: count,
|
|
639
|
+
pauseMs: pauseMs,
|
|
640
|
+
pattern: pattern,
|
|
641
|
+
points: dragPoints
|
|
642
|
+
) { startX, startY, endX, endY in
|
|
643
|
+
dragAt(
|
|
644
|
+
app: activeApp,
|
|
645
|
+
x: startX,
|
|
646
|
+
y: startY,
|
|
647
|
+
x2: endX,
|
|
648
|
+
y2: endY,
|
|
649
|
+
holdDuration: holdDuration
|
|
650
|
+
)
|
|
651
|
+
}
|
|
551
652
|
}
|
|
552
653
|
if let response = unsupportedResponse(for: outcome) {
|
|
553
654
|
return response
|
|
554
655
|
}
|
|
555
|
-
return
|
|
556
|
-
ok: true,
|
|
557
|
-
data: DataPayload(
|
|
558
|
-
message: "drag series",
|
|
559
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
560
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
561
|
-
)
|
|
562
|
-
)
|
|
656
|
+
return gestureResponse(message: "drag series", timing: timing, fallback: fallback)
|
|
563
657
|
case .remotePress:
|
|
564
658
|
guard let button = tvRemoteButton(from: command.remoteButton) else {
|
|
565
659
|
return Response(ok: false, error: ErrorPayload(message: "remotePress requires remoteButton"))
|
|
@@ -593,32 +687,18 @@ extension RunnerTests {
|
|
|
593
687
|
guard let direction = command.direction else {
|
|
594
688
|
return Response(ok: false, error: ErrorPayload(message: "swipe requires direction"))
|
|
595
689
|
}
|
|
690
|
+
// swipe returns an optional frame (tvOS-only) rather than a RunnerInteractionOutcome, so it
|
|
691
|
+
// keeps raw measureGesture and only routes the success payload through gestureResponse.
|
|
596
692
|
var executedFrame: DragVisualizationFrame?
|
|
597
693
|
let timing = measureGesture {
|
|
598
694
|
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
599
|
-
executedFrame = swipe(
|
|
600
|
-
app: activeApp,
|
|
601
|
-
direction: direction
|
|
602
|
-
)
|
|
695
|
+
executedFrame = swipe(app: activeApp, direction: direction)
|
|
603
696
|
}
|
|
604
697
|
}
|
|
605
698
|
guard let dragFrame = executedFrame else {
|
|
606
699
|
return Response(ok: false, error: ErrorPayload(message: "swipe is only supported on tvOS"))
|
|
607
700
|
}
|
|
608
|
-
return
|
|
609
|
-
ok: true,
|
|
610
|
-
data: DataPayload(
|
|
611
|
-
message: "swiped",
|
|
612
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
613
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
614
|
-
x: dragFrame.x,
|
|
615
|
-
y: dragFrame.y,
|
|
616
|
-
x2: dragFrame.x2,
|
|
617
|
-
y2: dragFrame.y2,
|
|
618
|
-
referenceWidth: dragFrame.referenceWidth,
|
|
619
|
-
referenceHeight: dragFrame.referenceHeight
|
|
620
|
-
)
|
|
621
|
-
)
|
|
701
|
+
return gestureResponse(message: "swiped", timing: timing, frame: .drag(dragFrame))
|
|
622
702
|
case .findText:
|
|
623
703
|
guard let text = command.text else {
|
|
624
704
|
return Response(ok: false, error: ErrorPayload(message: "findText requires text"))
|
|
@@ -785,21 +865,13 @@ extension RunnerTests {
|
|
|
785
865
|
guard let scale = command.scale, scale > 0 else {
|
|
786
866
|
return Response(ok: false, error: ErrorPayload(message: "pinch requires scale > 0"))
|
|
787
867
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
outcome = pinch(app: activeApp, scale: scale, x: command.x, y: command.y)
|
|
868
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
869
|
+
pinch(app: activeApp, scale: scale, x: command.x, y: command.y)
|
|
791
870
|
}
|
|
792
871
|
if let response = unsupportedResponse(for: outcome) {
|
|
793
872
|
return response
|
|
794
873
|
}
|
|
795
|
-
return
|
|
796
|
-
ok: true,
|
|
797
|
-
data: DataPayload(
|
|
798
|
-
message: "pinched",
|
|
799
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
800
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
801
|
-
)
|
|
802
|
-
)
|
|
874
|
+
return gestureResponse(message: "pinched", timing: timing)
|
|
803
875
|
case .rotateGesture:
|
|
804
876
|
guard let degrees = command.degrees, degrees.isFinite else {
|
|
805
877
|
return Response(ok: false, error: ErrorPayload(message: "rotateGesture requires degrees"))
|
|
@@ -808,9 +880,8 @@ extension RunnerTests {
|
|
|
808
880
|
guard velocity.isFinite && velocity != 0 else {
|
|
809
881
|
return Response(ok: false, error: ErrorPayload(message: "rotateGesture velocity must be non-zero"))
|
|
810
882
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
outcome = rotateGesture(
|
|
883
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
884
|
+
rotateGesture(
|
|
814
885
|
app: activeApp,
|
|
815
886
|
degrees: degrees,
|
|
816
887
|
x: command.x,
|
|
@@ -821,14 +892,7 @@ extension RunnerTests {
|
|
|
821
892
|
if let response = unsupportedResponse(for: outcome) {
|
|
822
893
|
return response
|
|
823
894
|
}
|
|
824
|
-
return
|
|
825
|
-
ok: true,
|
|
826
|
-
data: DataPayload(
|
|
827
|
-
message: "rotatedGesture",
|
|
828
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
829
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
830
|
-
)
|
|
831
|
-
)
|
|
895
|
+
return gestureResponse(message: "rotatedGesture", timing: timing)
|
|
832
896
|
case .transformGesture:
|
|
833
897
|
guard
|
|
834
898
|
let x = command.x,
|
|
@@ -852,9 +916,8 @@ extension RunnerTests {
|
|
|
852
916
|
guard durationMs.isFinite && durationMs >= 16 else {
|
|
853
917
|
return Response(ok: false, error: ErrorPayload(message: "transformGesture durationMs must be >= 16"))
|
|
854
918
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
outcome = transformGesture(
|
|
919
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
920
|
+
transformGesture(
|
|
858
921
|
app: activeApp,
|
|
859
922
|
x: x,
|
|
860
923
|
y: y,
|
|
@@ -868,14 +931,7 @@ extension RunnerTests {
|
|
|
868
931
|
if let response = unsupportedResponse(for: outcome) {
|
|
869
932
|
return response
|
|
870
933
|
}
|
|
871
|
-
return
|
|
872
|
-
ok: true,
|
|
873
|
-
data: DataPayload(
|
|
874
|
-
message: "transformedGesture",
|
|
875
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
876
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
877
|
-
)
|
|
878
|
-
)
|
|
934
|
+
return gestureResponse(message: "transformedGesture", timing: timing)
|
|
879
935
|
}
|
|
880
936
|
}
|
|
881
937
|
|