XCUIElement Wait Extension
During the development of the Siwonschool app, I made note of new insights I gained about XCUITest. I intend to revise and share these insights one by one.
Apple has already introduced the waitForExistence
feature.
However, I desired a more elaborate feature set. For instance, the ability to overlook errors, retry with a prerequisite task, or wait for conditions beyond just existence.
Usage
This is new wait extension method for XCUIElement.
predicate: This allows me to define conditions, including existence.
testCase: This parameter is required to create XCTestExpectation internally.
timeout: This parameter should already be familiar to you.
failure: If specified, this function will be called instead of raising an XCFail.
failover: If specified, the test will be retried after calling this function in case of a timeout during testing.
Implementation
So how could I make this?
Predicate
I crafted a default Predicate to verify the existence of the Element.
var predicates = [NSPredicate.init(format: "exists == true")];
if let predicate = predicate{
predicates.append(predicate);
}
NSCompoundPredicate
Furthermore, I generated a compound expectation using And operator.
let andPredicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: predicates);
let expect = testCase.expectation(for: andPredicate, evaluatedWith: self, handler: nil);
Failover
In the event that waiting fails and a failover is provided, I will trigger the designated failover handler and attempt waiting again.
let result = XCTWaiter.wait(for: [expect], timeout: timeout);
if result != .completed{
if let retry = failover{
print("retry wait with failover");
retry(self);
return self.wait(predicate, testCase: testCase, timeout: timeout, failure: failure);
Failure
If waiting fails without a failover handler but a failure handler is specified, I will invoke the failure handler rather than raising an XCFail exception.
}else if let completion = failure{
completion(result);
return nil;
}
Full Source
extension XCUIElement{
@discardableResult
func wait(_ predicate : NSPredicate? = nil, testCase: XCTestCase, timeout: TimeInterval, failure: ((XCTWaiter.Result) -> Void)? = nil, failover: ((XCUIElement) -> Void)? = nil ) -> XCUIElement?{
var predicates = [NSPredicate.init(format: "exists == true")];
if let predicate = predicate{
predicates.append(predicate);
}
let andPredicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: predicates);
let expect = testCase.expectation(for: andPredicate, evaluatedWith: self, handler: nil);
let result = XCTWaiter.wait(for: [expect], timeout: timeout);
if result != .completed{
if let retry = failover{
print("retry wait with failover");
retry(self);
return self.wait(predicate, testCase: testCase, timeout: timeout, failure: failure);
}else if let completion = failure{
completion(result);
return nil;
}else{
XCTFail("can not find \(self). predicate[\(predicate)]");
}
}
return self;
}
}
Using the wait
method, I am able to conduct testing even if the user is not logged in or if a network error occurs. In case of such scenarios, the testing process will automatically retry once the user logs in or upon pressing the "retry" option.