Finding Table Cells by Header Text

Surely you have seen (screenshots of) huge Excel spreadsheets before. If not, just imagine a wall of numbers. Now imagine such a table in the application you need to test. Verifying a table cell by looking for the correct number? Not so easy.

What would be better than looking for the literal cell value? Well, we could use the cell's coordinates. However, coordinates are fragile in cases where the table columns can be reordered. How about accessing the table like we humans do: by first looking at the table headers. For the sake of this example, we will be using Squish's itemviews example application. There is a QTableView in it that looks like this:

Admittedly, that is not quite the wall of numbers we anticipated. It demonstrates the workflow well though. Next, we need to choose the table header views we will use to find the cell. Pick the QTableView by clicking into the empty space below the table rows. After expanding the picked object in the Application Objects view of the Squish IDE, you should see this:

Right-click both of the QHeaderViews and select "Add to Object Map". Now open the object map script file from the Test Suite Resources section. It will be named names.py, names.js or otherwise depending on the scripting language you chose. For this article I will be using Python.

Among others, you should see these two entries:
[code language="python" title="Object names as recorded"]o_QHeaderView = {"container": item_Views_QTableView, "orientation": 2, "type": "QHeaderView", "unnamed": 1, "visible": 1}
o_QHeaderView_2 = {"container": item_Views_QTableView, "orientation": 1, "type": "QHeaderView", "unnamed": 1, "visible": 1}[/code]
The names o_QHeaderView and o_QHeaderView_2 are not very helpful yet. We will be using them in a function shortly, so let's give them better names: row_QHeaderView and column_QHeaderView respectively. (Also check out our tip on good object naming!)

Now it is time to wield the power of the Scripted Object Map. Add these two short functions to names.py:
[code language="python" title="Name generator functions"]import squish

def headerViewItemName(headerViewName, text):
return {"container": headerViewName, "text": text, "type": "HeaderViewItem", "visible": True}

def cellFromHeaders(columnText, rowText):
rowHeaderViewItem = squish.waitForObject(headerViewItemName(row_QHeaderView, rowText))
columnHeaderViewItem = squish.waitForObject(headerViewItemName(column_QHeaderView, columnText))
return {"column": columnHeaderViewItem.section, "container": item_Views_QTableView, "row": rowHeaderViewItem.section, "type": "QModelIndex"}[/code]
The first one is just a helper. It contains the real name template for the HeaderViewItem so that we don't need to duplicate it. The second one is what we are after. Remember, we wanted to identify table cells not by content or coordinates but by the table headers. The cellFromHeaders() function takes the row and column header texts and returns the object name of the corresponding cell - or rather QModelIndex. How does it do that? First, it fetches the row and column header view items that have the texts given by the caller. These items can tell us which row and column ("section") they are in. We then use the section indices to form the object name of the table cell.

If you want to see the result in action, try this test script:
[code language="python" title="Test script that uses the cellFromHeaders() function"]import names

def main():
startApplication("itemviews")
cell = waitForObject(names.cellFromHeaders("Three", "1"))
test.verify(cell.text, "Nitrogen")[/code]
That's it! Now you can address your table cells by row and column and have more stable tests!

What about "waitForObjectItem()"?

Because you're working with tables in Squish, you might also have stumbled upon the waitForObjectItem() function. Our helper can be easily adapted to work with that one, too:
[code language="python" title="cellFromHeaders() variant that works with waitForObjectItem()"]def cellCoordsFromHeaders(columnText, rowText):
rowHeaderViewItem = squish.waitForObject(headerViewItemName(row_QHeaderView, rowText))
columnHeaderViewItem = squish.waitForObject(headerViewItemName(column_QHeaderView, columnText))
return "{}/{}".format(columnHeaderViewItem.section, rowHeaderViewItem.section)[/code]
Calling this version works much the same way as using cellFromHeaders():
[code language="python"]cell = waitForObjectItem(names.item_Views_QTableView, names.cellCoordsFromHeaders("Three", "1"))
test.verify(cell.text, "Nitrogen")[/code]
Because waitForObjectItem() is not Qt-specific, the approach shown above also works with other GUI toolkits. The only requirement is a way to map row/column captions to the respective indices.

Comments

    The Qt Company acquired froglogic GmbH in order to bring the functionality of their market-leading automated testing suite of tools to our comprehensive quality assurance offering.