xcodeactoruitest

Actor problem in Xcode UI tests with addUIInterruptionMonitor in Swift 6


To catch unexpected UI interruptions in a UI test, one can use

func addUIInterruptionMonitor(
    withDescription handlerDescription: String,
    handler: @escaping (XCUIElement) -> Bool
) -> any NSObjectProtocol

This function should return true if the interruption was handled, or false if not.
Whether it is handled may depend on the interrupting element, e.g. an alert.
In this case one can e.g. access the label property of the XCUIElement that is passed to the handler.

The problem:

The XCUIElement is declared as

@MainActor
var label: String { get }

but

open func addUIInterruptionMonitor(withDescription handlerDescription: String, handler: @escaping (XCUIElement) -> Bool) -> any NSObjectProtocol

is not marked as @MainActor.

The compiler thus assumes that it could run on a non-main thread.
If then label is accessed within the handler e.g. by

let alertMonitor = addUIInterruptionMonitor(withDescription: "Alerts") { alert in 
    let alertLabel = alert.label
    return false // for tests
}

the compiler issues the error

Main actor-isolated property 'label' can not be referenced from a nonisolated context

What I have tried:

I tried to call the handler on the MainActor using

let alertMonitor = addUIInterruptionMonitor(withDescription: "Alerts") { @MainActor alert in 
    let alertLabel = alert.label
    return false
}

but this gives the compiler error

Converting function value of type '@MainActor @Sendable (XCUIElement) -> Bool' to '(XCUIElement) -> Bool' loses global actor 'MainActor'  

Then I tried to create the handler as

let handler: (XCUIElement) -> Bool = DispatchQueue.main.sync {
    return { alert in
        let alertLabel = alert.label
        return false
}

This compiles, but when the handler is called, I get a runtime error with DispatchQueue.main.sync:

Thread 1: EXC_BREAKPOINT (code=1, subcode=0x18018d2b4)

My question:

How can I handle the situation right?


Solution

  • I found a working solution. It compiles and executes without error under Swift 6.
    Unfortunately I'm not yet so familiar with Actors that I could explain why.

    class IOSTestCase: XCTestCase {
        var alertMonitor: NSObjectProtocol?
    
        override func setUp() {
            super.setUp()
    
            var handler: (XCUIElement) -> Bool {
                return { alert in
                    let alertLabel = alert.label
                return false // for testing
            }
            
            alertMonitor = addUIInterruptionMonitor(withDescription: "Alerts", handler: handler)
        }
    
    //…
    )