import XCTest /// UI Tests for Portfolio Journal app /// These tests verify the app's user interface and navigation flows final class PortfolioJournalUITests: XCTestCase { var app: XCUIApplication! override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() app.launchArguments = ["--uitesting"] app.launch() } override func tearDownWithError() throws { app = nil } // MARK: - App Launch Tests func testAppLaunches() throws { // Verify the app launches successfully XCTAssertTrue(app.exists) } func testTabBarExists() throws { // Wait for the tab bar to appear let tabBar = app.tabBars.firstMatch let exists = tabBar.waitForExistence(timeout: 5) XCTAssertTrue(exists, "Tab bar should exist after launch") } // MARK: - Navigation Tests func testDashboardTabIsSelected() throws { // Dashboard should be the default selected tab let tabBar = app.tabBars.firstMatch _ = tabBar.waitForExistence(timeout: 5) // Look for Dashboard tab button let dashboardTab = tabBar.buttons["Dashboard"] if dashboardTab.exists { XCTAssertTrue(dashboardTab.isSelected || dashboardTab.isHittable) } } func testNavigateToSourcesTab() throws { let tabBar = app.tabBars.firstMatch _ = tabBar.waitForExistence(timeout: 5) let sourcesTab = tabBar.buttons["Sources"] if sourcesTab.exists { sourcesTab.tap() // Verify we're on the Sources screen XCTAssertTrue(sourcesTab.isSelected || sourcesTab.isHittable) } } func testNavigateToGoalsTab() throws { let tabBar = app.tabBars.firstMatch _ = tabBar.waitForExistence(timeout: 5) let goalsTab = tabBar.buttons["Goals"] if goalsTab.exists { goalsTab.tap() XCTAssertTrue(goalsTab.isSelected || goalsTab.isHittable) } } func testNavigateToJournalTab() throws { let tabBar = app.tabBars.firstMatch _ = tabBar.waitForExistence(timeout: 5) let journalTab = tabBar.buttons["Journal"] if journalTab.exists { journalTab.tap() XCTAssertTrue(journalTab.isSelected || journalTab.isHittable) } } func testNavigateToSettingsTab() throws { let tabBar = app.tabBars.firstMatch _ = tabBar.waitForExistence(timeout: 5) let settingsTab = tabBar.buttons["Settings"] if settingsTab.exists { settingsTab.tap() XCTAssertTrue(settingsTab.isSelected || settingsTab.isHittable) } } // MARK: - All Tabs Navigation Test func testNavigateThroughAllTabs() throws { let tabBar = app.tabBars.firstMatch guard tabBar.waitForExistence(timeout: 5) else { XCTFail("Tab bar not found") return } let tabs = ["Dashboard", "Sources", "Goals", "Journal", "Settings"] for tabName in tabs { let tab = tabBar.buttons[tabName] if tab.exists && tab.isHittable { tab.tap() // Give time for navigation Thread.sleep(forTimeInterval: 0.3) } } // Return to Dashboard let dashboardTab = tabBar.buttons["Dashboard"] if dashboardTab.exists { dashboardTab.tap() } } // MARK: - App Store Screenshot Capture /// Captures full-screen screenshots of the main tabs with demo data for App Store /// marketing. Launches the app in `--screenshots` mode (onboarding/lock skipped, /// SampleDataService seeded). Language can be driven via the SCREENSHOT_LANG env var /// (e.g. "es", "de"). Screenshots are attached to the test result and extracted from /// the .xcresult afterwards by the framing pipeline. func testCaptureScreenshots() throws { let capture = XCUIApplication() capture.launchArguments = ["--screenshots"] if let lang = ProcessInfo.processInfo.environment["SCREENSHOT_LANG"], !lang.isEmpty { capture.launchArguments += ["-AppleLanguages", "(\(lang))", "-AppleLocale", lang] } capture.launch() let tabBar = capture.tabBars.firstMatch guard tabBar.waitForExistence(timeout: 20) else { XCTFail("Tab bar not found in screenshot mode") return } // Let demo data seed and charts render. Thread.sleep(forTimeInterval: 3.0) func snap(_ name: String) { let shot = XCTAttachment(screenshot: capture.screenshot()) shot.name = name shot.lifetime = .keepAlways add(shot) } // Dashboard is already selected on launch. snap("01_Dashboard") for (index, tabName) in ["Sources", "Goals", "Journal", "Settings"].enumerated() { let tab = tabBar.buttons[tabName] if tab.waitForExistence(timeout: 5), tab.isHittable { tab.tap() Thread.sleep(forTimeInterval: 1.5) snap(String(format: "%02d_%@", index + 2, tabName)) } } } }