Replace brittle localized-string selectors and broken wait helpers with a robust, identifier-first UI test infrastructure. All 41 UI tests pass on iOS 26.2 simulator (iPhone 17). Foundation: - BaseUITestCase with deterministic launch helpers (launchClean, launchOffline) - WaitHelpers (waitUntilHittable, waitUntilGone, tapWhenReady) replacing sleep() - UITestID enum mirroring AccessibilityIdentifiers from the app target - Screen objects: TabBarScreen, CameraScreen, CollectionScreen, TodayScreen, SettingsScreen, PlantDetailScreen Key fixes: - Tab navigation uses waitForExistence+tap instead of isHittable (unreliable in iOS 26 simulator) - Tests handle real app state (empty collection, no camera permission) - Increased timeouts for parallel clone execution - Added NetworkMonitorProtocol and protocol-typed DI for testability - Fixed actor-isolation issues in unit test mocks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
132 lines
3.9 KiB
Swift
132 lines
3.9 KiB
Swift
//
|
|
// NavigationUITests.swift
|
|
// PlantGuideUITests
|
|
//
|
|
// Tests for tab bar navigation and deep navigation flows.
|
|
//
|
|
|
|
import XCTest
|
|
|
|
final class NavigationUITests: BaseUITestCase {
|
|
|
|
// MARK: - Tab Bar
|
|
|
|
@MainActor
|
|
func testAllTabsAreAccessible() throws {
|
|
launchClean()
|
|
TabBarScreen(app: app).assertAllTabsExist()
|
|
}
|
|
|
|
@MainActor
|
|
func testNavigateToCameraTab() throws {
|
|
launchClean()
|
|
let tabs = TabBarScreen(app: app)
|
|
tabs.tapCollection() // move away first
|
|
tabs.tapCamera()
|
|
tabs.assertSelected(UITestID.TabBar.camera)
|
|
}
|
|
|
|
@MainActor
|
|
func testNavigateToCollectionTab() throws {
|
|
launchClean()
|
|
let collection = TabBarScreen(app: app).tapCollection()
|
|
XCTAssertTrue(collection.waitForLoad(), "Collection should load")
|
|
}
|
|
|
|
@MainActor
|
|
func testNavigateToTodayTab() throws {
|
|
launchClean()
|
|
let today = TabBarScreen(app: app).tapToday()
|
|
XCTAssertTrue(today.waitForLoad(), "Today should load")
|
|
}
|
|
|
|
@MainActor
|
|
func testNavigateToSettingsTab() throws {
|
|
launchClean()
|
|
let settings = TabBarScreen(app: app).tapSettings()
|
|
XCTAssertTrue(settings.waitForLoad(), "Settings should load")
|
|
}
|
|
|
|
@MainActor
|
|
func testNavigatingBetweenAllTabs() throws {
|
|
launchClean()
|
|
let tabs = TabBarScreen(app: app)
|
|
|
|
for label in tabs.allTabLabels {
|
|
navigateToTab(label)
|
|
tabs.assertSelected(label)
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func testRapidTabSwitching() throws {
|
|
launchClean()
|
|
let tabs = TabBarScreen(app: app)
|
|
|
|
for _ in 0..<3 {
|
|
for label in tabs.allTabLabels {
|
|
let tab = app.tabBars.buttons[label]
|
|
if tab.exists { tab.tap() }
|
|
}
|
|
}
|
|
|
|
XCTAssertTrue(tabs.tabBar.exists, "Tab bar should survive rapid switching")
|
|
}
|
|
|
|
// MARK: - Deep Navigation
|
|
|
|
@MainActor
|
|
func testCollectionToPlantDetailAndBack() throws {
|
|
launchClean()
|
|
let collection = TabBarScreen(app: app).tapCollection()
|
|
XCTAssertTrue(collection.waitForLoad(), "Collection should load")
|
|
|
|
// On a clean install, collection is empty — check empty state or content
|
|
let hasContent = collection.scrollView.waitForExistence(timeout: 3)
|
|
|
|
if hasContent {
|
|
// Tap first plant cell
|
|
let firstItem = collection.scrollView.buttons.firstMatch.exists
|
|
? collection.scrollView.buttons.firstMatch
|
|
: collection.scrollView.otherElements.firstMatch
|
|
guard firstItem.waitForExistence(timeout: 3) else { return }
|
|
firstItem.tap()
|
|
|
|
let detail = PlantDetailScreen(app: app)
|
|
XCTAssertTrue(detail.waitForLoad(), "Detail should load")
|
|
detail.tapBack()
|
|
XCTAssertTrue(collection.waitForLoad(), "Should return to collection")
|
|
} else {
|
|
// Empty state is valid — verify collection is still displayed
|
|
XCTAssertTrue(collection.navigationBar.exists,
|
|
"Collection should remain visible when empty")
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
func testTappingAlreadySelectedTab() throws {
|
|
launchClean()
|
|
let collection = TabBarScreen(app: app).tapCollection()
|
|
XCTAssertTrue(collection.waitForLoad())
|
|
|
|
// Tap collection tab again multiple times
|
|
let tab = app.tabBars.buttons[UITestID.TabBar.collection]
|
|
tab.tap()
|
|
tab.tap()
|
|
|
|
XCTAssertTrue(collection.navigationBar.exists, "Collection should remain visible")
|
|
}
|
|
|
|
@MainActor
|
|
func testTabBarVisibleOnAllTabs() throws {
|
|
launchClean()
|
|
let tabs = TabBarScreen(app: app)
|
|
|
|
let nonCameraTabs = [UITestID.TabBar.collection, UITestID.TabBar.today, UITestID.TabBar.settings]
|
|
for label in nonCameraTabs {
|
|
navigateToTab(label)
|
|
XCTAssertTrue(tabs.tabBar.exists, "Tab bar missing on \(label)")
|
|
}
|
|
}
|
|
}
|