agent-device 0.21.8 → 0.21.9
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.21.8.apk → agent-device-android-ime-helper-0.21.9.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.9.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.8.manifest.json → agent-device-android-ime-helper-0.21.9.manifest.json} +4 -4
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.8.apk → agent-device-android-snapshot-helper-0.21.9.apk} +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.9.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.8.manifest.json → agent-device-android-snapshot-helper-0.21.9.manifest.json} +6 -6
- package/apple/macos-helper/Sources/AgentDeviceMacOSDeviceHub/DeviceHubWindowInventory.swift +65 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubPose.swift +45 -140
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubSidebar.swift +57 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/DeviceHubWindowDiscovery.swift +108 -0
- package/apple/macos-helper/Tests/AgentDeviceMacOSDeviceHubTests/DeviceHubWindowInventoryTests.swift +65 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/AgentDeviceRunnerUITests-Bridging-Header.h +1 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAppScreenCapture.swift +175 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +10 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +42 -15
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +8 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +45 -22
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +39 -734
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Keyboard.swift +20 -4
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Lifecycle.swift +63 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +13 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +25 -10
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +5 -562
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotAcquisition.swift +566 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +5 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +594 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.h +35 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerXCTestEventBridge.m +160 -0
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +3 -0
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-frames.swift +203 -0
- package/dist/apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotCoordinateSpace.swift +5 -0
- package/dist/src/absence-observation-errors.js +1 -1
- package/dist/src/adapter.js +1 -1
- package/dist/src/agent-browser-lifecycle.js +1 -1
- package/dist/src/agent-browser-process-record.js +1 -1
- package/dist/src/agent-device-client.js +1 -1
- package/dist/src/alert-runtime.js +1 -1
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-input-ownership.js +1 -1
- package/dist/src/android.js +1 -1
- package/dist/src/app-launch.js +1 -1
- package/dist/src/app-log-runtime2.js +3 -3
- package/dist/src/app-resolution.js +2 -2
- package/dist/src/apple-runner-platform.js +1 -1
- package/dist/src/application-lifecycle-interaction.js +1 -1
- package/dist/src/artifact-paths.js +1 -0
- package/dist/src/back-mode.js +1 -1
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +8 -7
- package/dist/src/cli.js +4 -4
- package/dist/src/client-types.d.ts +23 -5
- package/dist/src/command-tools.js +3 -3
- package/dist/src/connection-runtime.js +1 -1
- package/dist/src/device-claim-inspection.js +1 -1
- package/dist/src/device-claim-rule.js +2 -2
- package/dist/src/device-input-state.d.ts +1 -1
- package/dist/src/device-selection-resolver.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/device2.js +1 -1
- package/dist/src/dispatch-resolve.js +1 -1
- package/dist/src/doctor-facade.js +1 -1
- package/dist/src/durable-resource.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/finders.js +1 -1
- package/dist/src/fold-runtime.js +1 -0
- package/dist/src/gesture-admission.js +1 -1
- package/dist/src/interaction-positionals.js +1 -1
- package/dist/src/interaction-ref-policy.js +1 -1
- package/dist/src/interaction.js +1 -1
- package/dist/src/interaction2.js +1 -1
- package/dist/src/interactor-operation-catalog.js +1 -1
- package/dist/src/interactor.js +4 -4
- package/dist/src/interactors.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +2 -2
- package/dist/src/inventory2.js +1 -1
- package/dist/src/ios-snapshot-acquisition.js +1 -1
- package/dist/src/ios-snapshot-runtime.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/local-interactor-operation-set.js +1 -1
- package/dist/src/location-coordinates.js +1 -1
- package/dist/src/managed-device-scope.js +1 -1
- package/dist/src/mechanics.js +17 -17
- package/dist/src/mobile-snapshot-semantics.js +1 -1
- package/dist/src/mp4-duration.js +1 -0
- package/dist/src/multitouch-support.js +1 -1
- package/dist/src/owner-identity.js +1 -1
- package/dist/src/parameterized-recorded-fill.js +1 -1
- package/dist/src/perf-facade.js +2 -2
- package/dist/src/perf-runtime-plan.js +1 -1
- package/dist/src/platform-request-scope.js +1 -1
- package/dist/src/platform-runtime-managed-owner.js +1 -1
- package/dist/src/platform-runtime-open-target.js +1 -1
- package/dist/src/platform-runtime-operation-host.js +2 -2
- package/dist/src/platform-runtime-runtime-hints.js +1 -1
- package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
- package/dist/src/platform-runtime-unavailable.js +1 -1
- package/dist/src/platform-runtime.js +1 -1
- package/dist/src/platform-runtime2.js +1 -1
- package/dist/src/png-crop-bytes.js +1 -1
- package/dist/src/png-encode.js +1 -0
- package/dist/src/png-size.js +1 -0
- package/dist/src/provider-webdriver.js +1 -1
- package/dist/src/quality-warnings.js +1 -1
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-runtime.js +1 -1
- package/dist/src/recording-export-quality.js +1 -1
- package/dist/src/recording-scope.js +1 -0
- package/dist/src/recording.js +3 -0
- package/dist/src/registry.js +35 -35
- package/dist/src/replay.js +2 -2
- package/dist/src/request-admission.js +1 -1
- package/dist/src/request-runtime-binding.js +1 -1
- package/dist/src/result-serialization.js +1 -1
- package/dist/src/runner-cache-metadata.js +2 -2
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-contract.js +1 -1
- package/dist/src/runner-dev-tools-security.js +1 -1
- package/dist/src/runner-device-readiness.js +1 -1
- package/dist/src/runner-disposal.js +1 -1
- package/dist/src/runner-operations-facade.js +1 -1
- package/dist/src/runner-presentation.js +1 -1
- package/dist/src/runner.js +1 -1
- package/dist/src/runtime.js +2 -2
- package/dist/src/runtime10.js +2 -1
- package/dist/src/runtime2.js +4 -4
- package/dist/src/runtime4.js +5 -5
- package/dist/src/runtime5.js +1 -1
- package/dist/src/runtime6.js +1 -1
- package/dist/src/runtime7.js +1 -1
- package/dist/src/runtime8.js +1 -1
- package/dist/src/runtime9.js +1 -1
- package/dist/src/screen-recording-admission-ledger.js +1 -1
- package/dist/src/screen-recording-resource-recovery.js +1 -1
- package/dist/src/screen-recording-transport.js +1 -1
- package/dist/src/screenshot-density.js +1 -1
- package/dist/src/screenshot-overlay-draw.js +1 -0
- package/dist/src/screenshot-runtime.js +1 -1
- package/dist/src/script.js +1 -1
- package/dist/src/scroll-gesture.js +1 -1
- package/dist/src/sdk-batch.js +2 -2
- package/dist/src/sdk-contracts.d.ts +7 -5
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-finders.js +1 -1
- package/dist/src/sdk-selectors.d.ts +44 -1
- package/dist/src/sdk-selectors.js +1 -1
- package/dist/src/selection.js +0 -0
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selectors.js +1 -1
- package/dist/src/semantic-index.js +1 -1
- package/dist/src/session-routing.js +1 -1
- package/dist/src/session-snapshot.js +1 -1
- package/dist/src/session-store.js +3 -3
- package/dist/src/session2.js +3 -3
- package/dist/src/simctl.js +1 -1
- package/dist/src/simulator-boot.js +1 -1
- package/dist/src/snapshot-runtime-binding.js +1 -1
- package/dist/src/snapshot-runtime.js +1 -1
- package/dist/src/snapshot-state.js +1 -1
- package/dist/src/snapshot4.js +1 -1
- package/dist/src/src.js +1 -1
- package/dist/src/src7.js +1 -1
- package/dist/src/stop-sequence.js +1 -1
- package/dist/src/text-entry-focus.js +1 -1
- package/dist/src/tree.js +1 -1
- package/dist/src/type-text-runtime.js +1 -1
- package/dist/src/video.js +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.8.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.8.apk.sha256 +0 -1
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift
CHANGED
|
@@ -2,11 +2,11 @@ import XCTest
|
|
|
2
2
|
import AgentDeviceSnapshotPresentation
|
|
3
3
|
|
|
4
4
|
extension RunnerTests {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
static let axSnapshotErrorCode = "IOS_AX_SNAPSHOT_FAILED"
|
|
6
|
+
static let axSnapshotFailureMessage =
|
|
7
7
|
"iOS XCTest snapshot failed while serializing the accessibility tree."
|
|
8
8
|
private static let axSnapshotUnavailableReason = "ax_snapshot_unavailable"
|
|
9
|
-
|
|
9
|
+
static let axSnapshotHint =
|
|
10
10
|
"Snapshot state is unavailable because XCTest could not serialize this iOS accessibility tree. This can be specific to the current screen. Use plain screenshot, not screenshot --overlay-refs, as visual truth; navigate with coordinate commands if needed; then retry snapshot -i after reaching another screen. If you own the app and need full-tree inspection, simplify this screen's accessibility tree and expose stable ids on actionable controls."
|
|
11
11
|
private static let rawSnapshotTooLargeCode = "IOS_RAW_SNAPSHOT_TOO_LARGE"
|
|
12
12
|
private static let rawSnapshotMaxNodes = 5_000
|
|
@@ -32,7 +32,7 @@ extension RunnerTests {
|
|
|
32
32
|
let keyboardBand: RunnerKeyboardBandFact?
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
struct SnapshotEvaluation {
|
|
36
36
|
let label: String
|
|
37
37
|
let identifier: String
|
|
38
38
|
let valueText: String?
|
|
@@ -117,7 +117,7 @@ extension RunnerTests {
|
|
|
117
117
|
"ScrollView"
|
|
118
118
|
]
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
static let collapsedTabCandidateTypes: Set<XCUIElement.ElementType> = [
|
|
121
121
|
.button,
|
|
122
122
|
.link,
|
|
123
123
|
.menuItem,
|
|
@@ -799,562 +799,5 @@ extension RunnerTests {
|
|
|
799
799
|
|
|
800
800
|
|
|
801
801
|
|
|
802
|
-
|
|
803
|
-
private func interactiveRootNode(rect: CGRect) -> RawAXNode {
|
|
804
|
-
RawAXNode(
|
|
805
|
-
index: 0,
|
|
806
|
-
type: "Application",
|
|
807
|
-
label: nil,
|
|
808
|
-
identifier: nil,
|
|
809
|
-
value: nil,
|
|
810
|
-
rect: SnapshotRect(rect),
|
|
811
|
-
enabled: true,
|
|
812
|
-
focused: nil,
|
|
813
|
-
selected: nil,
|
|
814
|
-
hittable: false,
|
|
815
|
-
depth: 0,
|
|
816
|
-
parentIndex: nil,
|
|
817
|
-
hiddenContentAbove: nil,
|
|
818
|
-
hiddenContentBelow: nil
|
|
819
|
-
)
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
private func interactiveRootFrame(for candidates: [RawAXNode]) -> CGRect {
|
|
823
|
-
guard !candidates.isEmpty else {
|
|
824
|
-
return .zero
|
|
825
|
-
}
|
|
826
|
-
let maxX = candidates.map { CGFloat($0.rect.x + $0.rect.width) }.max() ?? 0
|
|
827
|
-
let maxY = candidates.map { CGFloat($0.rect.y + $0.rect.height) }.max() ?? 0
|
|
828
|
-
return CGRect(x: 0, y: 0, width: max(1, maxX), height: max(1, maxY))
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
func makeSnapshotTraversalContext(
|
|
834
|
-
app: XCUIApplication,
|
|
835
|
-
hint: CaptureHint,
|
|
836
|
-
captureDeadline: Date = .distantFuture,
|
|
837
|
-
treeCaptureSliceBudgetOverride: TimeInterval? = nil
|
|
838
|
-
) throws -> SnapshotTraversalContext? {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
let geometry = try runMainThreadWork(
|
|
843
|
-
"snapshot_viewport",
|
|
844
|
-
timeout: min(1.0, max(0.1, captureDeadline.timeIntervalSinceNow)),
|
|
845
|
-
timeoutError: snapshotMainThreadTimeoutError("preparing tree snapshot")
|
|
846
|
-
) {
|
|
847
|
-
(
|
|
848
|
-
viewport: self.safeSnapshotViewport(app: app),
|
|
849
|
-
interfaceOrientation: self.capturedInterfaceOrientation(app: app)
|
|
850
|
-
)
|
|
851
|
-
}
|
|
852
|
-
let viewport = geometry.viewport
|
|
853
|
-
let interfaceOrientation = geometry.interfaceOrientation
|
|
854
|
-
let treeSliceBudget = treeCaptureSliceBudgetOverride ?? treeCaptureSliceBudget
|
|
855
|
-
let slice = min(treeSliceBudget, max(0.5, captureDeadline.timeIntervalSinceNow))
|
|
856
|
-
guard let rootSnapshot = try captureSnapshotRootBounded(app, sliceSeconds: slice) else {
|
|
857
|
-
return nil
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
let keyboardBand = captureKeyboardBandFact(app: app, deadline: captureDeadline)
|
|
866
|
-
|
|
867
|
-
return SnapshotTraversalContext(
|
|
868
|
-
queryRoot: app,
|
|
869
|
-
rootSnapshot: rootSnapshot,
|
|
870
|
-
viewport: viewport,
|
|
871
|
-
interfaceOrientation: interfaceOrientation,
|
|
872
|
-
keyboardBand: keyboardBand
|
|
873
|
-
)
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
static let xCTestSnapshotTimeoutCode = "IOS_TREE_CAPTURE_TIMEOUT"
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
private func captureSnapshotRootBounded(
|
|
885
|
-
_ element: XCUIElement,
|
|
886
|
-
sliceSeconds: TimeInterval
|
|
887
|
-
) throws -> XCUIElementSnapshot? {
|
|
888
|
-
if Thread.isMainThread {
|
|
889
|
-
return try captureSnapshotRoot(element)
|
|
890
|
-
}
|
|
891
|
-
return try runMainThreadWork(
|
|
892
|
-
"tree_capture",
|
|
893
|
-
timeout: sliceSeconds,
|
|
894
|
-
timeoutError: treeCaptureTimeoutError(sliceSeconds: sliceSeconds)
|
|
895
|
-
) {
|
|
896
|
-
try self.captureSnapshotRoot(element)
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
private func treeCaptureTimeoutError(sliceSeconds: TimeInterval) -> () -> Error {
|
|
901
|
-
{
|
|
902
|
-
SnapshotCaptureFailure(
|
|
903
|
-
code: Self.xCTestSnapshotTimeoutCode,
|
|
904
|
-
message: "the XCTest tree capture exceeded its \(Int(sliceSeconds))s time slice",
|
|
905
|
-
hint: "The capture plan will avoid or tightly bound XCTest-backed snapshot tiers on this screen."
|
|
906
|
-
)
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
func snapshotMainThreadTimeoutError(_ operation: String) -> () -> Error {
|
|
911
|
-
{
|
|
912
|
-
SnapshotCaptureFailure(
|
|
913
|
-
code: Self.xCTestSnapshotTimeoutCode,
|
|
914
|
-
message: "timed out while \(operation) on the XCTest main thread",
|
|
915
|
-
hint: "The capture plan will skip XCTest-backed snapshot tiers while the previous main-thread work drains."
|
|
916
|
-
)
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
private func captureSnapshotRoot(_ element: XCUIElement) throws -> XCUIElementSnapshot? {
|
|
921
|
-
var rootSnapshot: XCUIElementSnapshot?
|
|
922
|
-
var swiftErrorMessage: String?
|
|
923
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
924
|
-
do {
|
|
925
|
-
rootSnapshot = try element.snapshot()
|
|
926
|
-
} catch {
|
|
927
|
-
swiftErrorMessage = describeSnapshotError(error)
|
|
928
|
-
}
|
|
929
|
-
})
|
|
930
|
-
|
|
931
|
-
if let rootSnapshot {
|
|
932
|
-
return rootSnapshot
|
|
933
|
-
}
|
|
934
|
-
let message = exceptionMessage ?? swiftErrorMessage ?? "snapshot returned no root"
|
|
935
|
-
if Self.isAxIllegalArgument(message) {
|
|
936
|
-
throw axSnapshotFailure(message)
|
|
937
|
-
}
|
|
938
|
-
return nil
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
func safeSnapshotViewport(app: XCUIApplication) -> CGRect {
|
|
942
|
-
safely("SNAPSHOT_VIEWPORT", CGRect.infinite) { snapshotViewport(app: app) }
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
private func describeSnapshotError(_ error: Error) -> String {
|
|
946
|
-
let localized = error.localizedDescription
|
|
947
|
-
let debug = String(describing: error)
|
|
948
|
-
if localized.isEmpty { return debug }
|
|
949
|
-
if debug == localized { return localized }
|
|
950
|
-
return "\(localized) (\(debug))"
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
private func axSnapshotFailure(_ message: String) -> SnapshotCaptureFailure {
|
|
954
|
-
let detail = message.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
955
|
-
let failureMessage: String
|
|
956
|
-
if detail.isEmpty {
|
|
957
|
-
failureMessage = Self.axSnapshotFailureMessage
|
|
958
|
-
} else {
|
|
959
|
-
failureMessage = "\(Self.axSnapshotFailureMessage) \(detail)"
|
|
960
|
-
}
|
|
961
|
-
return SnapshotCaptureFailure(
|
|
962
|
-
code: Self.axSnapshotErrorCode,
|
|
963
|
-
message: failureMessage,
|
|
964
|
-
hint: Self.axSnapshotHint
|
|
965
|
-
)
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
private static func isAxIllegalArgument(_ message: String) -> Bool {
|
|
969
|
-
let normalized = message.lowercased()
|
|
970
|
-
return normalized.contains("kaxerrorillegalargument")
|
|
971
|
-
|| (normalized.contains("illegal argument") && normalized.contains("snapshot"))
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
static func isAxSnapshotFailure(_ failure: SnapshotCaptureFailure) -> Bool {
|
|
975
|
-
failure.code == Self.axSnapshotErrorCode || isAxIllegalArgument(failure.message)
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
private func evaluateSnapshot(_ snapshot: XCUIElementSnapshot) -> SnapshotEvaluation {
|
|
979
|
-
let label = aggregatedLabel(for: snapshot) ?? snapshot.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
980
|
-
let identifier = snapshot.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
981
|
-
let valueText = snapshotValueText(snapshot)
|
|
982
|
-
return SnapshotEvaluation(
|
|
983
|
-
label: label,
|
|
984
|
-
identifier: identifier,
|
|
985
|
-
valueText: valueText,
|
|
986
|
-
focused: snapshotHasFocus(snapshot),
|
|
987
|
-
selected: snapshotIsSelected(snapshot)
|
|
988
|
-
)
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
private func makeSnapshotNode(
|
|
992
|
-
snapshot: XCUIElementSnapshot,
|
|
993
|
-
evaluation: SnapshotEvaluation,
|
|
994
|
-
depth: Int,
|
|
995
|
-
index: Int,
|
|
996
|
-
parentIndex: Int?
|
|
997
|
-
) -> RawAXNode {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
return RawAXNode(
|
|
1001
|
-
index: index,
|
|
1002
|
-
type: elementTypeName(snapshot.elementType),
|
|
1003
|
-
label: evaluation.label.isEmpty ? nil : evaluation.label,
|
|
1004
|
-
identifier: evaluation.identifier.isEmpty ? nil : evaluation.identifier,
|
|
1005
|
-
value: evaluation.valueText,
|
|
1006
|
-
rect: SnapshotRect(snapshot.frame),
|
|
1007
|
-
enabled: snapshot.isEnabled,
|
|
1008
|
-
focused: evaluation.focused ? true : nil,
|
|
1009
|
-
selected: evaluation.selected ? true : nil,
|
|
1010
|
-
hittable: false,
|
|
1011
|
-
depth: depth,
|
|
1012
|
-
parentIndex: parentIndex,
|
|
1013
|
-
hiddenContentAbove: nil,
|
|
1014
|
-
hiddenContentBelow: nil
|
|
1015
|
-
)
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
func capturedInterfaceOrientation(app: XCUIApplication) -> Int {
|
|
1021
|
-
safely("SNAPSHOT_INTERFACE_ORIENTATION", RunnerInterfaceOrientation.unknown) {
|
|
1022
|
-
Int(RunnerSynthesizedGesture.interfaceOrientation(forApplication: app))
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
private func snapshotValueText(_ snapshot: XCUIElementSnapshot) -> String? {
|
|
1027
|
-
guard let value = snapshot.value else { return nil }
|
|
1028
|
-
let text = String(describing: value).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1029
|
-
return text.isEmpty ? nil : text
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
private func snapshotViewport(app: XCUIApplication) -> CGRect {
|
|
1033
|
-
let appFrame = app.frame
|
|
1034
|
-
if !appFrame.isNull && !appFrame.isEmpty {
|
|
1035
|
-
return appFrame
|
|
1036
|
-
}
|
|
1037
|
-
return .infinite
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
static func snapshotTraversalIdentity(
|
|
1041
|
-
elementType: XCUIElement.ElementType,
|
|
1042
|
-
label: String,
|
|
1043
|
-
identifier: String,
|
|
1044
|
-
frame: CGRect
|
|
1045
|
-
) -> String {
|
|
1046
|
-
#if os(iOS)
|
|
1047
|
-
"\(elementType)-\(label)-\(identifier)-\(frame.origin.x)-\(frame.origin.y)-\(frame.width)-\(frame.height)"
|
|
1048
|
-
#else
|
|
1049
|
-
return "\(elementType)-\(label)-\(identifier)-\(frame.origin.x)-\(frame.origin.y)"
|
|
1050
|
-
#endif
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
private func aggregatedLabel(for snapshot: XCUIElementSnapshot, depth: Int = 0) -> String? {
|
|
1054
|
-
if depth > 4 { return nil }
|
|
1055
|
-
let text = snapshot.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1056
|
-
if !text.isEmpty { return text }
|
|
1057
|
-
if let valueText = snapshotValueText(snapshot) { return valueText }
|
|
1058
|
-
for child in snapshot.children {
|
|
1059
|
-
if let childLabel = aggregatedLabel(for: child, depth: depth + 1) {
|
|
1060
|
-
return childLabel
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
return nil
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
private func appendCollapsedTabFallbackNodes(
|
|
1067
|
-
to nodes: inout [RawAXNode],
|
|
1068
|
-
containerSnapshot: XCUIElementSnapshot,
|
|
1069
|
-
resolveElements: () -> [XCUIElement],
|
|
1070
|
-
depth: Int,
|
|
1071
|
-
parentIndex: Int
|
|
1072
|
-
) {
|
|
1073
|
-
let fallbackNodes = collapsedTabFallbackNodes(
|
|
1074
|
-
for: containerSnapshot,
|
|
1075
|
-
resolveElements: resolveElements,
|
|
1076
|
-
startingIndex: nodes.count,
|
|
1077
|
-
depth: depth,
|
|
1078
|
-
parentIndex: parentIndex
|
|
1079
|
-
)
|
|
1080
|
-
nodes.append(contentsOf: fallbackNodes)
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
private func collapsedTabFallbackNodes(
|
|
1084
|
-
for containerSnapshot: XCUIElementSnapshot,
|
|
1085
|
-
resolveElements: () -> [XCUIElement],
|
|
1086
|
-
startingIndex: Int,
|
|
1087
|
-
depth: Int,
|
|
1088
|
-
parentIndex: Int
|
|
1089
|
-
) -> [RawAXNode] {
|
|
1090
|
-
if !containerSnapshot.children.isEmpty { return [] }
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
guard shouldExpandCollapsedTabContainer(containerSnapshot) else { return [] }
|
|
1096
|
-
let containerFrame = containerSnapshot.frame
|
|
1097
|
-
if containerFrame.isNull || containerFrame.isEmpty { return [] }
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
let elements = resolveElements()
|
|
1102
|
-
let candidates = elements.compactMap { element in
|
|
1103
|
-
collapsedTabCandidateNode(
|
|
1104
|
-
element: element,
|
|
1105
|
-
containerSnapshot: containerSnapshot,
|
|
1106
|
-
containerFrame: containerFrame
|
|
1107
|
-
)
|
|
1108
|
-
}
|
|
1109
|
-
.sorted { left, right in
|
|
1110
|
-
if left.rect.x != right.rect.x {
|
|
1111
|
-
return left.rect.x < right.rect.x
|
|
1112
|
-
}
|
|
1113
|
-
return left.rect.y < right.rect.y
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
if candidates.count < 2 { return [] }
|
|
1117
|
-
let rowMidpoints = candidates.map { $0.rect.y + ($0.rect.height / 2) }
|
|
1118
|
-
let rowSpread = (rowMidpoints.max() ?? 0) - (rowMidpoints.min() ?? 0)
|
|
1119
|
-
|
|
1120
|
-
if rowSpread > max(24.0, Double(containerFrame.height) * 0.6) { return [] }
|
|
1121
|
-
|
|
1122
|
-
var seen = Set<String>()
|
|
1123
|
-
let uniqueCandidates = candidates.filter { node in
|
|
1124
|
-
let key = "\(node.type)-\(node.label ?? "")-\(node.identifier ?? "")-\(node.value ?? "")-\(node.rect.x)-\(node.rect.y)-\(node.rect.width)-\(node.rect.height)"
|
|
1125
|
-
if seen.contains(key) { return false }
|
|
1126
|
-
seen.insert(key)
|
|
1127
|
-
return true
|
|
1128
|
-
}
|
|
1129
|
-
if uniqueCandidates.count < 2 { return [] }
|
|
1130
|
-
|
|
1131
|
-
return uniqueCandidates.enumerated().map { offset, node in
|
|
1132
|
-
RawAXNode(
|
|
1133
|
-
index: startingIndex + offset,
|
|
1134
|
-
type: node.type,
|
|
1135
|
-
label: node.label,
|
|
1136
|
-
identifier: node.identifier,
|
|
1137
|
-
value: node.value,
|
|
1138
|
-
rect: node.rect,
|
|
1139
|
-
enabled: node.enabled,
|
|
1140
|
-
focused: node.focused,
|
|
1141
|
-
selected: node.selected,
|
|
1142
|
-
hittable: node.hittable,
|
|
1143
|
-
depth: depth,
|
|
1144
|
-
parentIndex: parentIndex,
|
|
1145
|
-
hiddenContentAbove: nil,
|
|
1146
|
-
hiddenContentBelow: nil
|
|
1147
|
-
)
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
private func collapsedTabCandidateNode(
|
|
1152
|
-
element: XCUIElement,
|
|
1153
|
-
containerSnapshot: XCUIElementSnapshot,
|
|
1154
|
-
containerFrame: CGRect
|
|
1155
|
-
) -> RawAXNode? {
|
|
1156
|
-
var node: RawAXNode?
|
|
1157
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
1158
|
-
if !element.exists { return }
|
|
1159
|
-
let elementType = element.elementType
|
|
1160
|
-
if !Self.collapsedTabCandidateTypes.contains(elementType) { return }
|
|
1161
|
-
let frame = element.frame
|
|
1162
|
-
if frame.isNull || frame.isEmpty { return }
|
|
1163
|
-
if frame.equalTo(containerFrame) { return }
|
|
1164
|
-
let area = max(CGFloat(1), frame.width * frame.height)
|
|
1165
|
-
let containerArea = max(CGFloat(1), containerFrame.width * containerFrame.height)
|
|
1166
|
-
if area >= containerArea * 0.9 { return }
|
|
1167
|
-
let center = CGPoint(x: frame.midX, y: frame.midY)
|
|
1168
|
-
if !containerFrame.contains(center) { return }
|
|
1169
|
-
|
|
1170
|
-
let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1171
|
-
let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1172
|
-
let valueText = snapshotValueText(element)
|
|
1173
|
-
let hasContent = !label.isEmpty || !identifier.isEmpty || valueText != nil
|
|
1174
|
-
if !hasContent { return }
|
|
1175
|
-
if sameSemanticElement(
|
|
1176
|
-
containerSnapshot: containerSnapshot,
|
|
1177
|
-
elementType: elementType,
|
|
1178
|
-
label: label,
|
|
1179
|
-
identifier: identifier
|
|
1180
|
-
) {
|
|
1181
|
-
return
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
node = RawAXNode(
|
|
1187
|
-
index: 0,
|
|
1188
|
-
type: elementTypeName(elementType),
|
|
1189
|
-
label: label.isEmpty ? nil : label,
|
|
1190
|
-
identifier: identifier.isEmpty ? nil : identifier,
|
|
1191
|
-
value: valueText,
|
|
1192
|
-
rect: SnapshotRect(frame),
|
|
1193
|
-
enabled: element.isEnabled,
|
|
1194
|
-
focused: elementHasFocus(element) ? true : nil,
|
|
1195
|
-
selected: element.isSelected ? true : nil,
|
|
1196
|
-
hittable: false,
|
|
1197
|
-
depth: 0,
|
|
1198
|
-
parentIndex: nil,
|
|
1199
|
-
hiddenContentAbove: nil,
|
|
1200
|
-
hiddenContentBelow: nil
|
|
1201
|
-
)
|
|
1202
|
-
})
|
|
1203
|
-
if let exceptionMessage {
|
|
1204
|
-
NSLog(
|
|
1205
|
-
"AGENT_DEVICE_RUNNER_SNAPSHOT_TAB_FALLBACK_IGNORED_EXCEPTION=%@",
|
|
1206
|
-
exceptionMessage
|
|
1207
|
-
)
|
|
1208
|
-
return nil
|
|
1209
|
-
}
|
|
1210
|
-
return node
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
private func snapshotHasFocus(_ snapshot: XCUIElementSnapshot) -> Bool {
|
|
1214
|
-
var focused = false
|
|
1215
|
-
_ = RunnerObjCExceptionCatcher.catchException({
|
|
1216
|
-
if let value = (snapshot as! NSObject).value(forKey: "hasFocus") as? Bool {
|
|
1217
|
-
focused = value
|
|
1218
|
-
}
|
|
1219
|
-
})
|
|
1220
|
-
return focused
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
private func snapshotIsSelected(_ snapshot: XCUIElementSnapshot) -> Bool {
|
|
1224
|
-
return snapshot.isSelected
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
private func shouldExpandCollapsedTabContainer(_ snapshot: XCUIElementSnapshot) -> Bool {
|
|
1228
|
-
let frame = snapshot.frame
|
|
1229
|
-
if frame.isNull || frame.isEmpty { return false }
|
|
1230
|
-
if frame.width < max(CGFloat(160), frame.height * 1.75) { return false }
|
|
1231
|
-
switch snapshot.elementType {
|
|
1232
|
-
case .tabBar, .segmentedControl, .slider:
|
|
1233
|
-
return true
|
|
1234
|
-
default:
|
|
1235
|
-
return false
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
private func snapshotValueText(_ element: XCUIElement) -> String? {
|
|
1240
|
-
let text = String(describing: element.value ?? "")
|
|
1241
|
-
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1242
|
-
return text.isEmpty ? nil : text
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
private func sameSemanticElement(
|
|
1246
|
-
containerSnapshot: XCUIElementSnapshot,
|
|
1247
|
-
elementType: XCUIElement.ElementType,
|
|
1248
|
-
label: String,
|
|
1249
|
-
identifier: String
|
|
1250
|
-
) -> Bool {
|
|
1251
|
-
if containerSnapshot.elementType != elementType { return false }
|
|
1252
|
-
let containerLabel = containerSnapshot.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1253
|
-
let containerIdentifier = containerSnapshot.identifier
|
|
1254
|
-
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1255
|
-
return containerLabel == label && containerIdentifier == identifier
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
private func flatInteractiveElements(
|
|
1259
|
-
app: XCUIApplication,
|
|
1260
|
-
deadline: Date
|
|
1261
|
-
) -> (elements: [XCUIElement], truncated: Bool) {
|
|
1262
|
-
let queries: [XCUIElementQuery] = [
|
|
1263
|
-
app.buttons,
|
|
1264
|
-
app.links,
|
|
1265
|
-
app.textFields,
|
|
1266
|
-
app.secureTextFields,
|
|
1267
|
-
app.searchFields,
|
|
1268
|
-
app.textViews,
|
|
1269
|
-
app.switches,
|
|
1270
|
-
app.sliders,
|
|
1271
|
-
app.segmentedControls,
|
|
1272
|
-
app.cells,
|
|
1273
|
-
app.collectionViews,
|
|
1274
|
-
app.tables,
|
|
1275
|
-
app.scrollViews,
|
|
1276
|
-
app.pickers,
|
|
1277
|
-
app.steppers,
|
|
1278
|
-
app.tabBars,
|
|
1279
|
-
app.menuItems,
|
|
1280
|
-
app.staticTexts,
|
|
1281
|
-
app.images
|
|
1282
|
-
]
|
|
1283
|
-
|
|
1284
|
-
var elements: [XCUIElement] = []
|
|
1285
|
-
var truncated = false
|
|
1286
|
-
for query in queries {
|
|
1287
|
-
if Date() >= deadline {
|
|
1288
|
-
NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_FLAT_FALLBACK_DEADLINE")
|
|
1289
|
-
truncated = true
|
|
1290
|
-
break
|
|
1291
|
-
}
|
|
1292
|
-
let result = snapshotElementsQuery {
|
|
1293
|
-
query.allElementsBoundByIndex
|
|
1294
|
-
}
|
|
1295
|
-
elements.append(contentsOf: result.elements)
|
|
1296
|
-
if result.axUnavailable {
|
|
1297
|
-
break
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
return (elements, truncated)
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
private func snapshotElementsQuery(
|
|
1304
|
-
_ fetch: () -> [XCUIElement]
|
|
1305
|
-
) -> (elements: [XCUIElement], axUnavailable: Bool) {
|
|
1306
|
-
let (elements, exceptionMessage) = catchingObjCException(fallback: [], fetch)
|
|
1307
|
-
guard let exceptionMessage else {
|
|
1308
|
-
return (elements, false)
|
|
1309
|
-
}
|
|
1310
|
-
NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_QUERY_IGNORED_EXCEPTION=%@", exceptionMessage)
|
|
1311
|
-
if Self.isAxIllegalArgument(exceptionMessage) {
|
|
1312
|
-
invalidateCachedTarget(reason: "ax_snapshot_query_unavailable")
|
|
1313
|
-
return ([], true)
|
|
1314
|
-
}
|
|
1315
|
-
return ([], false)
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
private func flatSnapshotNode(
|
|
1319
|
-
element: XCUIElement,
|
|
1320
|
-
index: Int,
|
|
1321
|
-
parentIndex: Int?
|
|
1322
|
-
) -> RawAXNode? {
|
|
1323
|
-
var node: RawAXNode?
|
|
1324
|
-
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
1325
|
-
if !element.exists { return }
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
let frame = element.frame
|
|
1329
|
-
if frame.isNull || frame.isEmpty { return }
|
|
1330
|
-
let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1331
|
-
let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1332
|
-
let valueText = snapshotValueText(element)
|
|
1333
|
-
let elementType = element.elementType
|
|
1334
|
-
let enabled = element.isEnabled
|
|
1335
|
-
|
|
1336
|
-
node = RawAXNode(
|
|
1337
|
-
index: index,
|
|
1338
|
-
type: elementTypeName(elementType),
|
|
1339
|
-
label: label.isEmpty ? nil : label,
|
|
1340
|
-
identifier: identifier.isEmpty ? nil : identifier,
|
|
1341
|
-
value: valueText,
|
|
1342
|
-
rect: SnapshotRect(frame),
|
|
1343
|
-
enabled: enabled,
|
|
1344
|
-
focused: elementHasFocus(element) ? true : nil,
|
|
1345
|
-
selected: element.isSelected ? true : nil,
|
|
1346
|
-
hittable: false,
|
|
1347
|
-
depth: 1,
|
|
1348
|
-
parentIndex: parentIndex,
|
|
1349
|
-
hiddenContentAbove: nil,
|
|
1350
|
-
hiddenContentBelow: nil
|
|
1351
|
-
)
|
|
1352
|
-
})
|
|
1353
|
-
if let exceptionMessage {
|
|
1354
|
-
NSLog("AGENT_DEVICE_RUNNER_SNAPSHOT_FLAT_IGNORED_EXCEPTION=%@", exceptionMessage)
|
|
1355
|
-
return nil
|
|
1356
|
-
}
|
|
1357
|
-
return node
|
|
1358
|
-
}
|
|
1359
802
|
|
|
1360
803
|
}
|