import Foundation import XCTest @MainActor extension XCUIApplication { /// Launch with a freshly-fetched pairing URL so the test starts from a /// known paired state. Hits `/pair-qr` on the sidecar to obtain a /// one-time-use token, then passes it to the app via launch args. /// Fails the current test if the sidecar is unreachable. func launchPaired(file: StaticString = #file, line: UInt = #line) { guard let url = TestSidecar.freshPairURL() else { XCTFail("Could not fetch fresh pair URL from sidecar — is it running?", file: file, line: line) return } launchArguments = ["--uitest", "--reset-state", "--pair-with-url", url] launch() } /// Launch with state reset so the PairingFlowView is shown. func launchUnpaired() { launchArguments = ["--uitest", "--reset-state"] launch() } /// Launch with a specific pairing URL (for the explicit deep-link test). func launchWithPairing(url: String) { launchArguments = ["--uitest", "--reset-state", "--pair-with-url", url] launch() } /// Launch directly into the lock screen (faceid enabled + forced lock). /// Resets state first so the test isn't polluted by previous runs. func launchLocked() { launchArguments = ["--uitest", "--reset-state", "--enable-faceid", "--force-lock"] launch() } } // MARK: - Sidecar helpers enum TestSidecar { static let baseURL = "http://10.13.37.2:17373" static let bearer = "GeoZytsPPGwItCHRNu8EwoZGlHH5iUAx" /// Synchronously fetch a fresh pairing URL from the sidecar. static func freshPairURL() -> String? { let url = URL(string: "\(baseURL)/pair-qr?token=\(bearer)&format=url")! var result: String? let sem = DispatchSemaphore(value: 0) URLSession.shared.dataTask(with: url) { data, _, _ in defer { sem.signal() } guard let data = data, let text = String(data: data, encoding: .utf8) else { return } if let match = text.range(of: #"pi-remote://[^\s]+"#, options: .regularExpression) { result = String(text[match]) } }.resume() _ = sem.wait(timeout: .now() + 5) return result } /// Delete a session via the sidecar REST API (best-effort, for cleanup). static func deleteSession(_ id: String) { var req = URLRequest(url: URL(string: "\(baseURL)/sessions/\(id)")!) req.httpMethod = "DELETE" req.setValue("Bearer \(bearer)", forHTTPHeaderField: "Authorization") let sem = DispatchSemaphore(value: 0) URLSession.shared.dataTask(with: req) { _, _, _ in sem.signal() }.resume() _ = sem.wait(timeout: .now() + 5) } }