// // PerformanceTests.swift // KordantUITests // // Performance tests using XCTMetric for launch, scroll, navigation, // and image loading on physical devices. // // Acceptance Criteria: // - Cold launch < 2s on iPhone 12 // - Scroll 60fps on all lists // - All metrics within 10% of baseline // - Runs on iPhone SE, 12, 15 Pro // import XCTest final class LaunchPerformanceTests: UITestBase { override class var scenario: UITestScenario { .populatedDashboard } // MARK: - Cold Launch Performance /// Measures cold launch time using XCTApplicationLaunchMetric. /// Baseline: < 2.0 seconds on iPhone 12. func testColdLaunchPerformance() { let metric = XCTApplicationLaunchMetric( waitUntilResponsive: true, waitFor: .navigationBar("Dashboard") ) measure(metrics: [metric]) { let app = XCUIApplication() app.launchArguments = ["-UITesting"] app.launchEnvironment["UITestScenario"] = UITestScenario.populatedDashboard.rawValue app.launch() // Wait for dashboard to fully appear let dashboardNav = app.navigationBars["Dashboard"] XCTAssertTrue(dashboardNav.waitForExistence(timeout: 10), "Dashboard should appear within launch window") app.terminate() } } /// Measures warm launch time (app is already in memory cache from OS). /// Baseline: < 1.0 second on iPhone 12. func testWarmLaunchPerformance() { // First launch to prime the cache let warmApp = XCUIApplication() warmApp.launchArguments = ["-UITesting"] warmApp.launchEnvironment["UITestScenario"] = UITestScenario.populatedDashboard.rawValue warmApp.launch() let dashboardNav = warmApp.navigationBars["Dashboard"] XCTAssertTrue(dashboardNav.waitForExistence(timeout: 10)) warmApp.terminate() // Now measure warm launch let metric = XCTApplicationLaunchMetric( waitUntilResponsive: true, waitFor: .navigationBar("Dashboard") ) measure(metrics: [metric]) { let app = XCUIApplication() app.launchArguments = ["-UITesting"] app.launchEnvironment["UITestScenario"] = UITestScenario.populatedDashboard.rawValue app.launch() let dashboardNav = app.navigationBars["Dashboard"] XCTAssertTrue(dashboardNav.waitForExistence(timeout: 5), "Dashboard should appear in warm launch") app.terminate() } } } final class ScrollPerformanceTests: UITestBase { override class var scenario: UITestScenario { .populatedDashboard } // MARK: - Dashboard Scroll Performance /// Measures Dashboard scroll FPS via clock, CPU, and memory metrics. /// Acceptance: scroll remains smooth (no dropped frames). func testDashboardScrollPerformance() { navigateToTab(.dashboard) // Ensure dashboard is fully loaded let threatScore = text("Threat Score") XCTAssertTrue(threatScore.waitForExistence(timeout: 5)) // The dashboard uses a ScrollView — find it let scrollView = app.scrollViews.firstMatch XCTAssertTrue(scrollView.exists, "Dashboard scroll view should exist") // Measure scrolling through the entire dashboard content measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) { // Scroll down through all content for _ in 0..<5 { scrollView.swipeUp() // Small pause to let rendering catch up (simulates user scrolling) let _ = scrollView.waitForExistence(timeout: 0.1) } } } // MARK: - Alert List Scroll Performance /// Measures alert list scroll performance using LazyVStack. /// Acceptance: smooth scrolling with no frame drops. func testAlertListScrollPerformance() { navigateToTab(.alerts) // Wait for alerts to load let alertsNav = app.navigationBars["Alerts"] XCTAssertTrue(alertsNav.waitForExistence(timeout: 5)) // Wait for content to appear - populatedDashboard has 3 alerts let alertExists = app.staticTexts["Data Exposure Detected"].waitForExistence(timeout: 5) if !alertExists { // May be showing empty state or loading state return } // Use the scroll view in the alerts list let scrollViews = app.scrollViews guard scrollViews.count > 0 else { return } measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) { // Scroll up and down through the alert list for _ in 0..<3 { scrollViews.element(boundBy: 0).swipeUp() RunLoop.current.run(until: Date().addingTimeInterval(0.05)) } } } // MARK: - Service List Scroll Performance /// Measures Services tab list scroll performance. func testServiceListScrollPerformance() { navigateToTab(.services) let servicesNav = app.navigationBars["Services"] XCTAssertTrue(servicesNav.waitForExistence(timeout: 5)) // Services list uses a SwiftUI List which renders as a table/collection let tables = app.tables guard tables.count > 0 else { return } measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) { for _ in 0..<4 { tables.element(boundBy: 0).swipeUp() RunLoop.current.run(until: Date().addingTimeInterval(0.05)) } } } } final class NavigationPerformanceTests: UITestBase { override class var scenario: UITestScenario { .populatedDashboard } // MARK: - Tab Navigation Performance /// Measures tab bar switching performance. /// Each navigation transition should complete in under 500ms. func testTabNavigationPerformance() { navigateToTab(.dashboard) let dashboardNav = app.navigationBars["Dashboard"] XCTAssertTrue(dashboardNav.waitForExistence(timeout: 5)) measure(metrics: [XCTClockMetric(), XCTCPUMetric()]) { // Navigate through all tabs in sequence navigateToTab(.services) let _ = app.navigationBars["Services"].waitForExistence(timeout: 3) navigateToTab(.alerts) let _ = app.navigationBars["Alerts"].waitForExistence(timeout: 3) navigateToTab(.settings) let _ = app.navigationBars["Settings"].waitForExistence(timeout: 3) navigateToTab(.account) let _ = app.navigationBars["Account"].waitForExistence(timeout: 3) navigateToTab(.dashboard) let _ = app.navigationBars["Dashboard"].waitForExistence(timeout: 3) } } // MARK: - Service Detail Navigation Performance /// Measures navigation from Services list into individual service detail views. func testServiceDetailNavigationPerformance() { navigateToTab(.services) let servicesNav = app.navigationBars["Services"] XCTAssertTrue(servicesNav.waitForExistence(timeout: 5)) measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) { // Tap DarkWatch service let darkWatchButton = app.buttons["DarkWatch"] if darkWatchButton.exists { darkWatchButton.tap() let _ = app.navigationBars["DarkWatch"].waitForExistence(timeout: 3) // Navigate back app.navigationBars.buttons.element(boundBy: 0).tap() let _ = servicesNav.waitForExistence(timeout: 3) } // Tap SpamShield service let spamShieldButton = app.buttons["SpamShield"] if spamShieldButton.exists { spamShieldButton.tap() let _ = app.navigationBars["SpamShield"].waitForExistence(timeout: 3) app.navigationBars.buttons.element(boundBy: 0).tap() let _ = servicesNav.waitForExistence(timeout: 3) } } } } final class DataLoadingPerformanceTests: UITestBase { override class var scenario: UITestScenario { .populatedDashboard } // MARK: - Dashboard Data Load Performance /// Measures the time for dashboard data to fully load and display. func testDashboardDataLoadPerformance() { // Relaunch with populated data scenario app.terminate() app = XCUIApplication() app.launchArguments = ["-UITesting"] app.launchEnvironment["UITestScenario"] = UITestScenario.populatedDashboard.rawValue app.launch() measure(metrics: [XCTClockMetric(), XCTCPUMetric(), XCTMemoryMetric()]) { navigateToTab(.dashboard) // Wait for all dashboard elements to appear let score = text("Threat Score") let exists = score.waitForExistence(timeout: 10) XCTAssertTrue(exists, "Dashboard data should load within timeout") } } // MARK: - DarkWatch Data Load Performance /// Measures DarkWatch service data loading time. func testDarkWatchDataLoadPerformance() { app.terminate() app = XCUIApplication() app.launchArguments = ["-UITesting"] app.launchEnvironment["UITestScenario"] = UITestScenario.darkWatchPopulated.rawValue app.launch() measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) { navigateToTab(.services) let darkWatchButton = app.buttons["DarkWatch"] XCTAssertTrue(darkWatchButton.waitForExistence(timeout: 5)) darkWatchButton.tap() // Wait for watchlist items to appear let watchlistSection = app.staticTexts["Watchlist"] let loaded = watchlistSection.waitForExistence(timeout: 5) XCTAssertTrue(loaded || app.staticTexts["Exposures"].waitForExistence(timeout: 3), "DarkWatch data should load") // Navigate back app.navigationBars.buttons.element(boundBy: 0).tap() } } } final class MemoryPerformanceTests: UITestBase { override class var scenario: UITestScenario { .populatedDashboard } // MARK: - Memory Usage During Navigation /// Measures memory usage across a full app navigation flow. /// Baseline: should not exceed 150MB on iPhone 12. func testMemoryUsageAcrossNavigationFlow() { navigateToTab(.dashboard) let dashboardNav = app.navigationBars["Dashboard"] XCTAssertTrue(dashboardNav.waitForExistence(timeout: 5)) measure(metrics: [XCTMemoryMetric()]) { // Full navigation flow through the app navigateToTab(.dashboard) // Scroll the dashboard let scrollView = app.scrollViews.firstMatch if scrollView.exists { for _ in 0..<3 { scrollView.swipeUp() } } navigateToTab(.services) let _ = app.navigationBars["Services"].waitForExistence(timeout: 3) navigateToTab(.alerts) let _ = app.navigationBars["Alerts"].waitForExistence(timeout: 3) navigateToTab(.settings) let _ = app.navigationBars["Settings"].waitForExistence(timeout: 3) navigateToTab(.dashboard) let _ = app.navigationBars["Dashboard"].waitForExistence(timeout: 3) } } // MARK: - Memory Leak Detection After Navigation /// Verifies memory returns to baseline after navigating through views. func testMemoryReturnsAfterNavigation() { navigateToTab(.dashboard) let dashboardNav = app.navigationBars["Dashboard"] XCTAssertTrue(dashboardNav.waitForExistence(timeout: 5)) // Navigate through several views to build up state navigateToTab(.services) let _ = app.navigationBars["Services"].waitForExistence(timeout: 3) // Tap into a service let darkWatchButton = app.buttons["DarkWatch"] if darkWatchButton.exists { darkWatchButton.tap() let _ = app.navigationBars["DarkWatch"].waitForExistence(timeout: 3) // Go back app.navigationBars.buttons.element(boundBy: 0).tap() let _ = app.navigationBars["Services"].waitForExistence(timeout: 3) } navigateToTab(.alerts) let _ = app.navigationBars["Alerts"].waitForExistence(timeout: 3) // Return to dashboard navigateToTab(.dashboard) // Measure that memory is stable (no leaks) measure(metrics: [XCTMemoryMetric()]) { // Check that navigating again doesn't increase memory significantly navigateToTab(.services) let _ = app.navigationBars["Services"].waitForExistence(timeout: 3) navigateToTab(.alerts) let _ = app.navigationBars["Alerts"].waitForExistence(timeout: 3) navigateToTab(.dashboard) let _ = app.navigationBars["Dashboard"].waitForExistence(timeout: 3) } } }