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>
79 lines
3.1 KiB
Swift
79 lines
3.1 KiB
Swift
//
|
|
// WaitHelpers.swift
|
|
// PlantGuideUITests
|
|
//
|
|
// Centralized wait helpers for UI tests.
|
|
// Replaces sleep() with deterministic, predicate-based waits.
|
|
//
|
|
|
|
import XCTest
|
|
|
|
// MARK: - XCUIElement Wait Helpers
|
|
|
|
extension XCUIElement {
|
|
|
|
/// Waits until the element exists and is hittable.
|
|
/// - Parameter timeout: Maximum seconds to wait (default 5).
|
|
/// - Returns: `true` if the element became hittable within the timeout.
|
|
@discardableResult
|
|
func waitUntilHittable(timeout: TimeInterval = 5) -> Bool {
|
|
let predicate = NSPredicate(format: "exists == true AND isHittable == true")
|
|
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
|
|
return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed
|
|
}
|
|
|
|
/// Waits until the element disappears.
|
|
/// - Parameter timeout: Maximum seconds to wait (default 5).
|
|
/// - Returns: `true` if the element disappeared within the timeout.
|
|
@discardableResult
|
|
func waitUntilGone(timeout: TimeInterval = 5) -> Bool {
|
|
let predicate = NSPredicate(format: "exists == false")
|
|
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
|
|
return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed
|
|
}
|
|
|
|
/// Waits until the element's value equals the expected string.
|
|
/// - Parameters:
|
|
/// - expectedValue: Target value.
|
|
/// - timeout: Maximum seconds to wait (default 5).
|
|
/// - Returns: `true` if the value matched within the timeout.
|
|
@discardableResult
|
|
func waitForValue(_ expectedValue: String, timeout: TimeInterval = 5) -> Bool {
|
|
let predicate = NSPredicate(format: "value == %@", expectedValue)
|
|
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
|
|
return XCTWaiter.wait(for: [expectation], timeout: timeout) == .completed
|
|
}
|
|
|
|
/// Taps the element once it becomes hittable.
|
|
/// - Parameter timeout: Maximum seconds to wait for hittable state.
|
|
func tapWhenReady(timeout: TimeInterval = 5) {
|
|
XCTAssertTrue(waitUntilHittable(timeout: timeout),
|
|
"Element \(debugDescription) not hittable after \(timeout)s")
|
|
tap()
|
|
}
|
|
}
|
|
|
|
// MARK: - XCUIApplication Wait Helpers
|
|
|
|
extension XCUIApplication {
|
|
|
|
/// Waits for the main tab bar to appear, indicating the app launched successfully.
|
|
/// - Parameter timeout: Maximum seconds to wait (default 10).
|
|
@discardableResult
|
|
func waitForLaunch(timeout: TimeInterval = 10) -> Bool {
|
|
tabBars.firstMatch.waitForExistence(timeout: timeout)
|
|
}
|
|
|
|
/// Waits for any element matching the identifier to appear.
|
|
/// - Parameters:
|
|
/// - identifier: The accessibility identifier.
|
|
/// - timeout: Maximum seconds to wait (default 5).
|
|
/// - Returns: The first matching element if found.
|
|
@discardableResult
|
|
func waitForElement(identifier: String, timeout: TimeInterval = 5) -> XCUIElement {
|
|
let element = descendants(matching: .any)[identifier]
|
|
_ = element.waitForExistence(timeout: timeout)
|
|
return element
|
|
}
|
|
}
|