Find Table Elements in XCUITest

Table

One way to set up a simple iOS app project in Xcode is by using a template when creating a new project. I often have used the “Single View App” template as that is the most bare bones project. I have begun to explore the other project templates as they offer more skeletal code to build from. One of them, the “Master-Detail App” template, gives me enough to work with so that I can craft UI Tests and develop concise and realistic examples for techniques I want to work with.

Original Master-Detail App UI after tapping + button 3 times

This post introduces you to a way to add identifiers in code for rows in a table. It also shows a couple of the ways you can query for a particular row. I modify the template code so that when tapping the ‘+’ button the background color is randomly set to red or green. I set a value for the accessibility identifier corresponding to its background color. The code for the UI tests is broken out into a screen class and test class per a screen object model pattern

Modified Master-Detail App UI after tapping + button 3 times

The source code for this post is available at https://github.com/JoeFerrara/xcuitest-blog. Right now, all of the example code is on the master branch. In the future, I plan to use this repo so that variations of the main app are stored in different branches. Longer term goal is to create several small apps that are useful for testing.

First, we can look at how to add an accessibility identifier from within your app’s code. If you do not usually modify the app under test, then this bit shows you how it is done. I encourage you, if you are interested in improving your programming skills, to learn how to add identifiers yourself.

For the UITable element, it has functions to add cells to the table. From the template code, an identifier is not associated with each new row. In order to write tests which can be related to individual rows, it would be necessary to create identifiers. From a UI Test, we cannot easily access the background color of a cell though this is not impossible.

if arc4random() % 2 == 1 {
  cell.backgroundColor = .red
  cell.accessibilityIdentifier = "Cell_Timestamp_Red"
  } else {
    cell.backgroundColor = .green
     cell.accessibilityIdentifier = "Cell_Timestamp_Green"
  }
}

Code snippet above is found in MasterViewController.swift in overload of tableView() which returns a UITableViewCell.

Discussion

There are at least a couple of ways to locate a table row based on its color. The accessibility identifier has already been set for each row so this simplifies things. The two methods I describe below are first to explicitly use the defined identifiers, “Cell_Timestamp_Red” and “Cell_Timestamp_Green”. The second method is to use an NSPredicate instance to find the row with desired background color.

These queries are defined in the screen class for the Master screen. That way they can be reused in other test classes which interact with the Master screen. A second reason is so that these functions are abstracted away from the tests. In this example code, the method to do the query is explicitly spelled out in the function name for learning. In production code, there is no need to inform the function’s caller how the element is located.

Query by Identifier

let redCount = masterScreen.table.cells.matching(identifier: "Cell_Timestamp_Red").allElementsBoundByIndex.count

Above code could be refactored into a screen class function, func getRowCountWhereColor()

func getRowCountWhereColor(is identifier: String) -> Int {
  masterScreen.table.cells.matching(identifier: “Cell_Timestamp_Red").allElementsBoundByIndex.count
}

Query by Predicate

func getCountOfTableRowsWhereColor(is uiColor: String) -> Int {
  let predicate = NSPredicate(format: "identifier LIKE \"*\(uiColor)\"")
        
  return table.cells.matching(predicate).allElementsBoundByIndex.count
}

Tests

I show the test source code here along with a helper function contained in the test class. Not shown are the screen class functions. See the source code for full details.

testAddOddNumberOfRowsUsingIdentifier()

func testAddOddNumberOfRowsUsingIdentifier() {
        if addRowsToTable(numRows: 3) {
            let redCount = masterScreen.table.cells.matching(identifier: "Cell_Timestamp_Red").allElementsBoundByIndex.count
            let greenCount = masterScreen.table.cells.matching(identifier: "Cell_Timestamp_Green").allElementsBoundByIndex.count
            
            XCTAssertNotEqual(redCount, greenCount, "The number of red rows and blue rows are equal")
        }
    }

testAddOddNumberOfRowsUsingPredicate()

func testAddOddNumberOfRowsUsingPredicate() {
  if addRowsToTable(numRows: 3) {
    let redCount = masterScreen.getCountOfTableRowsWhereColor(is: "Red")
    let greenCount = masterScreen.getCountOfTableRowsWhereColor(is: "Green")
            
    XCTAssertNotEqual(redCount, greenCount, "The number of red rows and blue rows are equal")
  }
}

Helper – addRowsToTable()

func addRowsToTable(numRows: Int) -> Bool {
  for _ in 1...numRows {
    masterScreen.navBarAdd.tap()
  }
  XCTAssertEqual(numRows, masterScreen.getCountOfTableRows(), "Error: \(numRows) rows not added to table")
        
  return true
}

In Summary

There are other ways to locate table cells and rows, but these are a couple of ways which have proven useful in my own UI Tests development. If you are not familiar with NSPredicate then it is worth learning. The SQL like syntax allows for great flexibility in querying for elements.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.