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>
78 lines
2.2 KiB
Swift
78 lines
2.2 KiB
Swift
//
|
|
// TabBarScreen.swift
|
|
// PlantGuideUITests
|
|
//
|
|
// Screen object for the main tab bar.
|
|
//
|
|
|
|
import XCTest
|
|
|
|
struct TabBarScreen {
|
|
let app: XCUIApplication
|
|
|
|
// MARK: - Elements
|
|
|
|
var tabBar: XCUIElement { app.tabBars.firstMatch }
|
|
var cameraTab: XCUIElement { tabBar.buttons[UITestID.TabBar.camera] }
|
|
var collectionTab: XCUIElement { tabBar.buttons[UITestID.TabBar.collection] }
|
|
var todayTab: XCUIElement { tabBar.buttons[UITestID.TabBar.today] }
|
|
var settingsTab: XCUIElement { tabBar.buttons[UITestID.TabBar.settings] }
|
|
|
|
var allTabLabels: [String] {
|
|
[UITestID.TabBar.camera,
|
|
UITestID.TabBar.collection,
|
|
UITestID.TabBar.today,
|
|
UITestID.TabBar.settings]
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
/// Taps a tab button using existence-based wait (not hittable — tab bar buttons
|
|
/// report isHittable == false in iOS 26 simulator despite being tappable).
|
|
private func tapTab(_ tab: XCUIElement) {
|
|
XCTAssertTrue(tab.waitForExistence(timeout: 10),
|
|
"Tab '\(tab.label)' not found within 10s")
|
|
tab.tap()
|
|
}
|
|
|
|
@discardableResult
|
|
func tapCamera() -> CameraScreen {
|
|
tapTab(cameraTab)
|
|
return CameraScreen(app: app)
|
|
}
|
|
|
|
@discardableResult
|
|
func tapCollection() -> CollectionScreen {
|
|
tapTab(collectionTab)
|
|
return CollectionScreen(app: app)
|
|
}
|
|
|
|
@discardableResult
|
|
func tapToday() -> TodayScreen {
|
|
tapTab(todayTab)
|
|
return TodayScreen(app: app)
|
|
}
|
|
|
|
@discardableResult
|
|
func tapSettings() -> SettingsScreen {
|
|
tapTab(settingsTab)
|
|
return SettingsScreen(app: app)
|
|
}
|
|
|
|
// MARK: - Assertions
|
|
|
|
func assertAllTabsExist() {
|
|
XCTAssertTrue(tabBar.waitForExistence(timeout: 10), "Tab bar missing")
|
|
for label in allTabLabels {
|
|
XCTAssertTrue(tabBar.buttons[label].waitForExistence(timeout: 5),
|
|
"Tab '\(label)' missing")
|
|
}
|
|
}
|
|
|
|
func assertSelected(_ label: String) {
|
|
let tab = tabBar.buttons[label]
|
|
XCTAssertTrue(tab.waitForExistence(timeout: 5), "Tab '\(label)' not found")
|
|
XCTAssertTrue(tab.isSelected, "Tab '\(label)' not selected")
|
|
}
|
|
}
|