Wrong cells count for collection view in UI Tests
Asked Answered
S

5

7

I have a test for a collection view that works like this:

func testDeleteItem() {
    app.collectionViews.staticTexts["Item"].tap()
    app.buttons["Delete"].tap()

    XCTAssertEqual(app.collectionViews.cells.count, 2)
    XCTAssertFalse(app.collectionViews.cells.staticTexts["Item"].exists)
}

After the tap, there is a new screen with the delete button. When the button is tapped, the screen dismisses itself and reloads the collection view. Everything goes as expected in the UI, but I get both asserts failing. In the first count it is still 3 and in the second item it still exists.

Sleet answered 12/11, 2015 at 19:36 Comment(6)
I see test in simulator as it goes and it should be ok. Screenshots show also 2 items left. I thought it can fail because it doesn't wait to finish screen dismissing, but expectationForPredicate with waitForExpectationsWithTimeout also fails.Sporangium
Logs don't show anything out of order.Sporangium
This test also fails, when cell is deleted directly on collectionView:didSelectCell:, putting the asserts in old good dispatch_async makes the tests pass. It does not seems to be a proper solution, but points out on threading as well. InterestingEme
I can't make it pass with dispatch_async, how did you managed to do this?Sporangium
I wrapped the asserts with dispatch_async(dispatch_get_main_queue())), anyway dispatch_async is a no go, it simply causes the assert to be not taken into account XCTAssertFalse(true) also passes ;) have you tried the very same scenario but with tableView? If this works it might indicate that there is indeed some bugEme
This view was table view before and I have changed it to collection view. I have tried to change all 'app.tableViews' to 'app.collectionsViews' in tests and this is where I stuck.Sporangium
S
13

I have found the solution, but it's a workaround for wrong API behavior. Collection view is caching cells, that's probably why I have 3 cells, even if I have removed one. Deleted cell is offscreen, so you can test if it is hittable:

XCTAssertFalse(app.cells.staticTexts["Item"].hittable)

To find a count, I have created extension:

extension XCUIElementQuery {
    var countForHittables: UInt {
        return UInt(allElementsBoundByIndex.filter { $0.hittable }.count)
    }
}

and my test looks like this:

func testDeleteItem() {
    app.collectionViews.staticTexts["Item"].tap()
    app.buttons["Delete"].tap()

    XCTAssertEqual(app.collectionViews.cells.countForHittables, 2)
    XCTAssertFalse(app.collectionViews.cells.staticTexts["Item"].hittable)
}
Sleet answered 15/11, 2015 at 19:19 Comment(1)
Just had this problem as well. The need for a workaround is unfortunate, but the workaround works, which is great. Thanks!Visionary
S
7

I also came across this issue, but in my case, .cells query wasn't evaluating correctly. Instead of .cells, using

XCUIApplication().collectionViews.element.childrenMatchingType(.Cell).count

worked for me and returned the correct count.


Update:

I also found that scrolling the view so that all the cells are dequeued before getting the count fixed the issue. It seems the accessibility framework does not find the other cells until they have been dequeued (I guess that makes sense).

XCUIApplication().collectionViews.element.swipeUp()

Snook answered 3/5, 2016 at 19:2 Comment(0)
A
1

I think cells query returns all cells from all the tables currently in view hierarchy. Try to do this app.tables.firstMatch.cells.count, it worked for me.

Argyrol answered 10/3, 2020 at 19:43 Comment(0)
P
0

I ran into this question when I was looking for the same answer, but in Objective-C. For those like me, I adapted @Tomasz's method to count Collection View cells in UI tests:

-(NSInteger)countForHittables:(NSArray<XCUIElement*>*)collectionView{
    __block int hittables = 0;
    [collectionView enumerateObjectsUsingBlock:^(XCUIElement * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.hittable){
            hittables++;
        }
    }];
    return hittables;
}

To call it: [self countForHittables:app.collectionViews.cells.allElementsBoundByIndex];.

Paternalism answered 16/2, 2017 at 14:37 Comment(0)
K
0

I had the same issue. Even if the collection hasn't been populated because it was waiting for the response of an API, cells.count >= 1 was always true.

What I did, based on Tomasz Bąk's answer I created an extension to wait for the collection to be populated:

extension XCTestCase {
    func waitForCollectionToBePopulated(_ element: XCUIElement, timeout: TimeInterval) {
        let query = element.cells
        let p = NSPredicate(format: "countForHittables >= 1")
        let e = expectation(for: p, evaluatedWith: query, handler: nil)
        wait(for: [e], timeout: timeout)
    }
}

And on the caller site will look:

waitForCollectionToBePopulated(collection, timeout: {timeOut})
Knelt answered 9/2, 2018 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.