How we're doing it at my current company is we create an XCUIElement expression expectation (to create a versatile wait method). We do it the following way to make sure it's maintainable (lots of expectation variety, and don't want to create lots of methods/specific predicates to do so.
Swift 5
Base method
The expression is used to form a dynamic predicate value. We can create XCTNSPredicateExpectation
's from predicates, which we then pass into XCTWaiter
to explicitly wait. If the result was anything other than completed
, then we fail with an optional message.
@discardableResult
func wait(
until expression: @escaping (XCUIElement) -> Bool,
timeout: TimeInterval = 15,
message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) -> Self {
if expression(self) {
return self
}
let predicate = NSPredicate { _, _ in
expression(self)
}
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: nil)
let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
if result != .completed {
XCTFail(
message().isEmpty ? "expectation not matched after waiting" : message(),
file: file,
line: line
)
}
return self
}
Usage
app.buttons["my_button"].wait(until: { $0.exists })
app.buttons["my_button"].wait(until: { $0.isHittable })
Keypaths
Then we wrap that in a method where a keyPath and match
value form the expression.
@discardableResult
func wait<Value: Equatable>(
until keyPath: KeyPath<XCUIElement, Value>,
matches match: Value,
timeout: TimeInterval = 15,
message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) -> Self {
wait(
until: { $0[keyPath: keyPath] == match },
timeout: timeout,
message: message,
file: file,
line: line
)
}
Usage
app.buttons["my_button"].wait(until: \.exists, matches: true)
app.buttons["my_button"].wait(until: \.isHittable, matches: false)
Then you can wrap that method, where the match
value is always true
for a use case I found most common.
Usage
app.buttons["my_button"].wait(until: \.exists)
app.buttons["my_button"].wait(until: \.isHittable)
I wrote a post about it, and get the full extension file there too: https://sourcediving.com/clean-waiting-in-xcuitest-43bab495230f
NSThread.sleepForTimeInterval(1)
should work – Anticlinedispatch_after
,dispatch_queue
stuff) – Anticline