mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
c6a8e78768
tvOS has no SpringBoard (it uses PineBoard/HeadBoard), so probing
com.apple.springboard for blocking system modals raises an XCTest failure
("Application com.apple.springboard is not running") that safely() cannot
trap, terminating the whole runner test. Every snapshot and alert
resolution then failed, and the CLI misreported it as the screen
"overwhelming the accessibility capture".
resolveBlockingSystemModal — the single chokepoint every snapshot/alert
path funnels through — assumed a SpringBoard host always exists. Model that
assumption explicitly with `hasSpringBoardSystemModalHost` and bail to
`.absent` when no host exists, so app-owned alert queries still run. The
fourth probe site (shouldRouteToSpringboardBlockingSystemModal) is already
`#if os(iOS)` and needs no change.
Add tvOS regression tests covering the snapshot and alert-resolution paths.
114 lines
3.9 KiB
Swift
114 lines
3.9 KiB
Swift
import XCTest
|
|
|
|
extension RunnerTests {
|
|
struct ResolvedBlockingSystemModal {
|
|
let root: XCUIElement
|
|
let ownerApp: XCUIApplication
|
|
let actions: [XCUIElement]
|
|
}
|
|
|
|
enum BlockingSystemModalResolution {
|
|
case absent
|
|
case unresolved
|
|
case resolved(ResolvedBlockingSystemModal)
|
|
}
|
|
|
|
func resolveBlockingSystemModal(
|
|
deadline: Date
|
|
) -> BlockingSystemModalResolution {
|
|
guard Self.hasSpringBoardSystemModalHost else {
|
|
return .absent
|
|
}
|
|
|
|
guard let springboardModal = firstBlockingSystemModal(
|
|
in: springboard,
|
|
deadline: deadline
|
|
) else {
|
|
return .absent
|
|
}
|
|
|
|
let springboardActions = actionableElements(in: springboardModal)
|
|
if !RemoteHostedSystemModalPolicy.shouldProbeRemoteHost(
|
|
springboardActionCount: springboardActions.count
|
|
) {
|
|
return .resolved(
|
|
ResolvedBlockingSystemModal(
|
|
root: springboardModal,
|
|
ownerApp: springboard,
|
|
actions: springboardActions
|
|
)
|
|
)
|
|
}
|
|
|
|
guard Date() < deadline, let remoteModal = remoteHostedSystemModal(deadline: deadline) else {
|
|
return .unresolved
|
|
}
|
|
return .resolved(remoteModal)
|
|
}
|
|
|
|
// AccessorySetupUI mirrors into SpringBoard, but its mirrored elements are not
|
|
// reliably hittable. Query the host directly; activating it breaks the picker.
|
|
private static let remoteSystemModalHostBundleIds = [
|
|
"com.apple.AccessorySetupUI"
|
|
]
|
|
|
|
private func remoteHostedSystemModal(deadline: Date) -> ResolvedBlockingSystemModal? {
|
|
for bundleId in Self.remoteSystemModalHostBundleIds {
|
|
guard Date() < deadline else { return nil }
|
|
let host = XCUIApplication(bundleIdentifier: bundleId)
|
|
let state = safely("REMOTE_MODAL_STATE", XCUIApplication.State.unknown) { host.state }
|
|
guard RemoteHostedSystemModalPolicy.isEligibleHostState(state) else { continue }
|
|
guard Date() < deadline else { return nil }
|
|
|
|
// A dismissed remote host can raise kAXErrorServerNotFound. The safe queries
|
|
// inside actionableElements turn that disappearance into an empty result.
|
|
let actions = actionableElements(in: host)
|
|
guard !actions.isEmpty else { continue }
|
|
return ResolvedBlockingSystemModal(root: host, ownerApp: host, actions: actions)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
enum RemoteHostedSystemModalPolicy {
|
|
static func shouldProbeRemoteHost(springboardActionCount: Int) -> Bool {
|
|
springboardActionCount == 0
|
|
}
|
|
|
|
static func isEligibleHostState(_ state: XCUIApplication.State) -> Bool {
|
|
state == .runningForeground
|
|
}
|
|
}
|
|
|
|
#if AGENT_DEVICE_RUNNER_UNIT_TESTS
|
|
extension RunnerTests {
|
|
func testRemoteHostProbeRunsOnlyWhenSpringboardModalHasNoActions() {
|
|
XCTAssertTrue(RemoteHostedSystemModalPolicy.shouldProbeRemoteHost(springboardActionCount: 0))
|
|
XCTAssertFalse(RemoteHostedSystemModalPolicy.shouldProbeRemoteHost(springboardActionCount: 1))
|
|
XCTAssertFalse(RemoteHostedSystemModalPolicy.shouldProbeRemoteHost(springboardActionCount: 3))
|
|
}
|
|
|
|
func testRemoteHostStateGateFailsClosedToForeground() {
|
|
XCTAssertTrue(RemoteHostedSystemModalPolicy.isEligibleHostState(.runningForeground))
|
|
XCTAssertFalse(RemoteHostedSystemModalPolicy.isEligibleHostState(.runningBackground))
|
|
XCTAssertFalse(RemoteHostedSystemModalPolicy.isEligibleHostState(.notRunning))
|
|
XCTAssertFalse(RemoteHostedSystemModalPolicy.isEligibleHostState(.unknown))
|
|
}
|
|
|
|
// tvOS has no SpringBoard host, so both the snapshot and alert-resolution paths
|
|
// must resolve without probing com.apple.springboard (#1351).
|
|
#if os(tvOS)
|
|
func testResolveBlockingSystemModalIsAbsentWithoutSpringBoardOnTvOS() {
|
|
guard case .absent = resolveBlockingSystemModal(deadline: .distantFuture) else {
|
|
XCTFail("tvOS blocking system-modal resolution must be .absent")
|
|
return
|
|
}
|
|
}
|
|
|
|
func testBlockingSystemAlertSnapshotIsNilOnTvOS() {
|
|
XCTAssertNil(blockingSystemAlertSnapshot(deadline: .distantFuture))
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|