Xcode UITesting: label disappears from hierarchy and won't come back - ios

This is using Xcode 7.2.1 and Swift 2.0.
I have a table cell containing a UILabel which is used to display an error message. So at initial load it is blanked using code like this:
cell.errorLabel.alpha = 0.0
cell.errorLabel.text = nil
Then later on when I've detected an error I want to display I do this:
cell.label.text = "to"
cell.errorLabel.alpha = 1.0
cell.errorLabel.text = "Error !!!!"
This works fine when running the app. However when running in a UI Test, I try to test that the error is being displayed like this:
let toCell = XCUIApplication().tables.... // Finds the cell with label 'to'
XCTAssertTrue(toCell.staticTexts["Error !!!!"].exists)
And it fails. I've verified I'm getting the right cell by checking the other ('to') label is present. Which it is. But the UI testing will not see the error label. I've tested this by adding a break point and using the debugger like this:
(lldb) p toCell.staticTexts.count
t = 427.03s Get number of matches for: Descendants matching type StaticText
t = 427.29s Snapshot accessibility hierarchy for enterprise.com.me
t = 427.31s Find: Descendants matching type Table
(UInt) $R2 = 1
t = 427.31s Find: Descendants matching type Cell
t = 427.32s Find: Elements containing elements matching type StaticText with identifier 'to'
t = 427.32s Find: Descendants matching type StaticText
The (UInt) $R2 = 1 indicating that there is one static text present. However looking at the simulator I can clearly see two UILabels.
I've tried a number of things individually to isolate the issue - Using just alpha or setting the text to nil, or using UIView's hidden property. Using any of these options to initially hide the label renders it invisible to UI tests when later made visible, no matter what I try.
I'm quite confused by this and I suspect it's a bug. Anyone have any ideas how to get UI Tests to see the UILabel once I make it visible?
P.S. I've also tried using a wait loop to wait for the label to appear (using expectationForPredicate(...), but the UILabel has not shown up.

Problem turned out to be that I had not set the accessibilityElements property on the cells. Therefore accessibility was having issues trying to figure out what was in each one.
So if you are building custom cells for a table view, ensure you set the accessibilityElements property so that testing can find the contents of the cells.

Related

How to access the `detailTextLabel` in a `tableViewCell` at UITests?

I wanna check whether there is a tableViewCell.detailTextLabel with a given string in my UITest. The problem is when I search for app.tables.cells.children(matching: .staticText) it will only look for labels that are tableViewCell.textLabel.
Any ideas on how to query for the detailTextLabel?
You can try a more generic query to determine if the text exists at all. If you are trying to assert that a cell contains "123 Main St.", for example, you can use the following:
let app = XCUIApplication()
XCTAssert(app.staticTexts["123 Main St."]).exists)
Granted, if that text appears in the textLabel the test will still pass.
It sounds like your detail text label isn't accessible.
To see everything that's available to your tests in the view hierarchy, print the debug description of the following query, which may help you to understand whether the element is appearing, what its type is, and what its identifier or label is:
let app = XCUIApplication()
print(app.descendants(matching: .any).debugDescription)
Make sure that the containing view (the cell?) is not accessible, and that the detail text label is accessible, and give it an identifier:
let cell = UITableViewCell!
cell.isAccessibilityElement = false
cell.detailTextLabel.isAccessibilityElement = true
cell.detailTextLabel.accessibilityIdentifier = "myDetailTextLabel"

How to find staticText properly with XCTest

