diff --git a/companion/Sources/AppLifecycle.swift b/companion/Sources/AppLifecycle.swift index 065abc5..9ebffb9 100644 --- a/companion/Sources/AppLifecycle.swift +++ b/companion/Sources/AppLifecycle.swift @@ -9,6 +9,7 @@ enum AppLifecycle { } static func launch(bundleIdentifier: String, foregroundIfRunning: Bool) throws { + var reached = XCUIApplication.State.unknown try onMainCatching { let application = XCUIApplication(bundleIdentifier: bundleIdentifier) if foregroundIfRunning { @@ -18,6 +19,17 @@ enum AppLifecycle { } else { application.launch() } + reached = application.state + } + // A refused launch is recorded as a test issue that never throws, so + // without this the runner answers ok for an app that is not running + // and the host learns nothing until its own bound expires. + switch reached { + case .runningForeground, .runningBackground, .runningBackgroundSuspended: + return + default: + throw LifecycleError.failed( + "\(bundleIdentifier) is \(name(of: reached)) after launch") } } @@ -54,6 +66,21 @@ enum AppLifecycle { return result } + private static func name(of state: XCUIApplication.State) -> String { + switch state { + case .runningForeground: + return "foreground" + case .runningBackground: + return "background" + case .runningBackgroundSuspended: + return "suspended" + case .notRunning: + return "not running" + default: + return "unknown" + } + } + // 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 {