Custom UI Interruption Monitor

Lee young-jun
2 min readSep 26, 2023

--

In the last article, I discussed handling system alerts.

However, there are various interruptions, such as login screens or popups, that occur during testing. To streamline the process, I implemented code to manage these interruptions within a single “wait” method.

setupInterruptions

I introduced a new method to set up interruptions before testing.

XCUIInterruption.swift

struct XCUIInterruption{
var element : XCUIElement;
var predicate : NSPredicate;
var handler : (XCUIElement) -> Void;
}

BaseTestCase.swift

var interruptions : [XCUIInterruption] = [];
...
open override func setUp() {
...
self.setupInterruptions();
}
open func setupInterruptions(){
self.addInterruption(element: self.application.buttons["close popup"]) { (button) in
button.tap();
}
...
}

addInterruption

Additionally, I created a method for adding new interruptions, which takes the target element, a condition predicate, and a handler as parameters.

func addInterruption(element: XCUIElement, predicate: NSPredicate = NSPredicate.init(format: "exists == true"), handler: @escaping (XCUIElement) -> Void){
self.interruptions.append(.init(element: element, predicate: predicate, handler: handler));
}

handleInterruptions

The final method is responsible for managing registered interruptions.

func handleInterruptions(withPredicate predicate: NSPredicate? = nil, forElement element: XCUIElement? = nil, timeout: TimeInterval){
let startTime = Date().timeIntervalSince1970;

while(true){
guard !self.interruptions.isEmpty else{
//print("[\(#function)]] there is no interruptions");
break;
}

if let predicate = predicate, let element = element, element.exists, predicate.evaluate(with: element){
"element has been evaluated without interruptions".trace();
return;
}

for interrupt in self.interruptions{
guard interrupt.element != element else{
continue;
}

"evaluate. element[\(interrupt.element)]".trace();
if(interrupt.predicate.evaluate(with: interrupt.element)){
"handle interruption. element[\(interrupt.element)] predicate[\(predicate?.description ?? "")]".trace();
interrupt.handler(interrupt.element);
return;
}
}

let nowTime = Date().timeIntervalSince1970;
guard nowTime - startTime < timeout else{
"expired. start[\(startTime)] now[\(nowTime)] timeout[\(timeout)]".error();
return;
}

sleep(1);
}
}

It checks for the presence of the target element and evaluates whether it meets the specified condition using the provided predicate.

If the target element is not found, it identifies interruptions and triggers the associated handler. The loop continues until the remaining time is calculated, and if all the allotted time has been consumed, it exits the loop.

wait

The final step involves integrating the new method, handleInterruptions, into the wait extension method, which I discussed in the previous article.

extension XCUIElement{
@discardableResult
func wait(...
...
let expect = testCase.expectation(for: andPredicate, evaluatedWith: self, handler: nil);
if let baseTestCase = testCase as? BaseTestCase{
baseTestCase.handleInterruptions(withPredicate: andPredicate, forElement: self, timeout: timeout);
}
...
let result = XCTWaiter.wait(for: [expect], timeout: timeout);

As a result, the wait extension method will now manage interruptions before waiting for an element based on the specified predicate.

References

--

--

No responses yet