mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
fix: improve daemon diagnostics and remove compact snapshots (#786)
* fix: improve daemon startup diagnostics * perf: relax query-sweep recovery budget * fix: make compact snapshot flag a no-op * fix: remove compact snapshot mode * test: align tvOS remote provider expectation * test: drop compact snapshot progress target
This commit is contained in:
committed by
GitHub
parent
4142daa0c3
commit
5c83fe4e50
+6
-9
@@ -107,15 +107,14 @@ extension RunnerTests {
|
||||
let typeName = elementTypeName(rawElementType: rawType)
|
||||
let enabled = privateAXBool(rawNode["enabled"]) ?? true
|
||||
let visible = isVisibleInViewport(rect, viewport)
|
||||
let compactCandidate = privateAXFlatCompactCandidate(rawElementType: rawType)
|
||||
let interactiveCandidate = privateAXInteractiveCandidate(rawElementType: rawType)
|
||||
let filterDecision = flatSnapshotFilterDecision(
|
||||
FlatSnapshotFilterNode(
|
||||
isRoot: parentIndex == nil,
|
||||
label: label,
|
||||
identifier: identifier,
|
||||
valueText: value.isEmpty ? nil : value,
|
||||
visible: visible,
|
||||
compactCandidate: compactCandidate
|
||||
visible: visible
|
||||
),
|
||||
options: options,
|
||||
insideMatchedScope: insideMatchedScope
|
||||
@@ -137,7 +136,7 @@ extension RunnerTests {
|
||||
enabled: enabled,
|
||||
focused: privateAXBool(rawNode["focused"]) == true ? true : nil,
|
||||
selected: privateAXBool(rawNode["selected"]) == true ? true : nil,
|
||||
hittable: visible && enabled && compactCandidate,
|
||||
hittable: visible && enabled && interactiveCandidate,
|
||||
depth: depth,
|
||||
parentIndex: parentIndex,
|
||||
hiddenContentAbove: nil,
|
||||
@@ -230,8 +229,7 @@ extension RunnerTests {
|
||||
appendPrivateAXNode(
|
||||
tree,
|
||||
to: &nodes,
|
||||
options: SnapshotOptions(
|
||||
interactiveOnly: false, compact: false, depth: nil, scope: "homeScreen", raw: false),
|
||||
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: "homeScreen", raw: false),
|
||||
viewport: .infinite,
|
||||
depth: 0,
|
||||
parentIndex: nil,
|
||||
@@ -245,7 +243,7 @@ extension RunnerTests {
|
||||
XCTAssertFalse(labels.contains("unrelated sibling"))
|
||||
}
|
||||
|
||||
func testPrivateAXCompactInteractiveFiltersLoginLikeHiddenDrawer() {
|
||||
func testPrivateAXInteractiveFiltersLoginLikeHiddenDrawer() {
|
||||
let tree: [String: Any] = [
|
||||
"type": Int(XCUIElement.ElementType.application.rawValue),
|
||||
"label": "Blue Sky",
|
||||
@@ -313,8 +311,7 @@ extension RunnerTests {
|
||||
appendPrivateAXNode(
|
||||
tree,
|
||||
to: &nodes,
|
||||
options: SnapshotOptions(
|
||||
interactiveOnly: true, compact: true, depth: nil, scope: nil, raw: false),
|
||||
options: SnapshotOptions(interactiveOnly: true, depth: nil, scope: nil, raw: false),
|
||||
viewport: CGRect(x: 0, y: 0, width: 390, height: 844),
|
||||
depth: 0,
|
||||
parentIndex: nil,
|
||||
|
||||
-1
@@ -691,7 +691,6 @@ extension RunnerTests {
|
||||
case .snapshot:
|
||||
let options = SnapshotOptions(
|
||||
interactiveOnly: command.interactiveOnly ?? false,
|
||||
compact: command.compact ?? false,
|
||||
depth: command.depth,
|
||||
scope: command.scope,
|
||||
raw: command.raw ?? false
|
||||
|
||||
+20
-44
@@ -6,7 +6,6 @@ struct FlatSnapshotFilterNode {
|
||||
let identifier: String
|
||||
let valueText: String?
|
||||
let visible: Bool
|
||||
let compactCandidate: Bool
|
||||
|
||||
var hasContent: Bool {
|
||||
return !label.isEmpty || !identifier.isEmpty || valueText != nil
|
||||
@@ -46,8 +45,6 @@ extension RunnerTests {
|
||||
include = false
|
||||
} else if options.interactiveOnly && !node.visible {
|
||||
include = false
|
||||
} else if options.compact {
|
||||
include = node.hasContent || node.compactCandidate
|
||||
} else {
|
||||
include = true
|
||||
}
|
||||
@@ -55,14 +52,7 @@ extension RunnerTests {
|
||||
return FlatSnapshotFilterDecision(include: include, insideMatchedScope: nowInsideScope)
|
||||
}
|
||||
|
||||
func querySweepFlatCompactCandidate(
|
||||
elementType: XCUIElement.ElementType,
|
||||
hittable: Bool
|
||||
) -> Bool {
|
||||
return hittable || interactiveTypes.contains(elementType)
|
||||
}
|
||||
|
||||
func privateAXFlatCompactCandidate(rawElementType: Int) -> Bool {
|
||||
func privateAXInteractiveCandidate(rawElementType: Int) -> Bool {
|
||||
guard let type = flatSnapshotElementType(rawElementType: rawElementType) else {
|
||||
return false
|
||||
}
|
||||
@@ -84,55 +74,48 @@ extension RunnerTests {
|
||||
label: "Welcome back",
|
||||
identifier: "",
|
||||
valueText: nil,
|
||||
visible: true,
|
||||
compactCandidate: false
|
||||
visible: true
|
||||
)
|
||||
let hiddenInteractive = FlatSnapshotFilterNode(
|
||||
isRoot: false,
|
||||
label: "Hidden menu",
|
||||
identifier: "",
|
||||
valueText: nil,
|
||||
visible: false,
|
||||
compactCandidate: true
|
||||
visible: false
|
||||
)
|
||||
let decorative = FlatSnapshotFilterNode(
|
||||
isRoot: false,
|
||||
label: "",
|
||||
identifier: "",
|
||||
valueText: nil,
|
||||
visible: true,
|
||||
compactCandidate: false
|
||||
visible: true
|
||||
)
|
||||
|
||||
XCTAssertTrue(
|
||||
flatSnapshotFilterDecision(
|
||||
visibleContent,
|
||||
options: SnapshotOptions(
|
||||
interactiveOnly: false, compact: true, depth: nil, scope: nil, raw: false),
|
||||
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
|
||||
insideMatchedScope: false
|
||||
).include
|
||||
)
|
||||
XCTAssertFalse(
|
||||
flatSnapshotFilterDecision(
|
||||
hiddenInteractive,
|
||||
options: SnapshotOptions(
|
||||
interactiveOnly: true, compact: true, depth: nil, scope: nil, raw: false),
|
||||
insideMatchedScope: false
|
||||
).include
|
||||
)
|
||||
XCTAssertFalse(
|
||||
flatSnapshotFilterDecision(
|
||||
decorative,
|
||||
options: SnapshotOptions(
|
||||
interactiveOnly: false, compact: true, depth: nil, scope: nil, raw: false),
|
||||
options: SnapshotOptions(interactiveOnly: true, depth: nil, scope: nil, raw: false),
|
||||
insideMatchedScope: false
|
||||
).include
|
||||
)
|
||||
XCTAssertTrue(
|
||||
flatSnapshotFilterDecision(
|
||||
decorative,
|
||||
options: SnapshotOptions(
|
||||
interactiveOnly: false, compact: false, depth: nil, scope: nil, raw: false),
|
||||
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
|
||||
insideMatchedScope: false
|
||||
).include
|
||||
)
|
||||
XCTAssertTrue(
|
||||
flatSnapshotFilterDecision(
|
||||
decorative,
|
||||
options: SnapshotOptions(interactiveOnly: false, depth: nil, scope: nil, raw: false),
|
||||
insideMatchedScope: false
|
||||
).include
|
||||
)
|
||||
@@ -144,19 +127,16 @@ extension RunnerTests {
|
||||
label: "",
|
||||
identifier: "homeScreen",
|
||||
valueText: nil,
|
||||
visible: true,
|
||||
compactCandidate: false
|
||||
visible: true
|
||||
)
|
||||
let unmatchedDescendant = FlatSnapshotFilterNode(
|
||||
isRoot: false,
|
||||
label: "Post body without the scope text",
|
||||
identifier: "",
|
||||
valueText: nil,
|
||||
visible: true,
|
||||
compactCandidate: false
|
||||
visible: true
|
||||
)
|
||||
let options = SnapshotOptions(
|
||||
interactiveOnly: false, compact: false, depth: nil, scope: "homeScreen", raw: false)
|
||||
let options = SnapshotOptions(interactiveOnly: false, depth: nil, scope: "homeScreen", raw: false)
|
||||
|
||||
let rootDecision = flatSnapshotFilterDecision(
|
||||
scopeRoot,
|
||||
@@ -182,14 +162,10 @@ extension RunnerTests {
|
||||
)
|
||||
}
|
||||
|
||||
func testFlatSnapshotCompactCandidatesPreserveBackendInputs() {
|
||||
XCTAssertFalse(
|
||||
querySweepFlatCompactCandidate(elementType: .scrollView, hittable: false),
|
||||
"query sweep should not newly admit contentless scroll containers"
|
||||
)
|
||||
func testPrivateAXInteractiveCandidatesPreserveBackendInputs() {
|
||||
XCTAssertTrue(
|
||||
privateAXFlatCompactCandidate(rawElementType: Int(XCUIElement.ElementType.scrollView.rawValue)),
|
||||
"private AX keeps its existing scroll-container compact candidate behavior"
|
||||
privateAXInteractiveCandidate(rawElementType: Int(XCUIElement.ElementType.scrollView.rawValue)),
|
||||
"private AX marks scroll containers as interactive candidates"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ struct Command: Codable {
|
||||
let fps: Int?
|
||||
let quality: Int?
|
||||
let interactiveOnly: Bool?
|
||||
let compact: Bool?
|
||||
let depth: Int?
|
||||
let scope: String?
|
||||
let raw: Bool?
|
||||
@@ -351,7 +350,6 @@ struct SnapshotNode: Codable {
|
||||
|
||||
struct SnapshotOptions {
|
||||
let interactiveOnly: Bool
|
||||
let compact: Bool
|
||||
let depth: Int?
|
||||
let scope: String?
|
||||
let raw: Bool
|
||||
|
||||
@@ -105,11 +105,8 @@ extension RunnerTests {
|
||||
if let blocking = blockingSystemAlertSnapshot() {
|
||||
return blocking
|
||||
}
|
||||
let plan = options.interactiveOnly && options.compact
|
||||
? Self.compactInteractivePlan
|
||||
: Self.regularVisiblePlan
|
||||
return try runSnapshotCapturePlan(
|
||||
plan,
|
||||
Self.regularVisiblePlan,
|
||||
app: app,
|
||||
options: options,
|
||||
terminal: .sparseWithFatalOnAXFailure
|
||||
@@ -307,7 +304,7 @@ extension RunnerTests {
|
||||
|
||||
func snapshotFlatInteractive(app: XCUIApplication, options: SnapshotOptions) -> DataPayload {
|
||||
var nodes: [SnapshotNode] = [
|
||||
compactInteractiveRootNode(rect: .zero)
|
||||
interactiveRootNode(rect: .zero)
|
||||
]
|
||||
if options.depth == 0 {
|
||||
return DataPayload(nodes: nodes, truncated: false)
|
||||
@@ -355,9 +352,9 @@ extension RunnerTests {
|
||||
// inside nodes[0].rect): use the real screen viewport when capture produced a finite one,
|
||||
// so off-screen candidates can never inflate the root and masquerade as on-screen.
|
||||
let rootRect = viewport.isInfinite || viewport.isNull || viewport.isEmpty
|
||||
? compactInteractiveRootFrame(for: candidates)
|
||||
? interactiveRootFrame(for: candidates)
|
||||
: viewport
|
||||
nodes[0] = compactInteractiveRootNode(rect: rootRect)
|
||||
nodes[0] = interactiveRootNode(rect: rootRect)
|
||||
for candidate in candidates {
|
||||
nodes.append(
|
||||
SnapshotNode(
|
||||
@@ -421,7 +418,7 @@ extension RunnerTests {
|
||||
) -> DataPayload {
|
||||
return DataPayload(
|
||||
message: message,
|
||||
nodes: [compactInteractiveRootNode(rect: .zero)],
|
||||
nodes: [interactiveRootNode(rect: .zero)],
|
||||
truncated: true,
|
||||
snapshotQuality: snapshotQuality,
|
||||
runnerFatal: runnerFatal,
|
||||
@@ -472,7 +469,7 @@ extension RunnerTests {
|
||||
XCTAssertEqual(failure.hint, Self.rawSnapshotTooLargeHint)
|
||||
}
|
||||
|
||||
private func compactInteractiveRootNode(rect: CGRect) -> SnapshotNode {
|
||||
private func interactiveRootNode(rect: CGRect) -> SnapshotNode {
|
||||
SnapshotNode(
|
||||
index: 0,
|
||||
type: "Application",
|
||||
@@ -491,7 +488,7 @@ extension RunnerTests {
|
||||
)
|
||||
}
|
||||
|
||||
private func compactInteractiveRootFrame(for candidates: [SnapshotNode]) -> CGRect {
|
||||
private func interactiveRootFrame(for candidates: [SnapshotNode]) -> CGRect {
|
||||
guard !candidates.isEmpty else {
|
||||
return .zero
|
||||
}
|
||||
@@ -523,9 +520,6 @@ extension RunnerTests {
|
||||
) -> Bool {
|
||||
let type = snapshot.elementType
|
||||
let hasContent = !label.isEmpty || !identifier.isEmpty || (valueText != nil)
|
||||
if options.compact && type == .other && !hasContent && !hittable {
|
||||
if snapshot.children.count <= 1 { return false }
|
||||
}
|
||||
if options.interactiveOnly {
|
||||
if isScrollableContainer(snapshot, visible: visible) { return true }
|
||||
#if os(macOS)
|
||||
@@ -538,9 +532,6 @@ extension RunnerTests {
|
||||
if hasContent { return true }
|
||||
return false
|
||||
}
|
||||
if options.compact {
|
||||
return hasContent || hittable
|
||||
}
|
||||
if regularSnapshot {
|
||||
if type == .application || type == .window { return true }
|
||||
return visible
|
||||
@@ -1121,8 +1112,7 @@ extension RunnerTests {
|
||||
label: label,
|
||||
identifier: identifier,
|
||||
valueText: valueText,
|
||||
visible: visible,
|
||||
compactCandidate: querySweepFlatCompactCandidate(elementType: elementType, hittable: hittable)
|
||||
visible: visible
|
||||
)
|
||||
if !flatSnapshotFilterDecision(filterNode, options: options, insideMatchedScope: false).include {
|
||||
return
|
||||
|
||||
-1
@@ -61,7 +61,6 @@ extension RunnerTests {
|
||||
// MARK: Plan definitions
|
||||
|
||||
static let regularVisiblePlan: [SnapshotBackendKind] = [.recursiveTree, .querySweep, .privateAX]
|
||||
static let compactInteractivePlan: [SnapshotBackendKind] = [.querySweep, .privateAX]
|
||||
static let rawDiagnosticPlan: [SnapshotBackendKind] = [.recursiveTree, .privateAX]
|
||||
|
||||
// MARK: Plan runner
|
||||
|
||||
@@ -35,16 +35,16 @@ Protocol and maintenance references:
|
||||
|
||||
## Snapshot Strategy
|
||||
|
||||
iOS snapshots have two explicit capture modes:
|
||||
iOS snapshots have two explicit public capture modes:
|
||||
|
||||
- full/raw snapshots use recursive XCTest snapshots for rich hierarchy and diagnostics;
|
||||
- compact interactive snapshots use a bounded flat XCTest query path for fast agent-facing refs.
|
||||
- interactive snapshots filter the same visible tree down to agent-facing refs.
|
||||
|
||||
Some simulator apps expose accessibility trees that lower-level AX services can inspect but XCTest
|
||||
cannot serialize reliably. In those cases compact interactive snapshots may return a sparse root
|
||||
quickly, while full snapshots preserve the XCTest error. See
|
||||
cannot serialize reliably. In those cases interactive snapshots may return a sparse root quickly,
|
||||
while full snapshots preserve the XCTest error. See
|
||||
[`../docs/adr/0004-ios-snapshot-backend-strategy.md`](../docs/adr/0004-ios-snapshot-backend-strategy.md)
|
||||
for the backend boundary and the rationale for a future simulator AX-service backend.
|
||||
for the backend boundary and future simulator AX-service direction.
|
||||
|
||||
## Protocol Notes
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ Examples:
|
||||
{
|
||||
"command": "snapshot",
|
||||
"interactiveOnly": true,
|
||||
"compact": true,
|
||||
"depth": 2,
|
||||
"scope": "app",
|
||||
"raw": false
|
||||
|
||||
Reference in New Issue
Block a user