Boiler Bill
Boiler Bill

Reputation: 1940

Find UIRefreshControl in UI Test

I am wanting to test the existence of a UIRefreshControl inside a UI Test. I define my control all init:

itemRefreshControl.accessibilityIdentifier = "MyRefreshIndicator"
    // allow UITest to find the refresh
    if let refreshLabel = itemRefreshControl.subviews.first?.subviews.last as? UILabel {
        refreshLabel.isAccessibilityElement = true
        refreshLabel.accessibilityIdentifier = "MyRefreshLabel"
    }

Then in my test case I have tried:

let refreshCtlQuery = NSPredicate(format: "label CONTAINS[c] 'Refreshing'")
    let refreshControl = app.staticTexts.containing(refreshCtlQuery)
    expectation(for: exists, evaluatedWith: refreshControl, handler: nil)
    start.press(forDuration: 0, thenDragTo: finish)
    print(app.debugDescription)
    waitForExpectations(timeout: 5, handler: nil)

I also tried:

let refreshControl = app.staticTexts["MyRefreshLabel"]

and I tried:

let refreshControl = app.activityIndicators["MyRefreshIndicator"]

In all those cases I can see the test runner perform the drag and I see the refresh control in the UI, but the expectation always fails. It's almost like the test blocks until the refreshing is done and then checks for existence and it's not there. When I print out the view hierarchy, I can't find the UIRefreshControl's label. How best can I test this?

Upvotes: 3

Views: 592

Answers (1)

Geri Borbás
Geri Borbás

Reputation: 16598

Indeed, while UIRefreshControl does its animation, the tests are hang up saying "Wait for <BUNDLE_IDENTIFIER> to idle".

You can swizzle XCUIApplicationProcess.waitForQuiescenceIncludingAnimationsIdle: to an empty method, so you can bypass this behaviour (based on this answer).

extension XCTestCase {

    static var disabledQuiescenceWaiting = false

    /// Swizzle `XCUIApplicationProcess.waitForQuiescenceIncludingAnimationsIdle(:)`
    /// to empty method. Invoke at `setUpWithError()` of your test case.
    func disableQuiescenceWaiting() {
        
        // Only if not disabled yet.
        guard Self.disabledQuiescenceWaiting == false else { return }
        
        // Swizzle.
        if
            let `class`: AnyClass = objc_getClass("XCUIApplicationProcess") as? AnyClass,
            let quiescenceWaitingMethod = class_getInstanceMethod(`class`, Selector(("waitForQuiescenceIncludingAnimationsIdle:"))),
            let emptyMethod = class_getInstanceMethod(type(of: self), #selector(Self.empty))
        {
            method_exchangeImplementations(quiescenceWaitingMethod, emptyMethod)
            Self.disabledQuiescenceWaiting = true
        }
    }

    @objc func empty() {
        return
    }
}

Then call at the setup of your test case.

override func setUpWithError() throws {
    disableQuiescenceWaiting()
}

You can mark the UIRefreshControl directly (I made this extension for convenience).

extension UIRefreshControl {
    
    func testable(as id: String) -> UIRefreshControl {
        self.isAccessibilityElement = true
        self.accessibilityIdentifier = id
        return self
    }
}

// Create the instance like this.
UIRefreshControl().testable(as: "RefreshControl")

So you can nicely assert the existence (get from otherElements).

let refreshControlElement = app.otherElements["RefreshControl"]        
XCTAssertTrue(refreshControlElement.waitForExistence(timeout: 1))

Upvotes: 1

Related Questions