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.

rak appdev
  • 717
  • 8
  • 21
Tomasz Bąk
  • 6,124
  • 3
  • 34
  • 48
  • 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. – Tomasz Bąk Nov 12 '15 at 20:16
  • Logs don't show anything out of order. – Tomasz Bąk Nov 12 '15 at 21:26
  • 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. Interesting – Michał Zygar Nov 12 '15 at 23:54
  • I can't make it pass with `dispatch_async`, how did you managed to do this? – Tomasz Bąk Nov 13 '15 at 09:08
  • 1
    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 bug – Michał Zygar Nov 13 '15 at 09:38
  • 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. – Tomasz Bąk Nov 13 '15 at 09:43

5 Answers5

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)
}
Tomasz Bąk
  • 6,124
  • 3
  • 34
  • 48
  • Just had this problem as well. The need for a workaround is unfortunate, but the workaround works, which is great. Thanks! – greymouser Dec 30 '15 at 15:11
6

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()

Chase Holland
  • 2,178
  • 19
  • 23
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.

Arpit
  • 111
  • 6
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];.

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})
rgkobashi
  • 2,551
  • 19
  • 25