Dan Loewenherz
Dan Loewenherz

Reputation: 11253

UI Testing Failure when displaying UIAlertController with no buttons

We're using a UIAlertController as a loading indicator while a network request occurs. There are no actions associated with this UIAlertController as it is closed automatically when the network activity is completed. We display this alert after the user taps the login button for our app.

When we run our tests, they fail at the point right after this with:

UI Testing Failure - Did not receive view did disappear notification within 2.0s  

Per other answers on SO, I've tried to use addUIInterruptionMonitor to handle the alert, but without any success. I think this is because there are no actionable buttons on the UIAlertController. Since there's no action that can be taken on the alert, the interruption monitor just looks like this:

addUIInterruptionMonitor(withDescription: "Loading") { handler in  
    return true  
}

Even with this though, I get the same error. How can I work around this?

EDIT: Relevant UI testing code below:

class UI_Tests: XCTestCase {
    override func setUp() {
        super.setUp()

        continueAfterFailure = true

        XCUIApplication().launch()
    }

    func testLogin() {
        let app = XCUIApplication()
        let tablesQuery = app.tables

        let secureTextField = tablesQuery.cells.containing(.staticText, identifier:"PIN").children(matching: .secureTextField).element
        secureTextField.tap()
        secureTextField.typeText("1234")

        app.buttons["Login"].tap()

        addUIInterruptionMonitor(withDescription: "Loading") { handler in
            return true
        }

        // Test failure occurs here.

        let searchField = tablesQuery.searchFields.element(boundBy: 0)
        searchField.tap()
        searchField.typeText("hey")
    }
}

Upvotes: 0

Views: 1211

Answers (1)

Dan Loewenherz
Dan Loewenherz

Reputation: 11253

After reaching out to Apple DTS, it turns out that when displaying a UI interruption / UIAlertController that dismisses based on a timeout, that you need to combine the UI interruption monitor with a timeout-based expectation (otherwise, the interruption monitor will return before the alert has dismissed!).

Using the UI testing example in the question, this approach looks like this:

class UI_Tests: XCTestCase {
    override func setUp() {
        super.setUp()

        continueAfterFailure = true

        XCUIApplication().launch()
    }

    func testLogin() {
        let app = XCUIApplication()
        let tablesQuery = app.tables

        let secureTextField = tablesQuery.cells.containing(.staticText, identifier:"PIN").children(matching: .secureTextField).element
        secureTextField.tap()
        secureTextField.typeText("1234")

        app.buttons["Login"].tap()

        addUIInterruptionMonitor(withDescription: "Loading") { alert in
            self.expectation(for: NSPredicate(format: "exists == false"), evaluatedWith: alert, handler: nil);
            self.waitForExpectations(timeout: 10, handler: nil)
            return true
        }

        // Test failure occurs here.

        let searchField = tablesQuery.searchFields.element(boundBy: 0)
        searchField.tap()
        searchField.typeText("hey")
    }
}

This expectation will wait for 10 seconds to be filled. If the alert doesn't dismiss after 10 seconds, the expectation won't be met and the test will fail, but if it does, the test will succeed.

Upvotes: 1

Related Questions