Skip to content

Instantly share code, notes, and snippets.

@Jwood97
Last active March 15, 2025 19:12
Show Gist options
  • Save Jwood97/8c3b9321cb61426ec6a57dcbaed4ec17 to your computer and use it in GitHub Desktop.
Save Jwood97/8c3b9321cb61426ec6a57dcbaed4ec17 to your computer and use it in GitHub Desktop.
extension XCUIElement {
enum Timeouts: TimeInterval {
case veryShort = 2.0
case short = 4.0
case medium = 8.0
case long = 12.0
case veryLong = 15.0
}
struct PredicatePollerDefaults {
static let minPollInterval: TimeInterval = 0.2
static let pollMultiplier: Double = 1.5
static let maxPollInterval: TimeInterval = 2.0
static let maxIterations: Int = 100
}
@discardableResult
class func wait(for condition: @escaping () -> Bool,
timeout: Timeouts = .medium,
failureMessage: String = "Condition not met",
hardAssertion: Bool = true,
description: String) -> Bool {
if condition() { return true }
let start = Date()
let timeoutDate = start.addingTimeInterval(timeout.rawValue)
let expectation = XCTestExpectation(description: description)
var pollInterval = PredicatePollerDefaults.minPollInterval
var iterationCount = 0
while Date() < timeoutDate {
iterationCount += 1
if iterationCount > PredicatePollerDefaults.maxIterations {
if hardAssertion {
XCTFail("Exceeded maximum allowed iterations (\(PredicatePollerDefaults.maxIterations))")
}
return false
}
if condition() {
expectation.fulfill()
return true
}
let remainingTime = timeoutDate.timeIntervalSinceNow
let effectivePollInterval = min(pollInterval, remainingTime)
if effectivePollInterval <= 0 { break }
RunLoop.current.run(until: Date().addingTimeInterval(effectivePollInterval))
// Exponential backoff so CI doesn't get overwhelmed
pollInterval = min(max(PredicatePollerDefaults.minPollInterval, pollInterval * PredicatePollerDefaults.pollMultiplier), PredicatePollerDefaults.maxPollInterval)
}
if hardAssertion { XCTFail(failureMessage) }
return false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment