mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
The Go runner transport already calls these methods; the in-device runner implemented them only latently. They become load-bearing on the device path, where the hybrid's legacy-companion fallback is absent. Backward compatible: the simulator hybrid never calls them.
75 lines
2.6 KiB
Swift
75 lines
2.6 KiB
Swift
import XCTest
|
|
|
|
// Launches and terminates apps through the automation session. Lifecycle
|
|
// performed outside the session leaves its cached app proxies bound to dead
|
|
// processes, after which snapshots hang and typing asserts.
|
|
enum AppLifecycle {
|
|
enum LifecycleError: Error {
|
|
case failed(String)
|
|
}
|
|
|
|
static func launch(bundleIdentifier: String, foregroundIfRunning: Bool) throws {
|
|
try onMainCatching {
|
|
let application = XCUIApplication(bundleIdentifier: bundleIdentifier)
|
|
if foregroundIfRunning {
|
|
// activate starts the app when needed and foregrounds it when
|
|
// already running, without a forced relaunch.
|
|
application.activate()
|
|
} else {
|
|
application.launch()
|
|
}
|
|
}
|
|
}
|
|
|
|
static func terminate(bundleIdentifier: String) throws {
|
|
try onMainCatching {
|
|
let application = XCUIApplication(bundleIdentifier: bundleIdentifier)
|
|
if application.state == .notRunning {
|
|
return
|
|
}
|
|
application.terminate()
|
|
}
|
|
}
|
|
|
|
// state reports the app's run state in the vocabulary the Go transport maps
|
|
// to a process state: foreground, background, or notRunning. On the
|
|
// simulator the hybrid never calls it; on device it backs ForegroundApp.
|
|
static func state(bundleIdentifier: String) -> String {
|
|
var result = "notRunning"
|
|
let collect = {
|
|
switch XCUIApplication(bundleIdentifier: bundleIdentifier).state {
|
|
case .runningForeground:
|
|
result = "foreground"
|
|
case .runningBackground, .runningBackgroundSuspended:
|
|
result = "background"
|
|
default:
|
|
result = "notRunning"
|
|
}
|
|
}
|
|
if Thread.isMainThread {
|
|
collect()
|
|
} else {
|
|
DispatchQueue.main.sync(execute: collect)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// onMainCatching runs automation work on the main thread and converts a
|
|
// framework assertion into a thrown error so the server survives it.
|
|
private static func onMainCatching(_ work: @escaping () -> Void) throws {
|
|
var caughtError: NSError?
|
|
var completed = false
|
|
let run = {
|
|
completed = CompanionRunCatching(work, &caughtError)
|
|
}
|
|
if Thread.isMainThread {
|
|
run()
|
|
} else {
|
|
DispatchQueue.main.sync(execute: run)
|
|
}
|
|
if !completed {
|
|
throw LifecycleError.failed(caughtError?.localizedDescription ?? "unknown")
|
|
}
|
|
}
|
|
}
|