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?
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)
}
//…
)