I am implementing UITests for my iOS app.
So far, I've been able to do some simple testing, but I've come to a tableView where there are two sections. Each section has a sectionHeaderView containing static text, eg. "SECTION 1" and "SECTION 2", in normal sectionHeader style.
When performing app.tables.staticTexts["SECTION 1"].exists it returns true. This is the first section, visible at the very top when the view loads.
When performing the same, but for "SECTION 2", it returns false. The sectionHeaderView for this section is outside the view at this point, so I thought this was the problem, but it turns out it's not..
I tried app.swipeUp(), which successfully brings the second section into the screen. After the swipeUp i sleep for a few seconds for the view to settle, and I perform the same check, but it simply cannot find the second sectionView.
After scrolling down, I have tried to print out app.tables.staticTexts.debugDescription to see what it can find, and it only shows the first section as well as a tableFooterView I have at the very bottom of the tableView.
At the time I perform app.tables.staticTexts["SECTION 2"].exists I can literally see the "SECTION 2" text on the simulator. Yet it does not exist to the test.
Why is my second sectionHeaderView completely invisible to XCTest? Could it be that I have disabled some sort of accessibility-variable on this view in particular? I can't find anything..
Edit, output:
t = 32.25s Find: Descendants matching type Table
t = 32.26s Find: Descendants matching type StaticText
t = 32.26s Find: Elements matching predicate '"SECTION 1" IN identifiers'
Found SECTION 1. Will scroll down to find Section 2.
t = 32.26s Swipe up Target Application 0x6080000bbf60
t = 32.26s Wait for app to idle
t = 32.30s Find the Target Application 0x6080000bbf60
t = 32.30s Snapshot accessibility hierarchy for my.bundle.identifier
t = 33.09s Wait for app to idle
t = 33.14s Synthesize event
t = 33.42s Wait for app to idle
Slept for 3 seconds. Have scrolled down. SECTION 2 in view now.
t = 38.86s Snapshot accessibility hierarchy for my.bundle.identifier
t = 39.64s Find: Descendants matching type Table
t = 39.65s Find: Descendants matching type StaticText
t = 39.65s Find: Elements matching predicate '"SECTION 2" IN identifiers'
t = 39.66s Assertion Failure: MyUITests.swift:347: XCTAssertTrue failed - SECTION 2 does not exist
t = 39.66s Tear Down
Place a breakpoint in your code at:
app.tables.staticTexts["SECTION 2"].exists
When you hit the breakpoint type this into the debug panel and hit enter:
po print(XCUIApplication().debugDescription)
This will list out everything that is available to XCUITest. Look for your Section 2 text in there. Often times when this happens to me, I spelled it wrong or the text in the app has an extra space somewhere. When using .staticText it has to match EXACTLY.
I ran into this problem for table footers. It seems like they are treated as "other" objects, not staticTexts so the following code should work:
XCTAssert(app.otherElements["SECTION 2"].exists)
Thanks to h.w.powers for the debugging tip:
po print(XCUIApplication().debugDescription)
A couple of questions, since I can't comment yet: (I've been doing a LOT of UI testing recently, so I'm learning a little)
What happens when you try to assert app.tables.staticTexts["SECTION 2"].exists after swiping up, and disregarding the "SECTION 1" label?
Is sectionHeaderView a custom subclass?
I've found it's helpful to assign specific views an accessibilityIdentifier and then access them via the XCUIElement proxy with that identifier.
Also, something I've found recently is that unless you're using UIAccessibilityContainer, referring to accessibility traits of superviews can negate the accessibility traits of its subviews.
Just put the debugger in the test function, run the test
and in debugger screen just write po app it will show the view hierarchy of your app containing images , static text etc.

XCTestCase - iOS UI Tests - dealing with UITableViews with many cells

