XCUIElement Wait Extension

Lee young-jun
3 min readAug 27, 2023

--

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.

References

--

--

No responses yet