agent-device 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/src/36.js +3 -0
- package/dist/src/bin.js +82 -66
- package/dist/src/daemon.js +40 -39
- package/dist/src/index.d.ts +567 -5
- package/dist/src/index.js +3 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +84 -16
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +140 -50
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +13 -2
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +22 -9
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +221 -0
- package/ios-runner/README.md +17 -2
- package/ios-runner/RUNNER_PROTOCOL.md +73 -0
- package/package.json +11 -6
- package/skills/agent-device/SKILL.md +38 -12
- package/skills/agent-device/references/bootstrap-install.md +55 -15
- package/skills/agent-device/references/exploration.md +65 -8
- package/skills/agent-device/references/macos-desktop.md +1 -2
- package/skills/agent-device/references/remote-tenancy.md +15 -2
- package/skills/agent-device/references/verification.md +7 -3
- package/dist/src/224.js +0 -2
- package/dist/src/331.js +0 -3
- package/dist/src/425.js +0 -1
- package/dist/src/bin.d.ts +0 -1
- package/dist/src/cli-client-commands.d.ts +0 -8
- package/dist/src/cli.d.ts +0 -6
- package/dist/src/client-metro.d.ts +0 -64
- package/dist/src/client-normalizers.d.ts +0 -20
- package/dist/src/client-shared.d.ts +0 -20
- package/dist/src/client-types.d.ts +0 -269
- package/dist/src/client.d.ts +0 -5
- package/dist/src/core/app-events.d.ts +0 -8
- package/dist/src/core/batch.d.ts +0 -17
- package/dist/src/core/capabilities.d.ts +0 -3
- package/dist/src/core/click-button.d.ts +0 -20
- package/dist/src/core/dispatch-payload.d.ts +0 -1
- package/dist/src/core/dispatch-resolve.d.ts +0 -29
- package/dist/src/core/dispatch-series.d.ts +0 -7
- package/dist/src/core/dispatch.d.ts +0 -37
- package/dist/src/core/open-target.d.ts +0 -4
- package/dist/src/core/session-surface.d.ts +0 -3
- package/dist/src/core/settings-contract.d.ts +0 -9
- package/dist/src/daemon/action-utils.d.ts +0 -3
- package/dist/src/daemon/android-system-dialog.d.ts +0 -11
- package/dist/src/daemon/app-log-android.d.ts +0 -4
- package/dist/src/daemon/app-log-ios.d.ts +0 -7
- package/dist/src/daemon/app-log-process.d.ts +0 -15
- package/dist/src/daemon/app-log-stream.d.ts +0 -19
- package/dist/src/daemon/app-log.d.ts +0 -28
- package/dist/src/daemon/artifact-archive.d.ts +0 -12
- package/dist/src/daemon/artifact-download.d.ts +0 -12
- package/dist/src/daemon/artifact-materialization.d.ts +0 -17
- package/dist/src/daemon/artifact-registry.d.ts +0 -12
- package/dist/src/daemon/config.d.ts +0 -16
- package/dist/src/daemon/context.d.ts +0 -25
- package/dist/src/daemon/device-ready.d.ts +0 -6
- package/dist/src/daemon/handlers/find.d.ts +0 -40
- package/dist/src/daemon/handlers/install-source.d.ts +0 -44
- package/dist/src/daemon/handlers/interaction-common.d.ts +0 -41
- package/dist/src/daemon/handlers/interaction-flags.d.ts +0 -4
- package/dist/src/daemon/handlers/interaction-get.d.ts +0 -3
- package/dist/src/daemon/handlers/interaction-is.d.ts +0 -3
- package/dist/src/daemon/handlers/interaction-read.d.ts +0 -14
- package/dist/src/daemon/handlers/interaction-scroll.d.ts +0 -3
- package/dist/src/daemon/handlers/interaction-selector.d.ts +0 -27
- package/dist/src/daemon/handlers/interaction-snapshot.d.ts +0 -8
- package/dist/src/daemon/handlers/interaction-targeting.d.ts +0 -28
- package/dist/src/daemon/handlers/interaction-touch.d.ts +0 -45
- package/dist/src/daemon/handlers/interaction.d.ts +0 -9
- package/dist/src/daemon/handlers/lease.d.ts +0 -8
- package/dist/src/daemon/handlers/parse-utils.d.ts +0 -3
- package/dist/src/daemon/handlers/record-trace-android.d.ts +0 -18
- package/dist/src/daemon/handlers/record-trace-ios.d.ts +0 -52
- package/dist/src/daemon/handlers/record-trace-recording.d.ts +0 -32
- package/dist/src/daemon/handlers/record-trace.d.ts +0 -10
- package/dist/src/daemon/handlers/session-batch.d.ts +0 -2
- package/dist/src/daemon/handlers/session-close.d.ts +0 -31
- package/dist/src/daemon/handlers/session-deploy.d.ts +0 -37
- package/dist/src/daemon/handlers/session-device-utils.d.ts +0 -26
- package/dist/src/daemon/handlers/session-open-target.d.ts +0 -3
- package/dist/src/daemon/handlers/session-open.d.ts +0 -22
- package/dist/src/daemon/handlers/session-perf.d.ts +0 -2
- package/dist/src/daemon/handlers/session-replay-heal.d.ts +0 -8
- package/dist/src/daemon/handlers/session-replay-script.d.ts +0 -3
- package/dist/src/daemon/handlers/session-runtime-command.d.ts +0 -9
- package/dist/src/daemon/handlers/session-runtime.d.ts +0 -36
- package/dist/src/daemon/handlers/session-startup-metrics.d.ts +0 -11
- package/dist/src/daemon/handlers/session.d.ts +0 -50
- package/dist/src/daemon/handlers/snapshot-alert.d.ts +0 -13
- package/dist/src/daemon/handlers/snapshot-capture.d.ts +0 -34
- package/dist/src/daemon/handlers/snapshot-session.d.ts +0 -15
- package/dist/src/daemon/handlers/snapshot-settings.d.ts +0 -24
- package/dist/src/daemon/handlers/snapshot-wait.d.ts +0 -37
- package/dist/src/daemon/handlers/snapshot.d.ts +0 -16
- package/dist/src/daemon/http-server.d.ts +0 -26
- package/dist/src/daemon/install-source-resolution.d.ts +0 -5
- package/dist/src/daemon/is-predicates.d.ts +0 -15
- package/dist/src/daemon/lease-context.d.ts +0 -9
- package/dist/src/daemon/lease-registry.d.ts +0 -63
- package/dist/src/daemon/materialized-path-registry.d.ts +0 -15
- package/dist/src/daemon/network-log.d.ts +0 -32
- package/dist/src/daemon/record-trace-errors.d.ts +0 -6
- package/dist/src/daemon/recording-gestures.d.ts +0 -3
- package/dist/src/daemon/recording-telemetry.d.ts +0 -20
- package/dist/src/daemon/recording-timing.d.ts +0 -24
- package/dist/src/daemon/request-cancel.d.ts +0 -9
- package/dist/src/daemon/request-lock-policy.d.ts +0 -2
- package/dist/src/daemon/request-router.d.ts +0 -23
- package/dist/src/daemon/runtime-hints.d.ts +0 -19
- package/dist/src/daemon/script-utils.d.ts +0 -28
- package/dist/src/daemon/scroll-planner.d.ts +0 -12
- package/dist/src/daemon/selectors-build.d.ts +0 -5
- package/dist/src/daemon/selectors-match.d.ts +0 -6
- package/dist/src/daemon/selectors-parse.d.ts +0 -29
- package/dist/src/daemon/selectors-resolve.d.ts +0 -33
- package/dist/src/daemon/selectors.d.ts +0 -5
- package/dist/src/daemon/server-lifecycle.d.ts +0 -23
- package/dist/src/daemon/session-open-script.d.ts +0 -7
- package/dist/src/daemon/session-routing.d.ts +0 -3
- package/dist/src/daemon/session-selector.d.ts +0 -10
- package/dist/src/daemon/session-store.d.ts +0 -33
- package/dist/src/daemon/snapshot-diff.d.ts +0 -20
- package/dist/src/daemon/snapshot-processing.d.ts +0 -10
- package/dist/src/daemon/touch-reference-frame.d.ts +0 -7
- package/dist/src/daemon/transport.d.ts +0 -6
- package/dist/src/daemon/types.d.ts +0 -173
- package/dist/src/daemon/upload-registry.d.ts +0 -7
- package/dist/src/daemon/upload.d.ts +0 -5
- package/dist/src/daemon-client.d.ts +0 -40
- package/dist/src/daemon.d.ts +0 -1
- package/dist/src/platforms/android/adb.d.ts +0 -5
- package/dist/src/platforms/android/app-lifecycle.d.ts +0 -31
- package/dist/src/platforms/android/device-input-state.d.ts +0 -19
- package/dist/src/platforms/android/devices.d.ts +0 -26
- package/dist/src/platforms/android/index.d.ts +0 -8
- package/dist/src/platforms/android/input-actions.d.ts +0 -17
- package/dist/src/platforms/android/install-artifact.d.ts +0 -11
- package/dist/src/platforms/android/manifest.d.ts +0 -1
- package/dist/src/platforms/android/notifications.d.ts +0 -11
- package/dist/src/platforms/android/open-target.d.ts +0 -4
- package/dist/src/platforms/android/screenshot.d.ts +0 -16
- package/dist/src/platforms/android/sdk.d.ts +0 -2
- package/dist/src/platforms/android/settings.d.ts +0 -3
- package/dist/src/platforms/android/snapshot.d.ts +0 -7
- package/dist/src/platforms/android/ui-hierarchy.d.ts +0 -21
- package/dist/src/platforms/appearance.d.ts +0 -2
- package/dist/src/platforms/boot-diagnostics.d.ts +0 -14
- package/dist/src/platforms/install-source.d.ts +0 -29
- package/dist/src/platforms/ios/app-filter.d.ts +0 -2
- package/dist/src/platforms/ios/apps.d.ts +0 -34
- package/dist/src/platforms/ios/config.d.ts +0 -10
- package/dist/src/platforms/ios/devicectl.d.ts +0 -13
- package/dist/src/platforms/ios/devices.d.ts +0 -40
- package/dist/src/platforms/ios/ensure-simulator.d.ts +0 -18
- package/dist/src/platforms/ios/index.d.ts +0 -3
- package/dist/src/platforms/ios/install-artifact.d.ts +0 -18
- package/dist/src/platforms/ios/launch-diagnostics.d.ts +0 -11
- package/dist/src/platforms/ios/macos-apps.d.ts +0 -12
- package/dist/src/platforms/ios/macos-helper.d.ts +0 -69
- package/dist/src/platforms/ios/plist.d.ts +0 -1
- package/dist/src/platforms/ios/runner-client.d.ts +0 -38
- package/dist/src/platforms/ios/runner-errors.d.ts +0 -20
- package/dist/src/platforms/ios/runner-macos-products.d.ts +0 -3
- package/dist/src/platforms/ios/runner-session.d.ts +0 -30
- package/dist/src/platforms/ios/runner-transport.d.ts +0 -10
- package/dist/src/platforms/ios/runner-xctestrun-products.d.ts +0 -2
- package/dist/src/platforms/ios/runner-xctestrun.d.ts +0 -38
- package/dist/src/platforms/ios/screenshot-status-bar.d.ts +0 -2
- package/dist/src/platforms/ios/screenshot.d.ts +0 -14
- package/dist/src/platforms/ios/simctl.d.ts +0 -7
- package/dist/src/platforms/ios/simulator.d.ts +0 -11
- package/dist/src/platforms/permission-utils.d.ts +0 -9
- package/dist/src/recording/overlay.d.ts +0 -10
- package/dist/src/upload-client.d.ts +0 -7
- package/dist/src/utils/args.d.ts +0 -27
- package/dist/src/utils/cli-config.d.ts +0 -10
- package/dist/src/utils/cli-option-schema.d.ts +0 -19
- package/dist/src/utils/cli-options.d.ts +0 -13
- package/dist/src/utils/command-schema.d.ts +0 -123
- package/dist/src/utils/device-isolation.d.ts +0 -3
- package/dist/src/utils/device.d.ts +0 -35
- package/dist/src/utils/diagnostics.d.ts +0 -30
- package/dist/src/utils/errors.d.ts +0 -26
- package/dist/src/utils/exec.d.ts +0 -32
- package/dist/src/utils/finders.d.ts +0 -12
- package/dist/src/utils/interactors.d.ts +0 -38
- package/dist/src/utils/json-input.d.ts +0 -1
- package/dist/src/utils/keyed-lock.d.ts +0 -1
- package/dist/src/utils/output.d.ts +0 -27
- package/dist/src/utils/path-resolution.d.ts +0 -8
- package/dist/src/utils/payload-input.d.ts +0 -12
- package/dist/src/utils/process-identity.d.ts +0 -11
- package/dist/src/utils/remote-config.d.ts +0 -15
- package/dist/src/utils/remote-open.d.ts +0 -9
- package/dist/src/utils/retry.d.ts +0 -54
- package/dist/src/utils/screenshot-diff.d.ts +0 -23
- package/dist/src/utils/session-binding.d.ts +0 -18
- package/dist/src/utils/snapshot-lines.d.ts +0 -15
- package/dist/src/utils/snapshot.d.ts +0 -49
- package/dist/src/utils/text-surface.d.ts +0 -19
- package/dist/src/utils/timeouts.d.ts +0 -3
- package/dist/src/utils/version.d.ts +0 -2
- package/dist/src/utils/video.d.ts +0 -9
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -422,38 +422,81 @@ extension RunnerTests {
|
|
|
422
422
|
guard let text = command.text else {
|
|
423
423
|
return Response(ok: false, error: ErrorPayload(message: "type requires text"))
|
|
424
424
|
}
|
|
425
|
+
let delaySeconds = Double(max(command.delayMs ?? 0, 0)) / 1000.0
|
|
426
|
+
let target: XCUIElement?
|
|
427
|
+
if let x = command.x, let y = command.y {
|
|
428
|
+
target = textInputAt(app: activeApp, x: x, y: y) ?? focusedTextInput(app: activeApp)
|
|
429
|
+
} else {
|
|
430
|
+
target = focusedTextInput(app: activeApp)
|
|
431
|
+
}
|
|
432
|
+
func typeIntoTarget(_ value: String) {
|
|
433
|
+
if let focused = target {
|
|
434
|
+
focused.typeText(value)
|
|
435
|
+
} else {
|
|
436
|
+
activeApp.typeText(value)
|
|
437
|
+
}
|
|
438
|
+
}
|
|
425
439
|
if command.clearFirst == true {
|
|
426
|
-
guard let focused =
|
|
427
|
-
|
|
440
|
+
guard let focused = target else {
|
|
441
|
+
let message =
|
|
442
|
+
(command.x != nil && command.y != nil)
|
|
443
|
+
? "no text input found at the provided coordinates to clear"
|
|
444
|
+
: "no focused text input to clear"
|
|
445
|
+
return Response(ok: false, error: ErrorPayload(message: message))
|
|
428
446
|
}
|
|
429
447
|
clearTextInput(focused)
|
|
430
|
-
focused.typeText(text)
|
|
431
|
-
return Response(ok: true, data: DataPayload(message: "typed"))
|
|
432
448
|
}
|
|
433
|
-
if
|
|
434
|
-
|
|
449
|
+
if delaySeconds > 0 && text.count > 1 {
|
|
450
|
+
let chunks = Array(text)
|
|
451
|
+
for (index, character) in chunks.enumerated() {
|
|
452
|
+
typeIntoTarget(String(character))
|
|
453
|
+
if index + 1 < chunks.count {
|
|
454
|
+
Thread.sleep(forTimeInterval: delaySeconds)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
435
457
|
} else {
|
|
436
|
-
|
|
458
|
+
typeIntoTarget(text)
|
|
437
459
|
}
|
|
438
460
|
return Response(ok: true, data: DataPayload(message: "typed"))
|
|
461
|
+
case .interactionFrame:
|
|
462
|
+
let frame = resolvedTouchReferenceFrame(app: activeApp, appFrame: activeApp.frame)
|
|
463
|
+
return Response(
|
|
464
|
+
ok: true,
|
|
465
|
+
data: DataPayload(
|
|
466
|
+
x: frame.minX,
|
|
467
|
+
y: frame.minY,
|
|
468
|
+
referenceWidth: frame.width,
|
|
469
|
+
referenceHeight: frame.height
|
|
470
|
+
)
|
|
471
|
+
)
|
|
439
472
|
case .swipe:
|
|
440
473
|
guard let direction = command.direction else {
|
|
441
474
|
return Response(ok: false, error: ErrorPayload(message: "swipe requires direction"))
|
|
442
475
|
}
|
|
443
|
-
|
|
476
|
+
var executedFrame: DragVisualizationFrame?
|
|
444
477
|
let timing = measureGesture {
|
|
445
478
|
withTemporaryScrollIdleTimeoutIfSupported(activeApp) {
|
|
446
|
-
swipe(
|
|
479
|
+
executedFrame = swipe(
|
|
480
|
+
app: activeApp,
|
|
481
|
+
direction: direction
|
|
482
|
+
)
|
|
447
483
|
}
|
|
448
484
|
}
|
|
485
|
+
guard let dragFrame = executedFrame else {
|
|
486
|
+
return Response(ok: false, error: ErrorPayload(message: "swipe is only supported on tvOS"))
|
|
487
|
+
}
|
|
449
488
|
return Response(
|
|
450
489
|
ok: true,
|
|
451
490
|
data: DataPayload(
|
|
452
491
|
message: "swiped",
|
|
453
492
|
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
454
493
|
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
455
|
-
|
|
456
|
-
|
|
494
|
+
x: dragFrame.x,
|
|
495
|
+
y: dragFrame.y,
|
|
496
|
+
x2: dragFrame.x2,
|
|
497
|
+
y2: dragFrame.y2,
|
|
498
|
+
referenceWidth: dragFrame.referenceWidth,
|
|
499
|
+
referenceHeight: dragFrame.referenceHeight
|
|
457
500
|
)
|
|
458
501
|
)
|
|
459
502
|
case .findText:
|
|
@@ -510,18 +553,43 @@ extension RunnerTests {
|
|
|
510
553
|
// Return path relative to app container root (tmp/ maps to NSTemporaryDirectory)
|
|
511
554
|
return Response(ok: true, data: DataPayload(message: "tmp/\(fileName)"))
|
|
512
555
|
#endif
|
|
513
|
-
case .back:
|
|
514
|
-
if
|
|
515
|
-
|
|
556
|
+
case .back, .backInApp:
|
|
557
|
+
if tapInAppBackControl(app: activeApp) {
|
|
558
|
+
let message = command.command == .back ? "back" : "backInApp"
|
|
559
|
+
return Response(ok: true, data: DataPayload(message: message))
|
|
560
|
+
}
|
|
561
|
+
return Response(ok: false, error: ErrorPayload(message: "in-app back control is not available"))
|
|
562
|
+
case .backSystem:
|
|
563
|
+
if performSystemBackAction(app: activeApp) {
|
|
564
|
+
return Response(ok: true, data: DataPayload(message: "backSystem"))
|
|
516
565
|
}
|
|
517
|
-
|
|
518
|
-
return Response(ok: true, data: DataPayload(message: "back"))
|
|
566
|
+
return Response(ok: false, error: ErrorPayload(message: "system back is not available"))
|
|
519
567
|
case .home:
|
|
520
568
|
pressHomeButton()
|
|
521
569
|
return Response(ok: true, data: DataPayload(message: "home"))
|
|
522
570
|
case .appSwitcher:
|
|
523
571
|
performAppSwitcherGesture(app: activeApp)
|
|
524
572
|
return Response(ok: true, data: DataPayload(message: "appSwitcher"))
|
|
573
|
+
case .keyboardDismiss:
|
|
574
|
+
let result = dismissKeyboard(app: activeApp)
|
|
575
|
+
if result.wasVisible && !result.dismissed {
|
|
576
|
+
return Response(
|
|
577
|
+
ok: false,
|
|
578
|
+
error: ErrorPayload(
|
|
579
|
+
code: "UNSUPPORTED_OPERATION",
|
|
580
|
+
message: "Unable to dismiss the iOS keyboard without a native dismiss gesture or control"
|
|
581
|
+
)
|
|
582
|
+
)
|
|
583
|
+
}
|
|
584
|
+
return Response(
|
|
585
|
+
ok: true,
|
|
586
|
+
data: DataPayload(
|
|
587
|
+
message: "keyboardDismiss",
|
|
588
|
+
visible: result.visible,
|
|
589
|
+
wasVisible: result.wasVisible,
|
|
590
|
+
dismissed: result.dismissed
|
|
591
|
+
)
|
|
592
|
+
)
|
|
525
593
|
case .alert:
|
|
526
594
|
let action = (command.action ?? "get").lowercased()
|
|
527
595
|
let alert = activeApp.alerts.firstMatch
|
|
@@ -17,14 +17,9 @@ extension RunnerTests {
|
|
|
17
17
|
let referenceHeight: Double
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
struct GestureReferenceFrame {
|
|
21
|
-
let referenceWidth: Double
|
|
22
|
-
let referenceHeight: Double
|
|
23
|
-
}
|
|
24
|
-
|
|
25
20
|
// MARK: - Navigation Gestures
|
|
26
21
|
|
|
27
|
-
func
|
|
22
|
+
func tapInAppBackControl(app: XCUIApplication) -> Bool {
|
|
28
23
|
#if os(macOS)
|
|
29
24
|
if let back = macOSNavigationBackElement(app: app) {
|
|
30
25
|
tapElementCenter(app: app, element: back)
|
|
@@ -37,7 +32,7 @@ extension RunnerTests {
|
|
|
37
32
|
back.tap()
|
|
38
33
|
return true
|
|
39
34
|
}
|
|
40
|
-
return
|
|
35
|
+
return false
|
|
41
36
|
#endif
|
|
42
37
|
}
|
|
43
38
|
|
|
@@ -51,6 +46,18 @@ extension RunnerTests {
|
|
|
51
46
|
start.press(forDuration: 0.05, thenDragTo: end)
|
|
52
47
|
}
|
|
53
48
|
|
|
49
|
+
func performSystemBackAction(app: XCUIApplication) -> Bool {
|
|
50
|
+
#if os(macOS)
|
|
51
|
+
return false
|
|
52
|
+
#else
|
|
53
|
+
if pressTvRemoteMenuIfAvailable() {
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
performBackGesture(app: app)
|
|
57
|
+
return true
|
|
58
|
+
#endif
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
func performAppSwitcherGesture(app: XCUIApplication) {
|
|
55
62
|
if performTvRemoteAppSwitcherIfAvailable() {
|
|
56
63
|
return
|
|
@@ -161,19 +168,114 @@ extension RunnerTests {
|
|
|
161
168
|
element.typeText(deletes)
|
|
162
169
|
}
|
|
163
170
|
|
|
164
|
-
func
|
|
165
|
-
let
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.
|
|
169
|
-
|
|
171
|
+
func textInputAt(app: XCUIApplication, x: Double, y: Double) -> XCUIElement? {
|
|
172
|
+
let point = CGPoint(x: x, y: y)
|
|
173
|
+
var matched: XCUIElement?
|
|
174
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
175
|
+
// Prefer the smallest matching field so nested editable controls win over large containers.
|
|
176
|
+
let candidates = app.descendants(matching: .any).allElementsBoundByIndex
|
|
177
|
+
.filter { element in
|
|
178
|
+
guard element.exists else { return false }
|
|
179
|
+
switch element.elementType {
|
|
180
|
+
case .textField, .secureTextField, .searchField, .textView:
|
|
181
|
+
let frame = element.frame
|
|
182
|
+
return !frame.isEmpty && frame.contains(point)
|
|
183
|
+
default:
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
.sorted { left, right in
|
|
188
|
+
let leftArea = max(1, left.frame.width * left.frame.height)
|
|
189
|
+
let rightArea = max(1, right.frame.width * right.frame.height)
|
|
190
|
+
if leftArea != rightArea {
|
|
191
|
+
return leftArea < rightArea
|
|
192
|
+
}
|
|
193
|
+
if left.frame.minY != right.frame.minY {
|
|
194
|
+
return left.frame.minY < right.frame.minY
|
|
195
|
+
}
|
|
196
|
+
if left.frame.minX != right.frame.minX {
|
|
197
|
+
return left.frame.minX < right.frame.minX
|
|
198
|
+
}
|
|
199
|
+
return left.elementType.rawValue < right.elementType.rawValue
|
|
200
|
+
}
|
|
201
|
+
matched = candidates.first
|
|
202
|
+
})
|
|
203
|
+
if let exceptionMessage {
|
|
204
|
+
NSLog(
|
|
205
|
+
"AGENT_DEVICE_RUNNER_TEXT_INPUT_AT_POINT_IGNORED_EXCEPTION=%@",
|
|
206
|
+
exceptionMessage
|
|
207
|
+
)
|
|
208
|
+
return nil
|
|
209
|
+
}
|
|
210
|
+
return matched
|
|
211
|
+
}
|
|
170
212
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
213
|
+
func focusedTextInput(app: XCUIApplication) -> XCUIElement? {
|
|
214
|
+
var focused: XCUIElement?
|
|
215
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
216
|
+
let candidate = app
|
|
217
|
+
.descendants(matching: .any)
|
|
218
|
+
.matching(NSPredicate(format: "hasKeyboardFocus == 1"))
|
|
219
|
+
.firstMatch
|
|
220
|
+
guard candidate.exists else { return }
|
|
221
|
+
|
|
222
|
+
switch candidate.elementType {
|
|
223
|
+
case .textField, .secureTextField, .searchField, .textView:
|
|
224
|
+
focused = candidate
|
|
225
|
+
default:
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
if let exceptionMessage {
|
|
230
|
+
NSLog(
|
|
231
|
+
"AGENT_DEVICE_RUNNER_FOCUSED_INPUT_QUERY_IGNORED_EXCEPTION=%@",
|
|
232
|
+
exceptionMessage
|
|
233
|
+
)
|
|
175
234
|
return nil
|
|
176
235
|
}
|
|
236
|
+
return focused
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func isKeyboardVisible(app: XCUIApplication) -> Bool {
|
|
240
|
+
let keyboard = app.keyboards.firstMatch
|
|
241
|
+
return keyboard.exists && !keyboard.frame.isEmpty
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
func dismissKeyboard(app: XCUIApplication) -> (wasVisible: Bool, dismissed: Bool, visible: Bool) {
|
|
245
|
+
let wasVisible = isKeyboardVisible(app: app)
|
|
246
|
+
guard wasVisible else {
|
|
247
|
+
return (wasVisible: false, dismissed: false, visible: false)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let keyboard = app.keyboards.firstMatch
|
|
251
|
+
keyboard.swipeDown()
|
|
252
|
+
sleepFor(0.2)
|
|
253
|
+
if !isKeyboardVisible(app: app) {
|
|
254
|
+
return (wasVisible: true, dismissed: true, visible: false)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if tapKeyboardDismissControl(app: app) {
|
|
258
|
+
sleepFor(0.2)
|
|
259
|
+
let visible = isKeyboardVisible(app: app)
|
|
260
|
+
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return (wasVisible: true, dismissed: false, visible: isKeyboardVisible(app: app))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private func tapKeyboardDismissControl(app: XCUIApplication) -> Bool {
|
|
267
|
+
for label in ["Hide keyboard", "Dismiss keyboard"] {
|
|
268
|
+
let candidates = [
|
|
269
|
+
app.keyboards.buttons[label],
|
|
270
|
+
app.keyboards.keys[label],
|
|
271
|
+
app.toolbars.buttons[label],
|
|
272
|
+
]
|
|
273
|
+
if let hittable = candidates.first(where: { $0.exists && $0.isHittable }) {
|
|
274
|
+
hittable.tap()
|
|
275
|
+
return true
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return false
|
|
177
279
|
}
|
|
178
280
|
|
|
179
281
|
private func moveCaretToEnd(element: XCUIElement) {
|
|
@@ -322,7 +424,7 @@ extension RunnerTests {
|
|
|
322
424
|
)
|
|
323
425
|
}
|
|
324
426
|
|
|
325
|
-
|
|
427
|
+
func resolvedTouchReferenceFrame(app: XCUIApplication, appFrame: CGRect) -> CGRect {
|
|
326
428
|
let window = app.windows.firstMatch
|
|
327
429
|
let windowFrame = window.frame
|
|
328
430
|
if window.exists && !windowFrame.isEmpty {
|
|
@@ -334,14 +436,6 @@ extension RunnerTests {
|
|
|
334
436
|
return CGRect(x: 0, y: 0, width: 0, height: 0)
|
|
335
437
|
}
|
|
336
438
|
|
|
337
|
-
func resolvedGestureReferenceFrame(app: XCUIApplication) -> GestureReferenceFrame {
|
|
338
|
-
let frame = resolvedTouchReferenceFrame(app: app, appFrame: app.frame)
|
|
339
|
-
return GestureReferenceFrame(
|
|
340
|
-
referenceWidth: frame.width,
|
|
341
|
-
referenceHeight: frame.height
|
|
342
|
-
)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
439
|
func runSeries(count: Int, pauseMs: Double, operation: (Int) -> Void) {
|
|
346
440
|
let total = max(count, 1)
|
|
347
441
|
let pause = max(pauseMs, 0)
|
|
@@ -353,39 +447,36 @@ extension RunnerTests {
|
|
|
353
447
|
}
|
|
354
448
|
}
|
|
355
449
|
|
|
356
|
-
func swipe(app: XCUIApplication, direction:
|
|
450
|
+
func swipe(app: XCUIApplication, direction: String) -> DragVisualizationFrame? {
|
|
357
451
|
if performTvRemoteSwipeIfAvailable(direction: direction) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
case .down:
|
|
370
|
-
start.press(forDuration: 0.1, thenDragTo: end)
|
|
371
|
-
case .left:
|
|
372
|
-
right.press(forDuration: 0.1, thenDragTo: left)
|
|
373
|
-
case .right:
|
|
374
|
-
left.press(forDuration: 0.1, thenDragTo: right)
|
|
452
|
+
let frame = resolvedTouchReferenceFrame(app: app, appFrame: app.frame)
|
|
453
|
+
let midX = frame.midX
|
|
454
|
+
let midY = frame.midY
|
|
455
|
+
return DragVisualizationFrame(
|
|
456
|
+
x: midX,
|
|
457
|
+
y: midY,
|
|
458
|
+
x2: midX,
|
|
459
|
+
y2: midY,
|
|
460
|
+
referenceWidth: frame.width,
|
|
461
|
+
referenceHeight: frame.height
|
|
462
|
+
)
|
|
375
463
|
}
|
|
464
|
+
return nil
|
|
376
465
|
}
|
|
377
466
|
|
|
378
|
-
private func performTvRemoteSwipeIfAvailable(direction:
|
|
467
|
+
private func performTvRemoteSwipeIfAvailable(direction: String) -> Bool {
|
|
379
468
|
#if os(tvOS)
|
|
380
469
|
switch direction {
|
|
381
|
-
case
|
|
470
|
+
case "up":
|
|
382
471
|
XCUIRemote.shared.press(.up)
|
|
383
|
-
case
|
|
472
|
+
case "down":
|
|
384
473
|
XCUIRemote.shared.press(.down)
|
|
385
|
-
case
|
|
474
|
+
case "left":
|
|
386
475
|
XCUIRemote.shared.press(.left)
|
|
387
|
-
case
|
|
476
|
+
case "right":
|
|
388
477
|
XCUIRemote.shared.press(.right)
|
|
478
|
+
default:
|
|
479
|
+
return false
|
|
389
480
|
}
|
|
390
481
|
return true
|
|
391
482
|
#else
|
|
@@ -461,5 +552,4 @@ extension RunnerTests {
|
|
|
461
552
|
let element = app.descendants(matching: .any).matching(predicate).firstMatch
|
|
462
553
|
return element.exists ? element : nil
|
|
463
554
|
}
|
|
464
|
-
|
|
465
555
|
}
|
|
@@ -152,7 +152,7 @@ extension RunnerTests {
|
|
|
152
152
|
|
|
153
153
|
func isReadOnlyCommand(_ command: Command) -> Bool {
|
|
154
154
|
switch command.command {
|
|
155
|
-
case .findText, .readText, .snapshot, .screenshot:
|
|
155
|
+
case .interactionFrame, .findText, .readText, .snapshot, .screenshot:
|
|
156
156
|
return true
|
|
157
157
|
case .alert:
|
|
158
158
|
let action = (command.action ?? "get").lowercased()
|
|
@@ -170,7 +170,18 @@ extension RunnerTests {
|
|
|
170
170
|
|
|
171
171
|
func isInteractionCommand(_ command: CommandType) -> Bool {
|
|
172
172
|
switch command {
|
|
173
|
-
case
|
|
173
|
+
case
|
|
174
|
+
.tap,
|
|
175
|
+
.longPress,
|
|
176
|
+
.drag,
|
|
177
|
+
.type,
|
|
178
|
+
.swipe,
|
|
179
|
+
.back,
|
|
180
|
+
.backInApp,
|
|
181
|
+
.backSystem,
|
|
182
|
+
.appSwitcher,
|
|
183
|
+
.keyboardDismiss,
|
|
184
|
+
.pinch:
|
|
174
185
|
return true
|
|
175
186
|
default:
|
|
176
187
|
return false
|
|
@@ -5,6 +5,7 @@ enum CommandType: String, Codable {
|
|
|
5
5
|
case mouseClick
|
|
6
6
|
case tapSeries
|
|
7
7
|
case longPress
|
|
8
|
+
case interactionFrame
|
|
8
9
|
case drag
|
|
9
10
|
case dragSeries
|
|
10
11
|
case type
|
|
@@ -14,8 +15,11 @@ enum CommandType: String, Codable {
|
|
|
14
15
|
case snapshot
|
|
15
16
|
case screenshot
|
|
16
17
|
case back
|
|
18
|
+
case backInApp
|
|
19
|
+
case backSystem
|
|
17
20
|
case home
|
|
18
21
|
case appSwitcher
|
|
22
|
+
case keyboardDismiss
|
|
19
23
|
case alert
|
|
20
24
|
case pinch
|
|
21
25
|
case recordStart
|
|
@@ -24,17 +28,11 @@ enum CommandType: String, Codable {
|
|
|
24
28
|
case shutdown
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
enum SwipeDirection: String, Codable {
|
|
28
|
-
case up
|
|
29
|
-
case down
|
|
30
|
-
case left
|
|
31
|
-
case right
|
|
32
|
-
}
|
|
33
|
-
|
|
34
31
|
struct Command: Codable {
|
|
35
32
|
let command: CommandType
|
|
36
33
|
let appBundleId: String?
|
|
37
34
|
let text: String?
|
|
35
|
+
let delayMs: Int?
|
|
38
36
|
let clearFirst: Bool?
|
|
39
37
|
let action: String?
|
|
40
38
|
let x: Double?
|
|
@@ -48,7 +46,7 @@ struct Command: Codable {
|
|
|
48
46
|
let x2: Double?
|
|
49
47
|
let y2: Double?
|
|
50
48
|
let durationMs: Double?
|
|
51
|
-
let direction:
|
|
49
|
+
let direction: String?
|
|
52
50
|
let scale: Double?
|
|
53
51
|
let outPath: String?
|
|
54
52
|
let fps: Int?
|
|
@@ -87,6 +85,9 @@ struct DataPayload: Codable {
|
|
|
87
85
|
let referenceWidth: Double?
|
|
88
86
|
let referenceHeight: Double?
|
|
89
87
|
let currentUptimeMs: Double?
|
|
88
|
+
let visible: Bool?
|
|
89
|
+
let wasVisible: Bool?
|
|
90
|
+
let dismissed: Bool?
|
|
90
91
|
|
|
91
92
|
init(
|
|
92
93
|
message: String? = nil,
|
|
@@ -103,7 +104,10 @@ struct DataPayload: Codable {
|
|
|
103
104
|
y2: Double? = nil,
|
|
104
105
|
referenceWidth: Double? = nil,
|
|
105
106
|
referenceHeight: Double? = nil,
|
|
106
|
-
currentUptimeMs: Double? = nil
|
|
107
|
+
currentUptimeMs: Double? = nil,
|
|
108
|
+
visible: Bool? = nil,
|
|
109
|
+
wasVisible: Bool? = nil,
|
|
110
|
+
dismissed: Bool? = nil
|
|
107
111
|
) {
|
|
108
112
|
self.message = message
|
|
109
113
|
self.text = text
|
|
@@ -120,11 +124,20 @@ struct DataPayload: Codable {
|
|
|
120
124
|
self.referenceWidth = referenceWidth
|
|
121
125
|
self.referenceHeight = referenceHeight
|
|
122
126
|
self.currentUptimeMs = currentUptimeMs
|
|
127
|
+
self.visible = visible
|
|
128
|
+
self.wasVisible = wasVisible
|
|
129
|
+
self.dismissed = dismissed
|
|
123
130
|
}
|
|
124
131
|
}
|
|
125
132
|
|
|
126
133
|
struct ErrorPayload: Codable {
|
|
134
|
+
let code: String?
|
|
127
135
|
let message: String
|
|
136
|
+
|
|
137
|
+
init(code: String? = nil, message: String) {
|
|
138
|
+
self.code = code
|
|
139
|
+
self.message = message
|
|
140
|
+
}
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
struct SnapshotRect: Codable {
|