Set cell accessibility identifiers to section+cell keys#109
Conversation
| let row = indexPath.row | ||
| let cellConfig = sectionData[row] | ||
| let cell = cellConfig.dequeueCell(from: tableView, at: indexPath) | ||
| cell.accessibilityIdentifier = sectionData.sectionKeyPathForRow(row) |
There was a problem hiding this comment.
I believe this is the job of the states. Cells can be very complex with a lot of views, and we will need to identify each of them, like we are currently doing in some of our states ButtonState for example.
There was a problem hiding this comment.
Meaning that the HostCell's state should hold the identifier? And for the views inside, couldn't we just set their identifiers to override the cell as the superview like Caio said?
There was a problem hiding this comment.
Yes, that is what I mean. I have no strong opinions on it, but I dont think the key makes a great identifier. Not every view needs an unique identifier, I would argue all orders cell for example should have the same identifier. But I dont know the scope of this change
There was a problem hiding this comment.
That information should still belong in ButtonState along with setting accessibilityLabels since we might want to access that button specifically in a UI test even if it's contained inside a HostCell's view, but having cell identifiers in addition would be super helpful. With this, we can add support for cell selection in UI tests for free and differentiate from other accessible items that are repeated in similar cells more easily.
I have no strong opinions on it, but I dont think the key makes a great identifier. Not every view needs an unique identifier, I would argue all orders cell for example should have the same identifier.
Having unique identifiers for things like individual orders has been pretty useful so far. If you want to access the 4th order, you can use the key you already wrote instead of getting the cell at index 3. I can write up an example...
But I dont know the scope of this change
accessibilityIdentifier is only used for UI testing, so this is an additive change for those doing UITesting. If you want your state to set your hosted view's accessibility information, you can still do that the same way as previously, but the cells that contain it will have identifiers instead of being anonymous.
There was a problem hiding this comment.
Ok bear with me...
Let's say we have a table view with a bunch sections of products on a screen with a product name and an info button and we want to tap on the info button of the 🍌 product. In our state, we could set the button accessibility identifier to be something like "productView.button.info-\(product.name)" so it can be differentiated from the others. Cool we can find and tap the button with something like:
app.tables["tableView.productList"].buttons["productView.button.info-🍌"].tap()What if we want to tap on the cell in the test too? Or what if this product name is listed twice in the list but in different sections? UIView subclasses aren't accessibility containers by default so our hosted product name view won't be hittable/tappable in UI tests unless we make it an accessibility container or element. If we do that, we have to implement Voice over support too. After that, our state can create and set the custom view's accessibility identifier and we could potentially access it like:
app.tables["tableView.productList"].otherElements["productView-🍌"].buttons["productView.button.info"].tap()Though, if we set the cell accessibility identifier, we can use UITableViewCell's Voice over support by default instead of providing our own (can still provide our own if needed). By using the section + cell keys provided by the cell configuration, we also get guaranteed unique identifiers for free. We could then access the button similarly:
app.tables["tableView.productList"].cells["fruit-🍌"].buttons["productView.button.info"].tap()And also tap the cell like:
app.tables["tableView.productList"].cells["fruit-🍌"].tap()Now you have access to a specific cell and any of its hosted view's subviews that have accessibility identifiers.
There was a problem hiding this comment.
Having unique identifiers for things like individual orders has been pretty useful so far. If you want to access the 4th order,
That's the part I don't agree with.
// pseudo code warning
let orders = XCTFindElement("orderCell").limit(5)
// ... then do something with the actual content
// I think its better to have access to "generic UI elements"I have always been talking about testing here, not accessibility information. I don't think the tests should be written with "specifics" in mind, we should be able to easily query for all orderCells in the screen instead of querying for section1-orderCell25.
There was a problem hiding this comment.
What's the downside of doing this though? You can still set accessibility state on the hosted view. Why not both?
There was a problem hiding this comment.
As others have said this is purely additive - you can still access elements in a set by index, that's not changing.
For list views, maybe we shouldn't write UI tests that target rows by ID. But for detail views, the keypath usually carries semantic meaning and is a useful way of identifying particular pieces of the UI.
This change gives us the flexibility to use both approaches.
ArielSD
left a comment
There was a problem hiding this comment.
Code looks good to me - Curious to understand Raul's point more.
| let row = indexPath.row | ||
| let cellConfig = sectionData[row] | ||
| let cell = cellConfig.dequeueCell(from: tableView, at: indexPath) | ||
| cell.accessibilityIdentifier = sectionData.sectionKeyPathForRow(row) |
There was a problem hiding this comment.
Meaning that the HostCell's state should hold the identifier? And for the views inside, couldn't we just set their identifiers to override the cell as the superview like Caio said?
|
|
||
| let cell = tableData.tableView?.visibleCells.first | ||
| XCTAssertNotNil(cell) | ||
| XCTAssertEqual(cell!.accessibilityIdentifier, section.sectionKeyPathForRow(0)) |
There was a problem hiding this comment.
Can you type out the expected string instead? If the sectionKeyPathForRow changes, this will not break
There was a problem hiding this comment.
cell?.accessibilityIndentifier. XCTAssertEqual handles optionals just fine, and this prevents the test from crashing if XCTAssertNotNil(cell) fails.
Agree with Raul though that it should use a hardcoded comparison value. That documents the format of the expected output, on which all of our unit tests will depend, so if it changes we want to know about it.
| let row = indexPath.row | ||
| let cellConfig = sectionData[row] | ||
| let cell = cellConfig.dequeueCell(from: tableView, at: indexPath) | ||
| cell.accessibilityIdentifier = sectionData.sectionKeyPathForRow(row) |
There was a problem hiding this comment.
Yes, that is what I mean. I have no strong opinions on it, but I dont think the key makes a great identifier. Not every view needs an unique identifier, I would argue all orders cell for example should have the same identifier. But I dont know the scope of this change
alinebee
left a comment
There was a problem hiding this comment.
Minor test change, otherwise I think this is a good addition 👍
|
|
||
| let cell = tableData.tableView?.visibleCells.first | ||
| XCTAssertNotNil(cell) | ||
| XCTAssertEqual(cell!.accessibilityIdentifier, section.sectionKeyPathForRow(0)) |
There was a problem hiding this comment.
cell?.accessibilityIndentifier. XCTAssertEqual handles optionals just fine, and this prevents the test from crashing if XCTAssertNotNil(cell) fails.
Agree with Raul though that it should use a hardcoded comparison value. That documents the format of the expected output, on which all of our unit tests will depend, so if it changes we want to know about it.
|
Updated test to use hard coded accessibility identifier. |
What
Adds an accessibility identifier to each cell so we can access them more easily for UI Testing. Since we already add unique keys to each section configuration and cell configuration, we should be able to reuse them as the accessibility identifiers. This way we don't have to input any extra identifiers.
If you have two components inside a cell you want to access separately you can set those components as accessibility elements to override the cell's accessibility identifier.
Since accessibility identifiers are only used for UITesting, this shouldn't affect any integrations with Voice Over.
Example
Similar to the test, if we had a section:
The
accessibilityIdentifierof the cell inside that section will be"sectionKeycellKey"