agent-device 0.16.9 → 0.16.10
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.10.apk} +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.16.10.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.10.manifest.json} +4 -4
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.16.9.apk → agent-device-android-snapshot-helper-0.16.10.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.16.10.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.10.manifest.json} +6 -6
- package/dist/src/2415.js +19 -19
- package/dist/src/apps.js +2 -2
- package/dist/src/input-actions.js +1 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +197 -232
- 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 +8 -34
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +30 -0
- 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/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.sha256 +0 -1
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -25,7 +25,116 @@ extension RunnerTests {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/// Optional visualization frame returned with a gesture response.
|
|
29
|
+
enum GestureFrame {
|
|
30
|
+
case none
|
|
31
|
+
case touch(TouchVisualizationFrame?)
|
|
32
|
+
case drag(DragVisualizationFrame)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Runs a gesture action with uniform timing capture. Touch gestures pass `idleTimeout: true`
|
|
36
|
+
/// (the default) to run inside the scroll idle-timeout + quiescence-skip wrapper; synthesis
|
|
37
|
+
/// gestures (pinch/rotate/transform) pass `false` because RunnerSynthesizedGesture governs its
|
|
38
|
+
/// own timing. Returns the captured timing and the action's outcome.
|
|
39
|
+
///
|
|
40
|
+
/// NOTE: a new SYNTHESIS gesture must pass `idleTimeout: false` — the default `true` would wrap
|
|
41
|
+
/// it in the scroll idle-timeout/quiescence-skip path and change its runtime behavior.
|
|
42
|
+
private func performGesture(
|
|
43
|
+
_ app: XCUIApplication,
|
|
44
|
+
idleTimeout: Bool = true,
|
|
45
|
+
_ action: () -> RunnerInteractionOutcome
|
|
46
|
+
) -> (timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double), outcome: RunnerInteractionOutcome) {
|
|
47
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
48
|
+
let timing = measureGesture {
|
|
49
|
+
if idleTimeout {
|
|
50
|
+
withTemporaryScrollIdleTimeoutIfSupported(app) { outcome = action() }
|
|
51
|
+
} else {
|
|
52
|
+
outcome = action()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return (timing, outcome)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Single factory for the success payload every gesture returns (message + gesture timing +
|
|
59
|
+
/// an optional touch/drag visualization frame), so the field shape lives in one place.
|
|
60
|
+
private func gestureResponse(
|
|
61
|
+
message: String,
|
|
62
|
+
timing: (gestureStartUptimeMs: Double, gestureEndUptimeMs: Double),
|
|
63
|
+
frame: GestureFrame = .none
|
|
64
|
+
) -> Response {
|
|
65
|
+
let data: DataPayload
|
|
66
|
+
switch frame {
|
|
67
|
+
case .none:
|
|
68
|
+
data = DataPayload(
|
|
69
|
+
message: message,
|
|
70
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
71
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
72
|
+
)
|
|
73
|
+
case .touch(let f):
|
|
74
|
+
data = DataPayload(
|
|
75
|
+
message: message,
|
|
76
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
77
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
78
|
+
x: f?.x,
|
|
79
|
+
y: f?.y,
|
|
80
|
+
referenceWidth: f?.referenceWidth,
|
|
81
|
+
referenceHeight: f?.referenceHeight
|
|
82
|
+
)
|
|
83
|
+
case .drag(let f):
|
|
84
|
+
data = DataPayload(
|
|
85
|
+
message: message,
|
|
86
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
87
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
88
|
+
x: f.x,
|
|
89
|
+
y: f.y,
|
|
90
|
+
x2: f.x2,
|
|
91
|
+
y2: f.y2,
|
|
92
|
+
referenceWidth: f.referenceWidth,
|
|
93
|
+
referenceHeight: f.referenceHeight
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
return Response(ok: true, data: data)
|
|
97
|
+
}
|
|
98
|
+
|
|
28
99
|
func execute(command: Command) throws -> Response {
|
|
100
|
+
if command.command == .status {
|
|
101
|
+
return executeStatus(command: command)
|
|
102
|
+
}
|
|
103
|
+
commandJournal.accept(command: command)
|
|
104
|
+
return try executeAccepted(command: command)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func executeAccepted(command: Command) throws -> Response {
|
|
108
|
+
commandJournal.start(command: command)
|
|
109
|
+
do {
|
|
110
|
+
let response = try executeDispatched(command: command)
|
|
111
|
+
commandJournal.finish(command: command, response: response)
|
|
112
|
+
return response
|
|
113
|
+
} catch {
|
|
114
|
+
commandJournal.fail(command: command, error: error)
|
|
115
|
+
throw error
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func executeStatus(command: Command) -> Response {
|
|
120
|
+
guard
|
|
121
|
+
let statusCommandId = command.statusCommandId?
|
|
122
|
+
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
123
|
+
!statusCommandId.isEmpty
|
|
124
|
+
else {
|
|
125
|
+
return Response(
|
|
126
|
+
ok: false,
|
|
127
|
+
error: ErrorPayload(
|
|
128
|
+
code: "INVALID_ARGS",
|
|
129
|
+
message: "status requires statusCommandId",
|
|
130
|
+
hint: "Set statusCommandId to the commandId of the runner command to inspect."
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
return Response(ok: true, data: commandJournal.status(commandId: statusCommandId))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private func executeDispatched(command: Command) throws -> Response {
|
|
29
138
|
if Thread.isMainThread {
|
|
30
139
|
return try executeOnMainSafely(command: command)
|
|
31
140
|
}
|
|
@@ -183,6 +292,8 @@ extension RunnerTests {
|
|
|
183
292
|
}
|
|
184
293
|
|
|
185
294
|
switch command.command {
|
|
295
|
+
case .status:
|
|
296
|
+
return executeStatus(command: command)
|
|
186
297
|
case .shutdown:
|
|
187
298
|
stopRecordingIfNeeded()
|
|
188
299
|
return Response(ok: true, data: DataPayload(message: "shutdown"))
|
|
@@ -266,83 +377,46 @@ extension RunnerTests {
|
|
|
266
377
|
let touchFrame = frame.isEmpty
|
|
267
378
|
? nil
|
|
268
379
|
: 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
|
-
}
|
|
380
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
381
|
+
if match.usedNonHittableFallback {
|
|
382
|
+
// Maestro compatibility: RN E2E backdoor controls can be 1x1 and
|
|
383
|
+
// reported non-hittable by XCTest, while Maestro still taps their
|
|
384
|
+
// resolved bounds. Keep this behind the explicit replay-only flag.
|
|
385
|
+
return tapAt(app: activeApp, x: frame.midX, y: frame.midY)
|
|
280
386
|
}
|
|
387
|
+
return activateElement(app: activeApp, element: element, action: "tap by selector")
|
|
281
388
|
}
|
|
282
389
|
if let response = unsupportedResponse(for: outcome) {
|
|
283
390
|
return response
|
|
284
391
|
}
|
|
285
392
|
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
|
-
)
|
|
393
|
+
return gestureResponse(
|
|
394
|
+
message: match.usedNonHittableFallback ? "tapped via non-hittable coordinate fallback" : "tapped",
|
|
395
|
+
timing: timing,
|
|
396
|
+
frame: .touch(touchFrame)
|
|
297
397
|
)
|
|
298
398
|
}
|
|
299
399
|
return Response(ok: false, error: ErrorPayload(code: "ELEMENT_NOT_FOUND", message: "element not found"))
|
|
300
400
|
}
|
|
301
401
|
if let text = command.text {
|
|
302
402
|
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
|
-
}
|
|
403
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
404
|
+
activateElement(app: activeApp, element: element, action: "tap by text")
|
|
308
405
|
}
|
|
309
406
|
if let response = unsupportedResponse(for: outcome) {
|
|
310
407
|
return response
|
|
311
408
|
}
|
|
312
|
-
return
|
|
313
|
-
ok: true,
|
|
314
|
-
data: DataPayload(
|
|
315
|
-
message: "tapped",
|
|
316
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
317
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
318
|
-
)
|
|
319
|
-
)
|
|
409
|
+
return gestureResponse(message: "tapped", timing: timing)
|
|
320
410
|
}
|
|
321
411
|
return Response(ok: false, error: ErrorPayload(message: "element not found"))
|
|
322
412
|
}
|
|
323
413
|
if let x = command.x, let y = command.y {
|
|
324
414
|
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
|
-
}
|
|
415
|
+
let (timing, outcome) = performGesture(activeApp) { tapAt(app: activeApp, x: x, y: y) }
|
|
331
416
|
if let response = unsupportedResponse(for: outcome) {
|
|
332
417
|
return response
|
|
333
418
|
}
|
|
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
|
-
)
|
|
419
|
+
return gestureResponse(message: "tapped", timing: timing, frame: .touch(touchFrame))
|
|
346
420
|
}
|
|
347
421
|
return Response(ok: false, error: ErrorPayload(message: "tap requires text or x/y"))
|
|
348
422
|
case .mouseClick:
|
|
@@ -351,6 +425,8 @@ extension RunnerTests {
|
|
|
351
425
|
}
|
|
352
426
|
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
353
427
|
do {
|
|
428
|
+
// mouseClick throws (it has no RunnerInteractionOutcome), so it keeps raw measureGesture
|
|
429
|
+
// and only routes the success payload through gestureResponse.
|
|
354
430
|
var clickError: Error?
|
|
355
431
|
let timing = measureGesture {
|
|
356
432
|
do {
|
|
@@ -362,18 +438,7 @@ extension RunnerTests {
|
|
|
362
438
|
if let clickError {
|
|
363
439
|
throw clickError
|
|
364
440
|
}
|
|
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
|
-
)
|
|
441
|
+
return gestureResponse(message: "clicked", timing: timing, frame: .touch(touchFrame))
|
|
377
442
|
} catch {
|
|
378
443
|
return Response(ok: false, error: ErrorPayload(message: error.localizedDescription))
|
|
379
444
|
}
|
|
@@ -386,84 +451,46 @@ extension RunnerTests {
|
|
|
386
451
|
let doubleTap = command.doubleTap ?? false
|
|
387
452
|
let touchFrame = resolvedTouchVisualizationFrame(app: activeApp, x: x, y: y)
|
|
388
453
|
if doubleTap {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
outcome = doubleTapAt(app: activeApp, x: x, y: y)
|
|
395
|
-
}
|
|
454
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
455
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
456
|
+
runSeries(count: count, pauseMs: intervalMs) { _ in
|
|
457
|
+
if case .performed = outcome {
|
|
458
|
+
outcome = doubleTapAt(app: activeApp, x: x, y: y)
|
|
396
459
|
}
|
|
397
460
|
}
|
|
461
|
+
return outcome
|
|
398
462
|
}
|
|
399
463
|
if let response = unsupportedResponse(for: outcome) {
|
|
400
464
|
return response
|
|
401
465
|
}
|
|
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
|
-
)
|
|
466
|
+
return gestureResponse(message: "tap series", timing: timing, frame: .touch(touchFrame))
|
|
414
467
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
outcome = tapAt(app: activeApp, x: x, y: y)
|
|
421
|
-
}
|
|
468
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
469
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
470
|
+
runSeries(count: count, pauseMs: intervalMs) { _ in
|
|
471
|
+
if case .performed = outcome {
|
|
472
|
+
outcome = tapAt(app: activeApp, x: x, y: y)
|
|
422
473
|
}
|
|
423
474
|
}
|
|
475
|
+
return outcome
|
|
424
476
|
}
|
|
425
477
|
if let response = unsupportedResponse(for: outcome) {
|
|
426
478
|
return response
|
|
427
479
|
}
|
|
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
|
-
)
|
|
480
|
+
return gestureResponse(message: "tap series", timing: timing, frame: .touch(touchFrame))
|
|
440
481
|
case .longPress:
|
|
441
482
|
guard let x = command.x, let y = command.y else {
|
|
442
483
|
return Response(ok: false, error: ErrorPayload(message: "longPress requires x and y"))
|
|
443
484
|
}
|
|
444
485
|
let duration = (command.durationMs ?? 800) / 1000.0
|
|
445
486
|
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
|
-
}
|
|
487
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
488
|
+
longPressAt(app: activeApp, x: x, y: y, duration: duration)
|
|
451
489
|
}
|
|
452
490
|
if let response = unsupportedResponse(for: outcome) {
|
|
453
491
|
return response
|
|
454
492
|
}
|
|
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
|
-
)
|
|
493
|
+
return gestureResponse(message: "long pressed", timing: timing, frame: .touch(touchFrame))
|
|
467
494
|
case .drag:
|
|
468
495
|
guard let x = command.x, let y = command.y, let x2 = command.x2, let y2 = command.y2 else {
|
|
469
496
|
return Response(ok: false, error: ErrorPayload(message: "drag requires x, y, x2, and y2"))
|
|
@@ -477,36 +504,20 @@ extension RunnerTests {
|
|
|
477
504
|
x2: dragPoints.x2,
|
|
478
505
|
y2: dragPoints.y2
|
|
479
506
|
)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
holdDuration: holdDuration
|
|
490
|
-
)
|
|
491
|
-
}
|
|
507
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
508
|
+
dragAt(
|
|
509
|
+
app: activeApp,
|
|
510
|
+
x: dragPoints.x,
|
|
511
|
+
y: dragPoints.y,
|
|
512
|
+
x2: dragPoints.x2,
|
|
513
|
+
y2: dragPoints.y2,
|
|
514
|
+
holdDuration: holdDuration
|
|
515
|
+
)
|
|
492
516
|
}
|
|
493
517
|
if let response = unsupportedResponse(for: outcome) {
|
|
494
518
|
return response
|
|
495
519
|
}
|
|
496
|
-
return
|
|
497
|
-
ok: true,
|
|
498
|
-
data: DataPayload(
|
|
499
|
-
message: "dragged",
|
|
500
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
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
|
-
)
|
|
509
|
-
)
|
|
520
|
+
return gestureResponse(message: "dragged", timing: timing, frame: .drag(dragFrame))
|
|
510
521
|
case .dragSeries:
|
|
511
522
|
guard let x = command.x, let y = command.y, let x2 = command.x2, let y2 = command.y2 else {
|
|
512
523
|
return Response(ok: false, error: ErrorPayload(message: "dragSeries requires x, y, x2, and y2"))
|
|
@@ -519,47 +530,39 @@ extension RunnerTests {
|
|
|
519
530
|
}
|
|
520
531
|
let holdDuration = min(max((command.durationMs ?? 60) / 1000.0, 0.016), 10.0)
|
|
521
532
|
let dragPoints = keyboardAvoidingDragPoints(app: activeApp, x: x, y: y, x2: x2, y2: y2)
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
)
|
|
548
|
-
}
|
|
533
|
+
let (timing, outcome) = performGesture(activeApp) {
|
|
534
|
+
var outcome = RunnerInteractionOutcome.performed
|
|
535
|
+
runSeries(count: count, pauseMs: pauseMs) { idx in
|
|
536
|
+
guard case .performed = outcome else {
|
|
537
|
+
return
|
|
538
|
+
}
|
|
539
|
+
let reverse = pattern == "ping-pong" && (idx % 2 == 1)
|
|
540
|
+
if reverse {
|
|
541
|
+
outcome = dragAt(
|
|
542
|
+
app: activeApp,
|
|
543
|
+
x: dragPoints.x2,
|
|
544
|
+
y: dragPoints.y2,
|
|
545
|
+
x2: dragPoints.x,
|
|
546
|
+
y2: dragPoints.y,
|
|
547
|
+
holdDuration: holdDuration
|
|
548
|
+
)
|
|
549
|
+
} else {
|
|
550
|
+
outcome = dragAt(
|
|
551
|
+
app: activeApp,
|
|
552
|
+
x: dragPoints.x,
|
|
553
|
+
y: dragPoints.y,
|
|
554
|
+
x2: dragPoints.x2,
|
|
555
|
+
y2: dragPoints.y2,
|
|
556
|
+
holdDuration: holdDuration
|
|
557
|
+
)
|
|
549
558
|
}
|
|
550
559
|
}
|
|
560
|
+
return outcome
|
|
551
561
|
}
|
|
552
562
|
if let response = unsupportedResponse(for: outcome) {
|
|
553
563
|
return response
|
|
554
564
|
}
|
|
555
|
-
return
|
|
556
|
-
ok: true,
|
|
557
|
-
data: DataPayload(
|
|
558
|
-
message: "drag series",
|
|
559
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
560
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
561
|
-
)
|
|
562
|
-
)
|
|
565
|
+
return gestureResponse(message: "drag series", timing: timing)
|
|
563
566
|
case .remotePress:
|
|
564
567
|
guard let button = tvRemoteButton(from: command.remoteButton) else {
|
|
565
568
|
return Response(ok: false, error: ErrorPayload(message: "remotePress requires remoteButton"))
|
|
@@ -593,32 +596,18 @@ extension RunnerTests {
|
|
|
593
596
|
guard let direction = command.direction else {
|
|
594
597
|
return Response(ok: false, error: ErrorPayload(message: "swipe requires direction"))
|
|
595
598
|
}
|
|
599
|
+
// swipe returns an optional frame (tvOS-only) rather than a RunnerInteractionOutcome, so it
|
|
600
|
+
// keeps raw measureGesture and only routes the success payload through gestureResponse.
|
|
596
601
|
var executedFrame: DragVisualizationFrame?
|
|
597
602
|
let timing = measureGesture {
|
|
598
603
|
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
599
|
-
executedFrame = swipe(
|
|
600
|
-
app: activeApp,
|
|
601
|
-
direction: direction
|
|
602
|
-
)
|
|
604
|
+
executedFrame = swipe(app: activeApp, direction: direction)
|
|
603
605
|
}
|
|
604
606
|
}
|
|
605
607
|
guard let dragFrame = executedFrame else {
|
|
606
608
|
return Response(ok: false, error: ErrorPayload(message: "swipe is only supported on tvOS"))
|
|
607
609
|
}
|
|
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
|
-
)
|
|
610
|
+
return gestureResponse(message: "swiped", timing: timing, frame: .drag(dragFrame))
|
|
622
611
|
case .findText:
|
|
623
612
|
guard let text = command.text else {
|
|
624
613
|
return Response(ok: false, error: ErrorPayload(message: "findText requires text"))
|
|
@@ -785,21 +774,13 @@ extension RunnerTests {
|
|
|
785
774
|
guard let scale = command.scale, scale > 0 else {
|
|
786
775
|
return Response(ok: false, error: ErrorPayload(message: "pinch requires scale > 0"))
|
|
787
776
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
outcome = pinch(app: activeApp, scale: scale, x: command.x, y: command.y)
|
|
777
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
778
|
+
pinch(app: activeApp, scale: scale, x: command.x, y: command.y)
|
|
791
779
|
}
|
|
792
780
|
if let response = unsupportedResponse(for: outcome) {
|
|
793
781
|
return response
|
|
794
782
|
}
|
|
795
|
-
return
|
|
796
|
-
ok: true,
|
|
797
|
-
data: DataPayload(
|
|
798
|
-
message: "pinched",
|
|
799
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
800
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
801
|
-
)
|
|
802
|
-
)
|
|
783
|
+
return gestureResponse(message: "pinched", timing: timing)
|
|
803
784
|
case .rotateGesture:
|
|
804
785
|
guard let degrees = command.degrees, degrees.isFinite else {
|
|
805
786
|
return Response(ok: false, error: ErrorPayload(message: "rotateGesture requires degrees"))
|
|
@@ -808,9 +789,8 @@ extension RunnerTests {
|
|
|
808
789
|
guard velocity.isFinite && velocity != 0 else {
|
|
809
790
|
return Response(ok: false, error: ErrorPayload(message: "rotateGesture velocity must be non-zero"))
|
|
810
791
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
outcome = rotateGesture(
|
|
792
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
793
|
+
rotateGesture(
|
|
814
794
|
app: activeApp,
|
|
815
795
|
degrees: degrees,
|
|
816
796
|
x: command.x,
|
|
@@ -821,14 +801,7 @@ extension RunnerTests {
|
|
|
821
801
|
if let response = unsupportedResponse(for: outcome) {
|
|
822
802
|
return response
|
|
823
803
|
}
|
|
824
|
-
return
|
|
825
|
-
ok: true,
|
|
826
|
-
data: DataPayload(
|
|
827
|
-
message: "rotatedGesture",
|
|
828
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
829
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
830
|
-
)
|
|
831
|
-
)
|
|
804
|
+
return gestureResponse(message: "rotatedGesture", timing: timing)
|
|
832
805
|
case .transformGesture:
|
|
833
806
|
guard
|
|
834
807
|
let x = command.x,
|
|
@@ -852,9 +825,8 @@ extension RunnerTests {
|
|
|
852
825
|
guard durationMs.isFinite && durationMs >= 16 else {
|
|
853
826
|
return Response(ok: false, error: ErrorPayload(message: "transformGesture durationMs must be >= 16"))
|
|
854
827
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
outcome = transformGesture(
|
|
828
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
829
|
+
transformGesture(
|
|
858
830
|
app: activeApp,
|
|
859
831
|
x: x,
|
|
860
832
|
y: y,
|
|
@@ -868,14 +840,7 @@ extension RunnerTests {
|
|
|
868
840
|
if let response = unsupportedResponse(for: outcome) {
|
|
869
841
|
return response
|
|
870
842
|
}
|
|
871
|
-
return
|
|
872
|
-
ok: true,
|
|
873
|
-
data: DataPayload(
|
|
874
|
-
message: "transformedGesture",
|
|
875
|
-
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
876
|
-
gestureEndUptimeMs: timing.gestureEndUptimeMs
|
|
877
|
-
)
|
|
878
|
-
)
|
|
843
|
+
return gestureResponse(message: "transformedGesture", timing: timing)
|
|
879
844
|
}
|
|
880
845
|
}
|
|
881
846
|
|