agent-device 0.20.3 → 0.20.4
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/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.3.apk → agent-device-android-ime-helper-0.20.4.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.4.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.3.manifest.json → agent-device-android-ime-helper-0.20.4.manifest.json} +4 -4
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.4.apk +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.4.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.3.manifest.json → agent-device-android-snapshot-helper-0.20.4.manifest.json} +6 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +57 -4
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +45 -9
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +73 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +1 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +3 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SequenceExecution.swift +1 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +8 -2
- package/dist/src/agent-browser-tool.js +1 -1
- package/dist/src/agent-device-client.js +2 -2
- package/dist/src/android-adb.d.ts +1 -1
- package/dist/src/android.js +1 -1
- package/dist/src/app-helpers.js +1 -1
- package/dist/src/app-inventory.d.ts +4 -0
- package/dist/src/app-lifecycle.js +7 -7
- package/dist/src/app-resolution.js +1 -1
- package/dist/src/apps.js +5 -5
- package/dist/src/boot-diagnostics.js +1 -1
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +27 -19
- package/dist/src/cli-runner.js +1 -1
- package/dist/src/cli.js +15 -14
- package/dist/src/client-metro.js +1 -1
- package/dist/src/{tv-remote.d.ts → cloud-artifacts.d.ts} +109 -126
- package/dist/src/command-metadata.js +1 -1
- package/dist/src/command-schema.js +1 -1
- package/dist/src/command-surface.js +1 -1
- package/dist/src/context.js +1 -1
- package/dist/src/contracts.d.ts +1 -1
- package/dist/src/debug-symbols.js +4 -4
- package/dist/src/device-input-state.d.ts +2 -2
- package/dist/src/device-input-state.js +1 -1
- package/dist/src/device.js +1 -9
- package/dist/src/devices2.js +1 -1
- package/dist/src/entities.js +1 -0
- package/dist/src/find.js +1 -1
- package/dist/src/finders.d.ts +1 -1
- package/dist/src/finders.js +1 -1
- package/dist/src/generic.js +1 -1
- package/dist/src/helper.js +2 -2
- package/dist/src/ime-lifecycle.js +9 -0
- package/dist/src/index.d.ts +592 -555
- package/dist/src/input-actions.js +4 -4
- package/dist/src/install-artifact.js +4 -4
- package/dist/src/install-artifact2.js +1 -1
- package/dist/src/install-source2.js +1 -1
- package/dist/src/interaction-snapshot.js +1 -1
- package/dist/src/interaction.d.ts +7 -4
- package/dist/src/interaction.js +1 -1
- package/dist/src/interactor.js +2 -2
- package/dist/src/interactor2.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +14 -14
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/lease-context.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/limrun.d.ts +103 -198
- package/dist/src/limrun.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/logcat.js +1 -1
- package/dist/src/manifest.js +1 -1
- package/dist/src/metro.d.ts +1 -1
- package/dist/src/parser.js +2 -0
- package/dist/src/path-resolution.js +1 -1
- package/dist/src/physical-device-control.js +1 -1
- package/dist/src/plist-xml.js +1 -0
- package/dist/src/plist.js +1 -1
- package/dist/src/png-worker-contract.js +1 -1
- package/dist/src/process-lock.js +1 -1
- package/dist/src/provider-limrun-runtime.js +1 -0
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-trace.js +1 -1
- package/dist/src/register-builtins.js +1 -1
- package/dist/src/registry.js +22 -22
- package/dist/src/remote-config2.js +1 -1
- package/dist/src/reporting.js +7 -7
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-disposal.js +1 -1
- package/dist/src/runner-lease.js +1 -1
- package/dist/src/runner-provider.js +1 -1
- package/dist/src/runner-sequence.js +1 -1
- package/dist/src/runtime-transport.js +1 -1
- package/dist/src/runtime.js +3 -3
- package/dist/src/screenshot-geometry.js +1 -0
- package/dist/src/screenshot-result.js +6 -0
- package/dist/src/sdk-android-adb.d.ts +5 -6
- package/dist/src/sdk-android-adb.js +3 -3
- package/dist/src/sdk-batch-runner.d.ts +2 -2
- package/dist/src/sdk-batch-runner.js +3 -3
- package/dist/src/sdk-contracts.d.ts +21 -25
- package/dist/src/sdk-contracts.js +2 -2
- package/dist/src/sdk-finders.d.ts +1 -1
- package/dist/src/sdk-finders.js +1 -1
- package/dist/src/sdk-io.js +1 -1
- package/dist/src/sdk-remote-config.d.ts +19 -4
- package/dist/src/sdk-remote-config.js +1 -1
- package/dist/src/sdk-selectors.js +1 -1
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selector-vocabulary.js +1 -1
- package/dist/src/selectors.d.ts +1 -1
- package/dist/src/selectors.js +1 -1
- package/dist/src/server.js +2 -2
- package/dist/src/session-target-evidence.js +1 -1
- package/dist/src/session.js +8 -8
- package/dist/src/setting-state.js +1 -1
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot.js +1 -1
- package/dist/src/snapshot2.js +1 -1
- package/dist/src/src.js +1 -0
- package/dist/src/tool-provider.js +3 -3
- package/dist/src/tool-provider2.js +1 -1
- package/dist/src/tool-provider3.js +1 -1
- package/dist/src/update-check.js +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/viewport-dimension.js +14 -0
- package/package.json +17 -7
- package/server.json +2 -2
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.3.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.3.apk +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.3.apk.sha256 +0 -1
- package/dist/src/android-input-method-overlays.js +0 -1
- package/dist/src/android-input-ownership.js +0 -1
- package/dist/src/apple-multitouch-support.js +0 -1
- package/dist/src/click-button.d.ts +0 -5
- package/dist/src/compatibility-policy.js +0 -1
- package/dist/src/gesture-plan-types.js +0 -1
- package/dist/src/gesture-plan.js +0 -1
- package/dist/src/launch-console.js +0 -1
- package/dist/src/recording-export-quality.js +0 -14
- package/dist/src/rolldown-runtime.js +0 -1
- package/dist/src/runtime2.js +0 -1
- package/dist/src/sdk-app-inventory.d.ts +0 -4
- package/dist/src/sdk-app-inventory.js +0 -1
- package/dist/src/snapshot-diagnostics.js +0 -6
- package/dist/src/xml.js +0 -2
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
019ebbda52976df5cff05f9e5a764c8d6e280be3640e8dea0a2d7cca6c25021f agent-device-android-ime-helper-0.20.4.apk
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "android-ime-helper",
|
|
3
|
-
"version": "0.20.
|
|
4
|
-
"assetName": "agent-device-android-ime-helper-0.20.
|
|
5
|
-
"sha256": "
|
|
3
|
+
"version": "0.20.4",
|
|
4
|
+
"assetName": "agent-device-android-ime-helper-0.20.4.apk",
|
|
5
|
+
"sha256": "019ebbda52976df5cff05f9e5a764c8d6e280be3640e8dea0a2d7cca6c25021f",
|
|
6
6
|
"packageName": "com.callstack.agentdevice.imehelper",
|
|
7
|
-
"versionCode":
|
|
7
|
+
"versionCode": 20004,
|
|
8
8
|
"serviceComponent": "com.callstack.agentdevice.imehelper/.TestInputMethodService",
|
|
9
9
|
"broadcastProtocol": "android-ime-helper-v1"
|
|
10
10
|
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e3799afe5549776e047744efe46939a9146a4742a5328bb2e22f0b9d6abd2b52 agent-device-android-snapshot-helper-0.20.4.apk
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "android-snapshot-helper",
|
|
3
|
-
"version": "0.20.
|
|
4
|
-
"releaseTag": "v0.20.
|
|
5
|
-
"assetName": "agent-device-android-snapshot-helper-0.20.
|
|
3
|
+
"version": "0.20.4",
|
|
4
|
+
"releaseTag": "v0.20.4",
|
|
5
|
+
"assetName": "agent-device-android-snapshot-helper-0.20.4.apk",
|
|
6
6
|
"apkUrl": null,
|
|
7
|
-
"sha256": "
|
|
8
|
-
"checksumName": "agent-device-android-snapshot-helper-0.20.
|
|
7
|
+
"sha256": "e3799afe5549776e047744efe46939a9146a4742a5328bb2e22f0b9d6abd2b52",
|
|
8
|
+
"checksumName": "agent-device-android-snapshot-helper-0.20.4.apk.sha256",
|
|
9
9
|
"packageName": "com.callstack.agentdevice.snapshothelper",
|
|
10
|
-
"versionCode":
|
|
10
|
+
"versionCode": 20004,
|
|
11
11
|
"instrumentationRunner": "com.callstack.agentdevice.snapshothelper/.SnapshotInstrumentation",
|
|
12
12
|
"minSdk": 23,
|
|
13
13
|
"targetSdk": 36,
|
|
@@ -922,6 +922,49 @@ extension RunnerTests {
|
|
|
922
922
|
x: touchPoint.x,
|
|
923
923
|
y: touchPoint.y
|
|
924
924
|
)
|
|
925
|
+
var fallback: GestureFallback?
|
|
926
|
+
if command.synthesized == true {
|
|
927
|
+
let policyKind = SynthesizedGesturePolicyKind.coordinateTap
|
|
928
|
+
let context = synthesizedCoordinateContext(
|
|
929
|
+
app: activeApp,
|
|
930
|
+
policy: synthesizedGesturePolicy(policyKind)
|
|
931
|
+
)
|
|
932
|
+
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
933
|
+
synthesizedTapAt(
|
|
934
|
+
app: activeApp,
|
|
935
|
+
x: touchPoint.x,
|
|
936
|
+
y: touchPoint.y,
|
|
937
|
+
context: context
|
|
938
|
+
)
|
|
939
|
+
}
|
|
940
|
+
if case .performed = outcome {
|
|
941
|
+
logSynthesizedGesturePolicyDecision(
|
|
942
|
+
kind: policyKind,
|
|
943
|
+
context: context,
|
|
944
|
+
fallbackAttempted: false
|
|
945
|
+
)
|
|
946
|
+
if isTextEntry {
|
|
947
|
+
waitForTextEntryReadinessAfterTap(app: activeApp, element: element)
|
|
948
|
+
}
|
|
949
|
+
return gestureResponse(
|
|
950
|
+
message: match.usedNonHittableFallback
|
|
951
|
+
? "tapped via non-hittable coordinate fallback"
|
|
952
|
+
: "tapped",
|
|
953
|
+
timing: timing,
|
|
954
|
+
frame: .touch(touchFrame),
|
|
955
|
+
maestroNonHittableCoordinateFallbackUsed:
|
|
956
|
+
command.allowNonHittableCoordinateFallback == true
|
|
957
|
+
? match.usedNonHittableFallback
|
|
958
|
+
: nil
|
|
959
|
+
)
|
|
960
|
+
}
|
|
961
|
+
logSynthesizedGesturePolicyDecision(
|
|
962
|
+
kind: policyKind,
|
|
963
|
+
context: context,
|
|
964
|
+
fallbackAttempted: true
|
|
965
|
+
)
|
|
966
|
+
fallback = gestureFallback(strategy: "xctest-coordinate-tap", from: outcome)
|
|
967
|
+
}
|
|
925
968
|
let (timing, outcome) = performGesture(activeApp) {
|
|
926
969
|
if expectedPoint != nil || match.usedNonHittableFallback {
|
|
927
970
|
// Maestro compatibility: RN E2E backdoor controls can be 1x1 and
|
|
@@ -942,6 +985,7 @@ extension RunnerTests {
|
|
|
942
985
|
message: match.usedNonHittableFallback ? "tapped via non-hittable coordinate fallback" : "tapped",
|
|
943
986
|
timing: timing,
|
|
944
987
|
frame: .touch(touchFrame),
|
|
988
|
+
fallback: fallback,
|
|
945
989
|
maestroNonHittableCoordinateFallbackUsed:
|
|
946
990
|
command.allowNonHittableCoordinateFallback == true
|
|
947
991
|
? match.usedNonHittableFallback
|
|
@@ -954,7 +998,10 @@ extension RunnerTests {
|
|
|
954
998
|
var fallback: GestureFallback?
|
|
955
999
|
if command.synthesized == true {
|
|
956
1000
|
let policyKind = SynthesizedGesturePolicyKind.coordinateTap
|
|
957
|
-
let context = synthesizedCoordinateContext(
|
|
1001
|
+
let context = synthesizedCoordinateContext(
|
|
1002
|
+
app: activeApp,
|
|
1003
|
+
policy: synthesizedGesturePolicy(policyKind)
|
|
1004
|
+
)
|
|
958
1005
|
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
959
1006
|
synthesizedTapAt(app: activeApp, x: x, y: y, context: context)
|
|
960
1007
|
}
|
|
@@ -1045,7 +1092,10 @@ extension RunnerTests {
|
|
|
1045
1092
|
)
|
|
1046
1093
|
}
|
|
1047
1094
|
let scrollPolicyKind = SynthesizedGesturePolicyKind.scroll
|
|
1048
|
-
guard let scrollContext = synthesizedCoordinateContext(
|
|
1095
|
+
guard let scrollContext = synthesizedCoordinateContext(
|
|
1096
|
+
app: activeApp,
|
|
1097
|
+
policy: synthesizedGesturePolicy(scrollPolicyKind)
|
|
1098
|
+
) else {
|
|
1049
1099
|
return Response(
|
|
1050
1100
|
ok: false,
|
|
1051
1101
|
error: ErrorPayload(message: "scroll could not resolve a usable interaction frame")
|
|
@@ -1439,7 +1489,10 @@ extension RunnerTests {
|
|
|
1439
1489
|
var fallback: GestureFallback?
|
|
1440
1490
|
if synthesized {
|
|
1441
1491
|
let durationMs = min(max(durationMs ?? 250, 16), 10000)
|
|
1442
|
-
let context = synthesizedCoordinateContext(
|
|
1492
|
+
let context = synthesizedCoordinateContext(
|
|
1493
|
+
app: activeApp,
|
|
1494
|
+
policy: synthesizedGesturePolicy(synthesizedPolicyKind)
|
|
1495
|
+
)
|
|
1443
1496
|
let (timing, outcome) = performGesture(activeApp, idleTimeout: false) {
|
|
1444
1497
|
synthesizedDragAt(
|
|
1445
1498
|
app: activeApp,
|
|
@@ -1495,7 +1548,7 @@ extension RunnerTests {
|
|
|
1495
1548
|
) -> Response? {
|
|
1496
1549
|
#if os(iOS)
|
|
1497
1550
|
let policy = synthesizedGesturePolicy(policyKind)
|
|
1498
|
-
let context = context ?? synthesizedCoordinateContext(policy: policy)
|
|
1551
|
+
let context = context ?? synthesizedCoordinateContext(app: activeApp, policy: policy)
|
|
1499
1552
|
guard let plan = axFreeSynthesizedDragPlan(
|
|
1500
1553
|
app: activeApp,
|
|
1501
1554
|
x: x,
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift
CHANGED
|
@@ -617,7 +617,10 @@ extension RunnerTests {
|
|
|
617
617
|
)
|
|
618
618
|
}
|
|
619
619
|
let orientation = Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
620
|
-
guard let context = context ?? synthesizedCoordinateContext(
|
|
620
|
+
guard let context = context ?? synthesizedCoordinateContext(
|
|
621
|
+
app: app,
|
|
622
|
+
policy: synthesizedGesturePolicy(.synthesizedDrag)
|
|
623
|
+
) else {
|
|
621
624
|
return .unsupported(
|
|
622
625
|
message: "synthesized coordinate drag could not resolve a finite screen frame",
|
|
623
626
|
hint: "Retry after the app is foregrounded, or use a plain screenshot to choose coordinates."
|
|
@@ -680,7 +683,10 @@ extension RunnerTests {
|
|
|
680
683
|
)
|
|
681
684
|
}
|
|
682
685
|
let orientation = Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
683
|
-
guard let context = context ?? synthesizedCoordinateContext(
|
|
686
|
+
guard let context = context ?? synthesizedCoordinateContext(
|
|
687
|
+
app: app,
|
|
688
|
+
policy: synthesizedGesturePolicy(.coordinateTap)
|
|
689
|
+
) else {
|
|
684
690
|
return .unsupported(
|
|
685
691
|
message: "synthesized coordinate tap could not resolve a finite screen frame",
|
|
686
692
|
hint: "Retry after the app is foregrounded, or use a plain screenshot to choose coordinates."
|
|
@@ -852,7 +858,10 @@ extension RunnerTests {
|
|
|
852
858
|
context: SynthesizedCoordinateContext? = nil
|
|
853
859
|
) -> SynthesizedDragPlan? {
|
|
854
860
|
#if os(iOS)
|
|
855
|
-
let context = context ?? synthesizedCoordinateContext(
|
|
861
|
+
let context = context ?? synthesizedCoordinateContext(
|
|
862
|
+
app: app,
|
|
863
|
+
policy: synthesizedGesturePolicy(.synthesizedDrag)
|
|
864
|
+
)
|
|
856
865
|
guard x.isFinite, y.isFinite, x2.isFinite, y2.isFinite,
|
|
857
866
|
let context
|
|
858
867
|
else {
|
|
@@ -892,11 +901,18 @@ extension RunnerTests {
|
|
|
892
901
|
)
|
|
893
902
|
}
|
|
894
903
|
|
|
895
|
-
func synthesizedCoordinateContext(
|
|
904
|
+
func synthesizedCoordinateContext(
|
|
905
|
+
app: XCUIApplication,
|
|
906
|
+
policy: SynthesizedGesturePolicy
|
|
907
|
+
) -> SynthesizedCoordinateContext? {
|
|
896
908
|
#if os(iOS)
|
|
897
909
|
let health = runnerAccessibilityHealth
|
|
898
|
-
|
|
899
|
-
|
|
910
|
+
let orientation = Int(
|
|
911
|
+
RunnerSynthesizedGesture.interfaceOrientation(forApplication: app)
|
|
912
|
+
)
|
|
913
|
+
guard let referenceFrame = orientedSynthesizedScreenshotReferenceFrame(
|
|
914
|
+
screenshotSize: XCUIScreen.main.screenshot().image.size,
|
|
915
|
+
interfaceOrientation: orientation
|
|
900
916
|
) else {
|
|
901
917
|
return nil
|
|
902
918
|
}
|
|
@@ -911,15 +927,35 @@ extension RunnerTests {
|
|
|
911
927
|
#endif
|
|
912
928
|
}
|
|
913
929
|
|
|
914
|
-
func
|
|
915
|
-
|
|
930
|
+
func orientedSynthesizedScreenshotReferenceFrame(
|
|
931
|
+
screenshotSize: CGSize,
|
|
932
|
+
interfaceOrientation: Int
|
|
933
|
+
) -> CGRect? {
|
|
934
|
+
// Physical iOS screenshots can retain portrait dimensions after the interface rotates,
|
|
935
|
+
// while accessibility frames remain in the logical landscape coordinate space.
|
|
916
936
|
guard screenshotSize.width.isFinite, screenshotSize.height.isFinite,
|
|
917
937
|
screenshotSize.width > 0,
|
|
918
938
|
screenshotSize.height > 0
|
|
919
939
|
else {
|
|
920
940
|
return nil
|
|
921
941
|
}
|
|
922
|
-
|
|
942
|
+
let isLandscape = interfaceOrientation == RunnerInterfaceOrientation.landscapeLeft
|
|
943
|
+
|| interfaceOrientation == RunnerInterfaceOrientation.landscapeRight
|
|
944
|
+
let isPortrait = interfaceOrientation == RunnerInterfaceOrientation.portrait
|
|
945
|
+
|| interfaceOrientation == RunnerInterfaceOrientation.portraitUpsideDown
|
|
946
|
+
let width: CGFloat
|
|
947
|
+
let height: CGFloat
|
|
948
|
+
if isLandscape {
|
|
949
|
+
width = max(screenshotSize.width, screenshotSize.height)
|
|
950
|
+
height = min(screenshotSize.width, screenshotSize.height)
|
|
951
|
+
} else if isPortrait {
|
|
952
|
+
width = min(screenshotSize.width, screenshotSize.height)
|
|
953
|
+
height = max(screenshotSize.width, screenshotSize.height)
|
|
954
|
+
} else {
|
|
955
|
+
width = screenshotSize.width
|
|
956
|
+
height = screenshotSize.height
|
|
957
|
+
}
|
|
958
|
+
return CGRect(x: 0, y: 0, width: width, height: height)
|
|
923
959
|
}
|
|
924
960
|
|
|
925
961
|
func synthesizedFrameAvoidingKeyboardWhenAllowed(
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift
CHANGED
|
@@ -2,6 +2,23 @@ import XCTest
|
|
|
2
2
|
|
|
3
3
|
private enum KeyboardDismissObservationTiming {
|
|
4
4
|
static let timeout: TimeInterval = 2
|
|
5
|
+
// #1542: dismissing the keyboard can trigger the host app's own content-offset
|
|
6
|
+
// adjustment (e.g. a ScrollView correcting the inset it grew to keep a focused
|
|
7
|
+
// field above the keyboard). That adjustment is a separate, unsynchronized
|
|
8
|
+
// animation the keyboard's own `waitForNonExistence` above knows nothing about:
|
|
9
|
+
// the keyboard AX element can vanish well before the app settles. The very next
|
|
10
|
+
// command is frequently a synthesized, AX-free drag (scroll/gesture — kept
|
|
11
|
+
// AX-free so it still works under #1105-family AX degradation), which has no
|
|
12
|
+
// XCTest quiescence wait of its own, so it can land mid-animation and net to
|
|
13
|
+
// zero. These bounds cap the settle wait this trades in.
|
|
14
|
+
// A spring-driven content-offset correction can pass through a near-zero-
|
|
15
|
+
// velocity inflection (the top of an overshoot) that two adjacent samples
|
|
16
|
+
// alone cannot distinguish from true rest. Requiring 3 consecutive matching
|
|
17
|
+
// samples demands ~2 full sample intervals of actual stillness before
|
|
18
|
+
// stopping early, so a momentary pause mid-animation cannot look settled.
|
|
19
|
+
static let settleTimeout: TimeInterval = 2.0
|
|
20
|
+
static let settleSampleInterval: TimeInterval = 0.15
|
|
21
|
+
static let settleRequiredConsecutiveMatches: Int = 3
|
|
5
22
|
}
|
|
6
23
|
|
|
7
24
|
extension RunnerTests {
|
|
@@ -24,6 +41,11 @@ extension RunnerTests {
|
|
|
24
41
|
#else
|
|
25
42
|
if tapKeyboardDismissControl(app: app) {
|
|
26
43
|
_ = keyboard.waitForNonExistence(timeout: KeyboardDismissObservationTiming.timeout)
|
|
44
|
+
waitForScreenshotStability(
|
|
45
|
+
timeout: KeyboardDismissObservationTiming.settleTimeout,
|
|
46
|
+
sampleInterval: KeyboardDismissObservationTiming.settleSampleInterval,
|
|
47
|
+
requiredConsecutiveMatches: KeyboardDismissObservationTiming.settleRequiredConsecutiveMatches
|
|
48
|
+
)
|
|
27
49
|
let visible = isKeyboardVisible(app: app)
|
|
28
50
|
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
29
51
|
}
|
|
@@ -32,6 +54,35 @@ extension RunnerTests {
|
|
|
32
54
|
#endif
|
|
33
55
|
}
|
|
34
56
|
|
|
57
|
+
// AX-free on purpose (screenshot bytes, not the accessibility tree) so it holds
|
|
58
|
+
// under the same AX degradation the synthesized gesture lane is built to survive.
|
|
59
|
+
// Bounded and self-terminating: returns as soon as `requiredConsecutiveMatches`
|
|
60
|
+
// consecutive samples match, so an already-settled screen (the common case)
|
|
61
|
+
// pays close to nothing. The stopping decision itself
|
|
62
|
+
// (`runnerScreenshotStabilitySettled`) is a pure function of the samples taken
|
|
63
|
+
// so far, so it is unit-testable without a real screenshot pipeline; this loop
|
|
64
|
+
// is the thin, untestable I/O shell around it.
|
|
65
|
+
private func waitForScreenshotStability(
|
|
66
|
+
timeout: TimeInterval,
|
|
67
|
+
sampleInterval: TimeInterval,
|
|
68
|
+
requiredConsecutiveMatches: Int
|
|
69
|
+
) {
|
|
70
|
+
let deadline = Date().addingTimeInterval(timeout)
|
|
71
|
+
var samples: [Data?] = [screenshotFingerprintForStabilityCheck()]
|
|
72
|
+
while Date() < deadline {
|
|
73
|
+
sleepFor(sampleInterval)
|
|
74
|
+
samples.append(screenshotFingerprintForStabilityCheck())
|
|
75
|
+
if runnerScreenshotStabilitySettled(samples, requiredConsecutiveMatches: requiredConsecutiveMatches) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private func screenshotFingerprintForStabilityCheck() -> Data? {
|
|
82
|
+
guard let image = captureRunnerFrame() else { return nil }
|
|
83
|
+
return runnerPngData(for: image)
|
|
84
|
+
}
|
|
85
|
+
|
|
35
86
|
func pressKeyboardReturn(app: XCUIApplication) -> (wasVisible: Bool, pressed: Bool, visible: Bool) {
|
|
36
87
|
#if os(tvOS)
|
|
37
88
|
return (wasVisible: false, pressed: pressTvRemote(.select), visible: false)
|
|
@@ -182,3 +233,25 @@ extension RunnerTests {
|
|
|
182
233
|
return frame.intersects(keyboardFrame) || abs(frame.maxY - keyboardFrame.minY) <= 80
|
|
183
234
|
}
|
|
184
235
|
}
|
|
236
|
+
|
|
237
|
+
/// True once the `requiredConsecutiveMatches` most recent samples are all present
|
|
238
|
+
/// and byte-identical — i.e. the screen held still across that whole run of
|
|
239
|
+
/// polls, not just the last two. A spring-driven content-offset correction can
|
|
240
|
+
/// pass through a near-zero-velocity inflection that two adjacent samples alone
|
|
241
|
+
/// cannot distinguish from true rest; requiring a longer run of matches demands
|
|
242
|
+
/// real elapsed stillness before stopping early. `nil` entries (a screenshot
|
|
243
|
+
/// capture that failed) never count toward a match, so a flaky capture cannot
|
|
244
|
+
/// look like stability; the caller's bounded loop still terminates on its
|
|
245
|
+
/// deadline regardless.
|
|
246
|
+
func runnerScreenshotStabilitySettled(
|
|
247
|
+
_ samples: [Data?],
|
|
248
|
+
requiredConsecutiveMatches: Int
|
|
249
|
+
) -> Bool {
|
|
250
|
+
guard requiredConsecutiveMatches >= 2, samples.count >= requiredConsecutiveMatches else {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
let window = samples.suffix(requiredConsecutiveMatches)
|
|
254
|
+
guard let first = window.first, let firstData = first else { return false }
|
|
255
|
+
return window.allSatisfy { $0 == firstData }
|
|
256
|
+
}
|
|
257
|
+
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift
CHANGED
|
@@ -118,6 +118,7 @@ extension RunnerTests {
|
|
|
118
118
|
}
|
|
119
119
|
let before = captureNavigationFallbackVisualState()
|
|
120
120
|
let context = synthesizedCoordinateContext(
|
|
121
|
+
app: app,
|
|
121
122
|
policy: synthesizedGesturePolicy(.coordinateTap)
|
|
122
123
|
)?.withReferenceFrame(frame)
|
|
123
124
|
let synthesized = performGesture(app, idleTimeout: false) {
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import XCTest
|
|
2
2
|
|
|
3
|
-
// Swift port of buildScrollGesturePlan from
|
|
3
|
+
// Swift port of buildScrollGesturePlan from packages/contracts/src/scroll-gesture.ts.
|
|
4
4
|
//
|
|
5
5
|
// This is a deliberate two-place invariant: the daemon keeps the TS implementation (for Android,
|
|
6
6
|
// recording, and reported-pixels), and the runner places the gesture with this Swift copy. The
|
|
7
|
-
// parity test vectors at the bottom of this file mirror
|
|
7
|
+
// parity test vectors at the bottom of this file mirror
|
|
8
|
+
// packages/contracts/src/scroll-gesture.test.ts —
|
|
8
9
|
// if you change the math in either language, update the other and both vector sets.
|
|
9
10
|
//
|
|
10
11
|
// All inputs here are positive (reference dims, travel, center), so Swift's `.rounded()`
|
|
@@ -40,7 +40,7 @@ extension RunnerTests {
|
|
|
40
40
|
? resolvedTouchVisualizationFrame(app: activeApp, x: firstStep.x!, y: firstStep.y!)
|
|
41
41
|
: nil
|
|
42
42
|
|
|
43
|
-
let synthesizedContext = synthesizedSequenceCoordinateContext(steps: steps)
|
|
43
|
+
let synthesizedContext = synthesizedSequenceCoordinateContext(steps: steps, app: activeApp)
|
|
44
44
|
|
|
45
45
|
let execution = assembleSequenceExecution(steps: steps) { _, step in
|
|
46
46
|
performSequenceStep(step, activeApp: activeApp, synthesizedContext: synthesizedContext)
|
|
@@ -108,9 +108,15 @@ func sequenceHasSynthesizedCoordinateStep(_ steps: [SequenceStep]) -> Bool {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
extension RunnerTests {
|
|
111
|
-
func synthesizedSequenceCoordinateContext(
|
|
111
|
+
func synthesizedSequenceCoordinateContext(
|
|
112
|
+
steps: [SequenceStep],
|
|
113
|
+
app: XCUIApplication
|
|
114
|
+
) -> SynthesizedCoordinateContext? {
|
|
112
115
|
guard sequenceHasSynthesizedCoordinateStep(steps) else { return nil }
|
|
113
|
-
return synthesizedCoordinateContext(
|
|
116
|
+
return synthesizedCoordinateContext(
|
|
117
|
+
app: app,
|
|
118
|
+
policy: synthesizedGesturePolicy(.synthesizedDrag)
|
|
119
|
+
)
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
func logSynthesizedGesturePolicyDecision(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{o as e}from"./png-worker-contract.js";import{_ as t,v as n}from"./sdk-contracts.js";import{T as r,j as i,v as a}from"./sdk-android-adb.js";import{c as o,i as s,o as c,t as l}from"./host-process.js";import{t as u}from"./process-lock.js";import d from"node:path";import f from"node:crypto";import p from"node:fs";import m from"node:os";const h=new Map;function g(e){let t=f.createHash(`sha256`);return t.update(d.resolve(e.stateDir)),t.update(`\0`),t.update(d.resolve(e.installDir)),`--agent-device-managed-web=${t.digest(`hex`).slice(0,16)}`}function _(e,t){let n=g(t),r=e?.trim();return r?T(r).includes(n)?r:`${r},${n}`:n}function v(e){return E(e.AGENT_BROWSER_IDLE_TIMEOUT_MS)??E(e.AGENT_DEVICE_WEB_IDLE_TIMEOUT_MS)??3e5}function y(e,t){if(e.pid!==process.pid&&D(e.command)){if(e.command.includes(g(t)))return{process:e,reason:`launch-marker`};if(O(t).some(t=>j(e,t)))return{process:e,reason:`managed-browser-home`}}}function b(e,t){let n=e.flatMap(e=>{let n=y(e,t);return n?[n]:[]});return{count:n.length,pids:n.map(e=>e.process.pid),processes:n}}async function x(e){return b(await w(),e)}async function S(e,t,n={}){let r=k([...n.openWebSessionNames??[]]);if(r.length>0)return A(`open-web-session`,{openWebSessionNames:r});let a=v(process.env),s=N(e);if(s!==void 0&&Date.now()-s<a)return A(`recent-browser-activity`,{idleTimeoutMs:a,latestActivityMs:s});let c=await w(),u=b(c,e),d=l(u.processes.map(e=>e.process.pid),c).map(e=>e.pid),f={...u,signalPids:d};return u.count===0?f:(i({level:`warn`,phase:`web_agent_browser_orphan_cleanup`,data:{reason:t,count:u.count,pids:u.pids,signalPids:d,stateDir:e.stateDir,installDir:e.installDir,matchReasons:u.processes.map(e=>e.reason)}}),await o({pids:d,termTimeoutMs:1500,killTimeoutMs:1e3}),f)}async function C(e,t={}){let n=Date.now(),r=d.resolve(e.stateDir),i=h.get(r);if(!(i!==void 0&&n-i<3e4))return h.set(r,n),await S(e,`provider-startup`,t)}async function w(){return await r(async()=>await s({timeoutMs:1500}))}function T(e){return e.split(/[,\n]/).map(e=>e.trim()).filter(Boolean)}function E(e){if(e===void 0||e.trim()===``)return;let t=Number.parseInt(e,10);return Number.isInteger(t)&&t>0?t:void 0}function D(e){return/\b(?:Google Chrome for Testing|Chrome for Testing|Chromium|chrome|chrome\.exe|chromium|chromium-browser|headless_shell)\b/i.test(e)}function O(e){return k([d.join(e.homeDir,`.agent-browser`,`browsers`),d.join(e.runtimeHomeDir,`.agent-browser`,`browsers`)])}function k(e){return[...new Set(e.filter(e=>e.length>0))]}function A(e,t){return{count:0,pids:[],processes:[],signalPids:[],skipped:{reason:e,...t}}}function j(e,t){let n=M(e.command),r=`${M(d.resolve(t)).replace(/\/+$/,``)}/`;return n.includes(r)}function M(e){return e.replace(/\\/g,`/`)}function N(e){let t=ee(e.socketDir).map(e=>P(e)).filter(e=>e!==void 0);return t.length>0?Math.max(...t):void 0}function ee(e){try{return p.readdirSync(e).map(t=>d.join(e,t))}catch{return[]}}function P(e){try{return p.statSync(e).mtimeMs}catch{return}}var F=e({doctorManagedAgentBrowser:()=>H,getManagedAgentBrowserStatus:()=>U,mapManagedAgentBrowserError:()=>$,resolveAgentBrowserTool:()=>B,setupManagedAgentBrowser:()=>V});const I=`0.27.1`,L=`agent-browser`,R=5*6e4,z=6e4;async function B(e){let t=U(e);if(t.installed)return W(t);throw Q(t)}async function V(e){let t=U(e);te(t.nodeMajor);let n=await u({lockDirPath:d.join(t.installDir,`..`,`.agent-browser-install.lock`),owner:{pid:process.pid,startTime:c(process.pid),acquiredAtMs:Date.now()},timeoutMs:R,description:`managed agent-browser setup`});try{let t=U(e);return t.installed?t:(p.mkdirSync(t.installDir,{recursive:!0}),await G(t),await K(t,[`install`],{timeoutMs:R}),await K(t,[`doctor`,`--offline`,`--quick`],{timeoutMs:z}),Y(t),U(e))}finally{await n()}}async function H(e){let t=U(e);if(!t.installed)throw Q(t);return{status:t,...await K(t,[`doctor`,`--offline`,`--quick`],{timeoutMs:z,allowFailure:!0})}}function U(e){let t=e.stateDir??process.env.AGENT_DEVICE_STATE_DIR??oe(),n=d.join(t,`tools`,`agent-browser`,I),r=Z(n),i=d.join(n,`home`),a=ne(n),o=re(n),s=ae(r)&&X(n),c=Number.parseInt(process.versions.node.split(`.`)[0]??`0`,10);return{version:I,stateDir:t,installDir:n,binaryPath:r,homeDir:i,runtimeHomeDir:a,socketDir:o,installed:s,nodeMajor:c,nodeSupported:c>=24}}function W(e){if(!e.installed)throw Q(e);return{command:e.binaryPath,env:q(e,process.env)}}async function G(e){let t=d.join(e.installDir,`package`);p.mkdirSync(t,{recursive:!0}),await a(`npm`,[`install`,`--prefix`,t,`--no-audit`,`--no-fund`,`--no-save`,`${L}@${I}`],{env:process.env,timeoutMs:R})}async function K(e,t,n){return await a(e.binaryPath,t,{allowFailure:n.allowFailure,env:q(e,process.env),timeoutMs:n.timeoutMs})}function q(e,t){return p.mkdirSync(e.homeDir,{recursive:!0}),J(e),p.mkdirSync(e.socketDir,{recursive:!0}),{...t,HOME:e.runtimeHomeDir,AGENT_BROWSER_SOCKET_DIR:e.socketDir,AGENT_BROWSER_IDLE_TIMEOUT_MS:String(v(t)),AGENT_BROWSER_ARGS:_(t.AGENT_BROWSER_ARGS,e)}}function J(e){if(e.runtimeHomeDir!==e.homeDir){p.mkdirSync(d.dirname(e.runtimeHomeDir),{recursive:!0});try{let t=p.lstatSync(e.runtimeHomeDir);if(t.isSymbolicLink()&&p.readlinkSync(e.runtimeHomeDir)===e.homeDir||t.isDirectory())return;t.isSymbolicLink()&&p.unlinkSync(e.runtimeHomeDir)}catch(e){if(!ie(e))throw e}try{p.symlinkSync(e.homeDir,e.runtimeHomeDir,`dir`)}catch{p.mkdirSync(e.runtimeHomeDir,{recursive:!0})}}}function Y(e){p.writeFileSync(d.join(e.installDir,`manifest.json`),JSON.stringify({package:L,version:I,node:process.version,installedAt:new Date().toISOString()},null,2),`utf8`)}function X(e){return p.existsSync(d.join(e,`manifest.json`))}function Z(e){let t=d.join(e,`package`);return d.join(t,`node_modules`,`.bin`,process.platform===`win32`?`agent-browser.cmd`:`agent-browser`)}function Q(e){return new t(`TOOL_MISSING`,`Managed web browser backend is not installed.`,{version:I,installDir:e.installDir,hint:e.nodeSupported===!1?`Web automation requires Node 24+; current Node is ${process.version}.`:"Run `agent-device web setup` to install the managed web backend."})}function te(e){if(!(e>=24))throw new t(`UNSUPPORTED_OPERATION`,`Web automation requires Node 24 or newer.`,{currentNode:process.version,requiredNodeMajor:24,hint:`Run agent-device with Node 24+ for web setup and web automation.`})}function ne(e){if(process.platform===`win32`)return d.join(e,`home`);let t=f.createHash(`sha1`).update(e).digest(`hex`).slice(0,12);return d.join(m.tmpdir(),`agent-device-web`,t)}function re(e){let t=f.createHash(`sha1`).update(e).digest(`hex`).slice(0,12);return d.join(m.tmpdir(),`adw`,t)}function ie(e){return typeof e==`object`&&!!e&&`code`in e&&e.code===`ENOENT`}function ae(e){try{return p.accessSync(e,p.constants.X_OK),!0}catch{return!1}}function oe(){return d.join(process.env.HOME??process.cwd(),`.agent-device`)}function $(e){let r=n(e);return r.code===`TOOL_MISSING`?new t(r.code,r.message,{...r.details??{},hint:typeof r.details?.hint==`string`?r.details.hint:"Run `agent-device web setup` to install the managed web backend."}):r}export{S as a,B as i,U as n,C as o,$ as r,x as s,F as t};
|