I am experimenting with the (Xcode 7) UI XCTestCase test cases and I just stumbled onto an issue with one UIView, in which I have a UITableView with many cells(4000+).
When the app is running normally, only the visible cells are rendered and there is no performance issue at all.
However, if I run the app within the context of recording a XCTestCase and I navigate to this screen, the simulator freezes, apparently because each single cell is rendered as if it were visible.
If I try to script the navigation manually and I run the XCTestCase, the test case fails right after navigating to this screen, exiting with a "UI Testing Failure - Failed to get refreshed snapshot", apparently again because all cells are being rendered and this does not finish in time.
I think this has to do with the fact that the testing framework builds an entire metamodel of the screen under display, adding each of the 4000+ cells into the view tree hierarchy.
I tried adding an expectation, hoping this would give the testing container enough time to finish rendering all cells, but this does not work.
Is there a workaround for this? Is it somehow possible to skip building part of the UI tree hierarchy or something?
My goal is being able to write UI tests for this screen.
You might be able to avoid having the entire table render, if you can use firstMatch instead of element, and also avoid count.
I had a test that checks for expected labels in the first two cells of a table. At first, I was using app.table.cells.element(boundBy: 0) and app.table.cells.element(boundBy: 1) to find the first and second cells. This was resulting in the whole table being rendered before I could access the cells.
I adapted my test to be slightly less precise, but still good enough for me (given the huge amount of time it would take otherwise). Instead, I use matching with predicates on the expected label values, with firstMatch, to find the first cells matching the criteria I want. This way the traversal stops as soon as it finds them (and since they're at the top of the table, it's quick).
Here's the code before and after.
Before (slow, yet more precise):
private func checkRhymes(query: String, expectedFirstRhyme: String, expectedSecondRhyme: String) {
let table = app.tables.element
let cell0 = table.cells.element(boundBy: 0)
let cell1 = table.cells.element(boundBy: 1)
let actualRhyme0 = cell0.staticTexts.matching(identifier: "RhymerCellWordLabel").firstMatch.label
let actualRhyme1 = cell1.staticTexts.matching(identifier: "RhymerCellWordLabel").firstMatch.label
XCTAssertEqual(expectedFirstRhyme, actualRhyme0, "Expected first rhyme for \(query) to be \(expectedFirstRhyme) but found \(actualRhyme0)")
XCTAssertEqual(expectedSecondRhyme, actualRhyme1, "Expected first rhyme for \(query) to be \(expectedSecondRhyme) but found \(actualRhyme1)")
}
Faster, but less precise (but good enough):
private func checkRhymes(query: String, expectedFirstRhyme: String, expectedSecondRhyme: String) {
let table = app.tables.firstMatch
let label0 = table.cells.staticTexts.matching(NSPredicate(format: "label = %#", expectedFirstRhyme)).firstMatch
let label1 = table.cells.staticTexts.matching(NSPredicate(format: "label = %#", expectedSecondRhyme)).firstMatch
// We query for the first cells that we find with the expected rhymes,
// instead of directly accessing the 1st and 2nd cells in the table,
// for performance issues.
// So we can't add assertions for the "first" and "second" rhymes.
// But we can at least add assertions that both rhymes are visible,
// and the first one is above the second one.
XCTAssertTrue(label0.frame.minY < label1.frame.minY)
XCTAssertTrue(label0.isHittable)
XCTAssertTrue(label1.isHittable)
}
Reference:
https://developer.apple.com/documentation/xctest/xcuielementquery/1500515-element
Use the element property to access a query’s result when you expect a
single matching element for the query, but want to check for multiple
ambiguous matches before accessing the result. The element property
traverses your app’s accessibility tree to check for multiple matching
elements before returning, and fails the current test if there is not
a single matching element.
In cases where you know categorically that there will be a single
matching element, use the XCUIElementTypeQueryProvider firstMatch
property instead. firstMatch stops traversing your app’s accessibility
hierarchy as soon as it finds a matching element, speeding up element
query resolution.
I had the same issue, and I agree it is frustrating having to wait for the entire table to load, but that is what I had to do using the following workaround.
This may not be what you are looking for but it may help others:
Basically I am counting the cells in the table 2 times consecutively if they are not equal that means the table is still loading. Put it in a loop it and do that until both counts return the same number which would mean the table is finished loading. I then put in a stop of 30 seconds so that if this takes longer than 30 seconds, the test will fail (this was enough time in my case). If your table will take longer than that you could increase the number to 180 for 3 mins etc...
let startTime = NSDate()
var duration : TimeInterval
var cellCount1 : UInt = app.tables.cells.count
var cellCount2 : UInt = app.tables.cells.count
while (cellCount1 != cellCount2) {
cellCount1 = app.tables.cells.count
cellCount2 = app.tables.cells.count
duration = NSDate().timeIntervalSince(startTime as Date)
if (duration > 30) {
XCTFail("Took too long waiting for cells to load")
}
}
//Now I know the table is finished loading and I can tap on a cell
app.tables.cells.element(boundBy: 1).tap()

Xcode UI Testing [xcode7-beta6] - Asserting actual label values when using accessibility labels

