// // WaitHelpers.swift // Tests iOS // // Centralized, explicit wait helpers. No sleep() allowed. // import XCTest extension XCUIElement { /// Wait for the element to exist in the hierarchy. /// - Parameters: /// - timeout: Maximum seconds to wait. /// - message: Custom failure message. /// - Returns: `true` if the element exists within the timeout. @discardableResult func waitForExistence(timeout: TimeInterval = 5, message: String? = nil) -> Bool { let result = waitForExistence(timeout: timeout) if !result, let message = message { XCTFail(message) } return result } /// Wait until the element is hittable (exists and is enabled/visible). /// - Parameter timeout: Maximum seconds to wait. @discardableResult func waitUntilHittable(timeout: TimeInterval = 5) -> Bool { let predicate = NSPredicate(format: "isHittable == true") let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self) let result = XCTWaiter.wait(for: [expectation], timeout: timeout) return result == .completed } /// Tap the element after waiting for it to become hittable. /// - Parameter timeout: Maximum seconds to wait before tapping. func tapWhenReady(timeout: TimeInterval = 5, file: StaticString = #file, line: UInt = #line) { guard waitUntilHittable(timeout: timeout) else { XCTFail("Element \(identifier) not hittable after \(timeout)s", file: file, line: line) return } tap() } /// Wait for the element to disappear from the hierarchy. /// - Parameter timeout: Maximum seconds to wait. @discardableResult func waitForDisappearance(timeout: TimeInterval = 5) -> Bool { let predicate = NSPredicate(format: "exists == false") let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self) let result = XCTWaiter.wait(for: [expectation], timeout: timeout) return result == .completed } } extension XCUIApplication { /// Wait for any element matching the identifier to exist. func waitForElement(identifier: String, timeout: TimeInterval = 5) -> XCUIElement { let element = descendants(matching: .any).matching(identifier: identifier).firstMatch _ = element.waitForExistence(timeout: timeout) return element } }