import XCTest /// Opt-in tests against the actual NAS deployment. No injected replies. final class VoiceLiveTests: XCTestCase { private var app: XCUIApplication! override func record(_ issue: XCTIssue) { if let app, app.state == .runningForeground { capture("Failure state") let hierarchy = XCTAttachment(string: app.debugDescription) hierarchy.name = "Failure UI hierarchy" hierarchy.lifetime = .keepAlways add(hierarchy) } super.record(issue) } override func tearDownWithError() throws { app?.terminate() } override func setUpWithError() throws { try XCTSkipUnless(ProcessInfo.processInfo.environment["HOPE_RUN_LIVE_TESTS"] == "1", "Requires the live Hope server") continueAfterFailure = false app = XCUIApplication() app.launchArguments = ["--ui-testing", "--reset-ui-testing", "--live-testing"] app.launch() } func testLiveConversationAndBackgroundTask() { app.buttons["Start chatting"].tap() let composer = app.descendants(matching: .any).matching(identifier: "Message Hope").firstMatch XCTAssertTrue(composer.waitForExistence(timeout: 60), "Agent must join the real LiveKit room") composer.tap() composer.typeText("What is six times seven? Reply in one short sentence.") app.buttons["Send message"].tap() let reply = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] 'forty-two' OR label CONTAINS[c] 'forty two' OR label CONTAINS '42'")).firstMatch XCTAssertTrue(reply.waitForExistence(timeout: 60), "Qwen must produce a real response") capture("Live Qwen reply") composer.tap() composer.typeText("Compare espresso and filter coffee in a short background task. Use your existing knowledge; no web search needed.") app.buttons["Send message"].tap() app.tabBars.buttons["Activity"].tap() let complete = app.staticTexts["Complete"].firstMatch XCTAssertTrue(complete.waitForExistence(timeout: 90), "Background task must finish and deliver its result") capture("Live background result") app.tabBars.buttons["Talk"].tap() app.buttons["End conversation"].tap() XCTAssertTrue(app.buttons["Start chatting"].waitForExistence(timeout: 20)) // A second room must connect cleanly after the first was torn down. app.buttons["Start chatting"].tap() XCTAssertTrue(composer.waitForExistence(timeout: 60)) app.buttons["End conversation"].tap() XCTAssertTrue(app.buttons["Start chatting"].waitForExistence(timeout: 20)) } private func capture(_ name: String) { let attachment = XCTAttachment(screenshot: app.screenshot()) attachment.name = name attachment.lifetime = .keepAlways add(attachment) } }