import XCTest /// Real UI interactions with isolated preferences. These cover the client, /// including offline recovery; they do not claim to verify the live backend. final class VoiceUITests: XCTestCase { private var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launchArguments = ["--ui-testing", "--reset-ui-testing"] } func testLogoutDoesNotLeakPriorAccountTranscriptOrTasks() { app.launchArguments.append("--ui-fixtures") app.launch() XCTAssertTrue(app.staticTexts["Help me plan a quiet weekend."].waitForExistence(timeout: 10)) app.buttons["Sign out"].tap() XCTAssertTrue(app.secureTextFields["Password"].waitForExistence(timeout: 10)) let username = app.textFields["Username"] username.tap() username.typeText("second-user") app.secureTextFields["Password"].tap() app.secureTextFields["Password"].typeText("offline-only") app.buttons["Sign in"].tap() XCTAssertTrue(app.buttons["Start chatting"].waitForExistence(timeout: 10)) XCTAssertFalse(app.staticTexts["Help me plan a quiet weekend."].exists) app.tabBars.buttons["Activity"].tap() XCTAssertFalse(app.staticTexts["Search backend timed out after 30s"].exists) } func testWelcomeNavigationAndOfflineRecovery() { app.launch() XCTAssertTrue(app.buttons["Start chatting"].waitForExistence(timeout: 10)) XCTAssertTrue(app.staticTexts["Your microphone starts after you connect."].exists) app.buttons["Type"].tap() XCTAssertTrue(app.buttons["Start chatting"].exists) XCTAssertTrue(app.staticTexts["Your microphone stays off."].exists) app.buttons["Start chatting"].tap() XCTAssertTrue(app.buttons["Try again"].waitForExistence(timeout: 20)) XCTAssertTrue(app.staticTexts["Couldn’t connect"].exists) app.buttons["Settings"].tap() XCTAssertTrue(app.textFields["Server URL"].waitForExistence(timeout: 5)) capture("Offline recovery") } func testVoiceSelectionPersistsAndURLValidation() { app.launch() app.buttons["Settings"].tap() app.buttons.matching(NSPredicate(format: "label CONTAINS 'Speaking voice'")).firstMatch.tap() let voice = app.buttons.matching(NSPredicate(format: "label CONTAINS 'Jenny'")).firstMatch XCTAssertTrue(voice.waitForExistence(timeout: 5)) voice.tap() XCTAssertTrue(app.buttons.matching(NSPredicate(format: "label CONTAINS 'Speaking voice' AND label CONTAINS 'Jenny'")).firstMatch.exists) let server = app.textFields["Server URL"] server.tap() let original = server.value as? String ?? "" server.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: original.count) + "http://example.com") app.buttons["Save server address"].tap() XCTAssertTrue(app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'must use https'")).firstMatch.waitForExistence(timeout: 5)) let invalid = server.value as? String ?? "" server.tap() server.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: invalid.count) + " https://127.0.0.1:1 ") app.buttons["Save server address"].tap() XCTAssertTrue(app.staticTexts["Server address saved."].waitForExistence(timeout: 5)) XCTAssertEqual(server.value as? String, "https://127.0.0.1:1") let unchanged = server.value as? String ?? "" server.tap() server.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: unchanged.count) + " https://127.0.0.1:2 ") app.buttons["Save server address"].tap() Thread.sleep(forTimeInterval: 2) XCTAssertTrue(app.navigationBars["Settings"].exists) XCTAssertTrue(app.staticTexts["Server address saved."].exists) XCTAssertEqual(server.value as? String, "https://127.0.0.1:2") app.terminate() app.launchArguments = ["--ui-testing"] app.launch() app.buttons["Settings"].tap() XCTAssertTrue(app.buttons.matching(NSPredicate(format: "label CONTAINS 'Speaking voice' AND label CONTAINS 'Jenny'")).firstMatch.waitForExistence(timeout: 5)) XCTAssertEqual(app.textFields["Server URL"].value as? String, "https://127.0.0.1:2") } func testTranscriptClearConfirmationAndActivityDetails() { app.launchArguments.append("--ui-fixtures") app.launch() XCTAssertTrue(app.staticTexts["Help me plan a quiet weekend."].waitForExistence(timeout: 10)) app.buttons["Conversation options"].tap() app.buttons["Clear conversation"].tap() app.buttons["Cancel"].tap() XCTAssertTrue(app.staticTexts["Help me plan a quiet weekend."].exists) app.buttons["Conversation options"].tap() app.buttons["Clear conversation"].tap() app.buttons["Clear conversation"].tap() XCTAssertTrue(app.buttons["Start chatting"].exists) XCTAssertFalse(app.staticTexts["Help me plan a quiet weekend."].exists) app.tabBars.buttons["Activity"].tap() XCTAssertTrue(app.staticTexts["Needs attention"].waitForExistence(timeout: 5)) XCTAssertTrue(app.staticTexts["Search backend timed out after 30s"].exists) app.staticTexts["Search backend timed out after 30s"].tap() XCTAssertTrue(app.staticTexts["Fetching benchmark articles…"].waitForExistence(timeout: 5)) capture("Activity details") } func testRestoredTranscriptStaysAtTopUntilLatest() { app.launchArguments.append("--ui-long-transcript") app.launch() let first = app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'Earlier message 1:'")).firstMatch let last = app.staticTexts.matching(NSPredicate(format: "label CONTAINS 'Earlier message 30:'")).firstMatch XCTAssertTrue(first.waitForExistence(timeout: 10)) XCTAssertTrue(app.buttons["Latest"].exists) XCTAssertFalse(last.isHittable) app.buttons["Latest"].tap() XCTAssertTrue(last.waitForExistence(timeout: 5)) XCTAssertTrue(last.isHittable) } func testLargeTypeNavigation() { app.launchArguments += ["-UIPreferredContentSizeCategoryName", "UICTContentSizeCategoryAccessibilityXXXL"] app.launch() XCTAssertTrue(app.buttons["Start chatting"].waitForExistence(timeout: 10)) XCTAssertTrue(app.buttons["Start chatting"].isHittable) app.tabBars.buttons["Activity"].tap() XCTAssertTrue(app.staticTexts["No tasks yet"].waitForExistence(timeout: 15)) app.buttons["Settings"].tap() XCTAssertTrue(app.navigationBars["Settings"].exists) capture("Accessibility text size") } private func capture(_ name: String) { let attachment = XCTAttachment(screenshot: app.screenshot()) attachment.name = name attachment.lifetime = .keepAlways add(attachment) } }