Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"src/utils/png-worker.ts",
"scripts/patch-xcuitest-runner-icon.ts",
"scripts/runner-request-count/run.ts",
"packages/capture-kit/src/ios-snapshot-engine/replay.ts",
// #1596 regression fixture: runs as a real `node --experimental-strip-types`
// subprocess (test/integration/daemon-replace-exit-flush.test.ts), so
// dependency analysis cannot follow the runCmdSync string path to it.
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ jobs:
uses: ./.github/actions/run-gate
with: { gate: macos-coverage }

- name: Run iOS snapshot Swift/TypeScript differential
uses: ./.github/actions/run-gate
with: { gate: ios-snapshot-differential }

- name: Restore and build macOS XCTest runner
uses: ./.github/actions/setup-apple-runner-build
with:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import AgentDeviceSnapshotPresentation
import Foundation
import CoreGraphics
import Foundation

private struct Input: Decodable {
private struct ConformanceInput: Decodable {
struct Node: Decodable {
let index: Int
let type: String
Expand All @@ -20,60 +20,132 @@ private struct Input: Decodable {
let hiddenContentBelow: Bool?
}

let name: String
let projection: String
let interactiveOnly: Bool
let depth: Int?
let scope: String?
let foldPolicy: String
let viewport: SnapshotRect
let nodes: [Node]
}

private struct Output: Encodable {
private struct BatchInput: Decodable {
let cases: [ConformanceInput]
}

private struct ConformanceError: Encodable {
let code: String
let reason: String
let message: String
}

private struct ConformanceOutput: Encodable {
let name: String
let outcome: String
let nodes: [PresentedNode]
let error: ConformanceError?
}

private struct BatchOutput: Encodable {
let cases: [ConformanceOutput]
}

private func acquisition(for input: ConformanceInput) -> SnapshotAcquisition {
let inputOptions = options(for: input)
return SnapshotAcquisition(
hint: SnapshotPresentation.captureHint(for: inputOptions),
nodes: input.nodes.map { node in
RawAXNode(
index: node.index,
type: node.type,
label: node.label,
identifier: node.identifier,
value: node.value,
rect: node.rect,
enabled: node.enabled,
focused: node.focused,
selected: node.selected,
hittable: node.hittable,
depth: node.depth,
parentIndex: node.parentIndex,
hiddenContentAbove: node.hiddenContentAbove,
hiddenContentBelow: node.hiddenContentBelow
)
},
truncated: false,
effectiveDepth: nil,
viewport: input.viewport.cgRect
)
}

private func options(for input: ConformanceInput) -> PresentationOptions {
PresentationOptions(
interactiveOnly: input.interactiveOnly,
depth: input.depth,
scope: input.scope,
raw: input.projection == CaptureHint.Projection.raw.rawValue
)
}

private func present(_ input: ConformanceInput) -> ConformanceOutput {
do {
let inputAcquisition = acquisition(for: input)
let inputOptions = options(for: input)
let nodes: [PresentedNode]
if input.projection == CaptureHint.Projection.raw.rawValue {
nodes = SnapshotPresentation.presentRaw(inputAcquisition, options: inputOptions).nodes
} else {
let policy: SnapshotVisibilityFold.Policy = input.foldPolicy == "plain-viewport"
? .plainViewport
: .cursorProjected
nodes = try SnapshotPresentation.presentRegular(
inputAcquisition,
options: inputOptions,
policy: policy
).nodes
}
return ConformanceOutput(name: input.name, outcome: "success", nodes: nodes, error: nil)
} catch let failure as SnapshotPresentationFailure {
return ConformanceOutput(
name: input.name,
outcome: "failure",
nodes: [],
error: ConformanceError(
code: failure.code,
reason: reason(for: failure),
message: failure.message
)
)
} catch {
return ConformanceOutput(
name: input.name,
outcome: "failure",
nodes: [],
error: ConformanceError(
code: "IOS_SNAPSHOT_PRESENTATION_FAILED",
reason: "unexpected",
message: String(describing: error)
)
)
}
}

private func reason(for failure: SnapshotPresentationFailure) -> String {
switch failure {
case .regularNodeOutsideCumulativeClip:
return "regular-node-outside-cumulative-clip"
case .regularDegenerateNodeIsActionable:
return "regular-degenerate-actionable-node"
}
}

private let input = try JSONDecoder().decode(
Input.self,
BatchInput.self,
from: FileHandle.standardInput.readDataToEndOfFile()
)
private let options = PresentationOptions(
interactiveOnly: input.interactiveOnly,
depth: input.depth,
scope: input.scope,
raw: input.projection == CaptureHint.Projection.raw.rawValue
)
private let acquisition = SnapshotAcquisition(
hint: SnapshotPresentation.captureHint(for: options),
nodes: input.nodes.map { node in
RawAXNode(
index: node.index,
type: node.type,
label: node.label,
identifier: node.identifier,
value: node.value,
rect: node.rect,
enabled: node.enabled,
focused: node.focused,
selected: node.selected,
hittable: node.hittable,
depth: node.depth,
parentIndex: node.parentIndex,
hiddenContentAbove: node.hiddenContentAbove,
hiddenContentBelow: node.hiddenContentBelow
)
},
truncated: false,
effectiveDepth: nil,
viewport: input.viewport.cgRect
private let output = try JSONEncoder().encode(
BatchOutput(cases: input.cases.map(present))
)
private let result = try SnapshotPresentation.present(acquisition, options: options)
?? SnapshotPresentationResult(
nodes: [],
truncated: false,
effectiveDepth: nil,
customActions: nil,
qualityNodes: nil
)
private let output = try JSONEncoder().encode(Output(nodes: result.nodes))
FileHandle.standardOutput.write(output)
FileHandle.standardOutput.write(Data([0x0a]))
Loading
Loading