The question is actually really simple:
Is there a way to assert the displayed value from a specific label (e.g. UILabel) when using an accessibility label on this object?
As far as I see it, all the assertions (e.g. XCTAssertEquals) made in the examples, be it from a WWDC Talk or Blogposts, are only checking if an element exists for a query like XCTAssertEquals(app.staticTexts["myValue"].exists, true) or if the number of cells in a table is correct XCTAssertEquals(app.tables.cells.count, 5). So, when avoiding accessibility labels it's possible to check if an object has a certain value displayed, but not which object / element.
And when using accessibility labels, it robs me of the opportunity to query against the displayed values, because app.staticTexts["myValue"] will now fail to deliver a result but app.staticTexts["myAccessibilityLabel"] will hit.
Assuming I want to test my "Add new Cell to Table" functionality, I can test that there is really a new cell added to the list, but I have no idea if the new cell is added at the top or the bottom of the list or somewhere in between.
For me, an easy way to check if a specific element has a certain value should be a no-brainer when it comes to UI Testing.
It is possible that due to the missing documentation I might overlook the obvious. If so, just tell me.
Be sure to set the .accessibilityValue property of the UILabel whenever you set its .text value. Then in UITest, you can test the accessibility value like this:
let labelElement = app.staticTexts["myLabel"]
...
XCTAssertEqual(labelElement.value as! String, "the expected text")
I think you are asking a few different things, so I will try to answer each question individually.
Is there a way to assert the displayed value from a specific label (e.g. UILabel) when using an accessibility label on this object?
In short, no. UI Testing works by hooking into accessibility APIs, so you are limited to querying for objects based on that. You can, however, check the -value property of certain elements, such as controls. This is used to test if a switch is on or off. Note that these boil to down using accessibility APIs as well, just a different method (-accessibilityValue over -accessibilityIdentifier and -accessibilityLabel).
...but I have no idea if the new cell is added at the top or the bottom of the list or somewhere in between.
To interrogate an XCUIElement for its frame you can use the new XCUIElementAttributes protocol which exposes -frame. For example:
let app = XCUIApplication()
app.launch()
app.buttons["Add New Cell to Table"].tap()
let lastCell = app.cells["Last Cell"]
let newCell = app.cells["New Cell"]
XCTAssert(newCell.exists)
XCTAssert(newCell.frame.minY > lastCell.frame.maxY)
For me, an easy way to check if a specific element has a certain value should be a no-brainer when it comes to UI Testing.
If you think of everything in terms of accessibility this becomes a non-issue. UI Testing can only interact with your elements via accessibility APIs, so you must implement them. You also get the added benefit of making your app more accessible to user's with those settings enabled.
Try setting both the -accessibilityLabel or -accessibilityIdentifier for the cell to the displayed text. UI Testing can be finicky as to which one it uses.
It is possible that due to the missing documentation I might overlook the obvious. If so, just tell me.
XCTest and UI Testing don't have any official documentation. So I've gone and extracted my own from the header files exposed in the framework. Note than even though they were pulled from source, they are unofficial.
XCTest / UI Testing Documentation
What works for me is to set the accessibility identifier of the UILabel to let's say MyLabel.
func myLabelText() -> String {
let myLabelUIElement: XCUIElement = self.application.staticTexts["MyLabel"]
return myLabelUIElement.label
}
Tested with Xcode 8 and iOS 10
From the apple forums it looks like it is possible to get the value of the label:
The only way I've found is to not set an Accessibility Label, but use identifier instead. Then XCUIElement.label will change to match the current text of the label.
However there is a gotcha: if you have previously set Accessibility Label in XC, and remove it, an entry setting the label to "" remains in the storyboard. In this case, not only will calling .label will return "", but you won't be able to query for the label by it's text!
The only thing you can do is delete and re-add the label, or manually edit the xml.
lastobelus - https://forums.developer.apple.com/thread/10428

iOS & Xcode 6: Auto Layout Text Scaling on a Button

I have a Today Extension with a button and UIImage. The button and image scale and center themselves correctly except the button's text is not scaled. I am writing this extension in Swift. I have tried these so far:
myButton.titleLabel.adjustsFontSizeToFitWidth = true
myButton.adjustsFontSizeToFitWidth = true
Both return errors.
Without seeing more code or the specific errors, the only thing I can see is that titleLabel is an optional. As written, the compiler would show the error 'UILabel? does not have a member named 'adjustsFontSizeToFitWidth'. Writing the line as myButton.titleLabel?.adjustsFontSizeToFitWidth = true should fix the error.
Also, UIButton doesn't have a member named 'adjustFontSizeToFitWidth' so the second line would throw the same error as above.
Try fixing that error as indicated above and see if your AutoLayout issue works.

Resources