Here is my code -> https://github.com/patchthecode/XCTestBug
Here is my view ->
I am trying to capture the gray colored view.
It contains a label Ll and Button 11
This code captures the view
let p1 = NSPredicate(format: "label LIKE[c] %#","L1")
let views = app.otherElements.containing(p1)
This code also captures the view correctly
let p2 = NSPredicate(format: "label LIKE[c] %#","11")
let views = app.otherElements.containing(p2)
But this code fails to capture anything
let p1 = NSPredicate(format: "label LIKE[c] %#","L1")
let p2 = NSPredicate(format: "label LIKE[c] %#","11")
let comp = NSCompoundPredicate(andPredicateWithSubpredicates: [p1, p2])
let views = app.otherElements.containing(comp)
What am i doing wrong?
Even if the comp predicate would work, it would not capture the gray view. It would capture all elements that contain the "L1" and the "11" element. In your case it captures 2 elements: the ViewController's view and the gray view (because both contain the "L1" and the "11". So this is not the right way to capture the gray view.
The easiest part to capture the gray view would be to add an accessibility identifier to it (and enable Accessibility for that UIView). Then you can query the gray box easily with:
let grayBox = app.otherElements.matching(identifier: "grayBox").element(boundBy: 0)
Your NSCompoundPredicate solution does not work because the query is looking for elements that contain ONE label with the text "L1" AND "11". And because a UILabel can only have one text this always fails.
Related
How To Display Dynamic Buttons Text Values in an app.scrollViews?
I would like to able to tap the button inside first row in the scrollViews, but not sure what the index of the button is. I tried the 1, 2 and 3 with no luck.
let scrollViewsQuery = app/*#START_MENU_TOKEN#*/.scrollViews/*[[".otherElements[\"Tabbar\"].scrollViews",".scrollViews"],[[[-1,1],[-1,0]]],[0]]#END_MENU_TOKEN#*/
let elementsQuery = scrollViewsQuery.otherElements
elementsQuery.buttons.element(boundBy: 0).tap() //
print("----------------------------------------------")
var i = 0
for element in elementsQuery.buttons.allElementsBoundByIndex{
i += 1
print(i)
print(element) //How To Display the Button Text here?
// print( elementsQuery.buttons.element(boundBy: i))
}
Assuming you only have one scrollView present, the code to tap the first button in it would be the following:
let myScrollView = app.scrollViews.firstMatch
let myScrollViewsButtons = myScrollView.buttons
let myScrollViewsFirstButton = myScrollViewButtons.firstMatch
myScrollViewsFirstButton.tap()
A button in this context is an XCUIElement, not something that is particularly printable. Buttons do have label attributes that are generally the text displayed on them...
I am writing an UI test case, in which I need to perform an action, and then on the current page, scroll the only UITableView to the bottom to check if specific text shows up inside the last cell in the UITableView.
Right now the only way I can think of is to scroll it using app.tables.cells.element(boundBy: 0).swipeUp(), but if there are too many cells, it doesn't scroll all the way to the bottom. And the number of cells in the UITableView is not always the same, I cannot swipe up more than once because there might be only one cell in the table.
One way you could go about this is by getting the last cell from the tableView. Then, run a while loop that scrolls and checks to see if the cell isHittable between each scroll. Once it's determined that isHittable == true, the element can then be asserted against.
https://developer.apple.com/documentation/xctest/xcuielement/1500561-ishittable
It would look something like this (Swift answer):
In your XCTestCase file, write a query to identify the table. Then, a subsequent query to identify the last cell.
let tableView = app.descendants(matching: .table).firstMatch
guard let lastCell = tableView.cells.allElementsBoundByIndex.last else { return }
Use a while loop to determine whether or not the cell isHittable/is on screen. Note: isHittable relies on the cell's userInteractionEnabled property being set to true
//Add in a count, so that the loop can escape if it's scrolled too many times
let MAX_SCROLLS = 10
var count = 0
while lastCell.isHittable == false && count < MAX_SCROLLS {
apps.swipeUp()
count += 1
}
Check the cell's text using the label property, and compare it against the expected text.
//If there is only one label within the cell
let textInLastCell = lastCell.descendants(matching: .staticText).firstMatch
XCTAssertTrue(textInLastCell.label == "Expected Text" && textInLastCell.isHittable)
Blaines answer lead me to dig a little bit more into this topic and I found a different solution that worked for me:
func testTheTest() {
let app = XCUIApplication()
app.launch()
// Opens a menu in my app which contains the table view
app.buttons["openMenu"].tap()
// Get a handle for the tableView
let listpagetableviewTable = app.tables["myTableView"]
// Get a handle for the not yet existing cell by its content text
let cell = listpagetableviewTable.staticTexts["This text is from the cell"]
// Swipe down until it is visible
while !cell.exists {
app.swipeUp()
}
// Interact with it when visible
cell.tap()
}
One thing I had to do for this in order to work is set isAccessibilityElement to true and also assign accessibilityLabel as a String to the table view so it can be queried by it within the test code.
This might not be best practice but for what I could see in my test it works very well. I don't know how it would work when the cell has no text, one might be able to reference the cell(which is not really directly referenced here) by an image view or something else. It's obviously missing the counter from Blaines answer but I left it out for simplicity reasons.
I see I can query textfields, tables, buttons, but not labels, why?
app.textFields["Username"].typeText("bcyops")
app.secureTextFields["Password"].typeText("ops15")
app.buttons["Submit"].tap()
app.tabBars.buttons["Main"].tap()
let tablesQuery = app.tables
If you mean label as static text element, you can query label:
let label = app.staticTexts["yourLabelIdentifier"]
If you need a text in the label
let textInLabel = app.staticTexts["yourLabelIdentifier"].label
documentation with all elements types is here: https://developer.apple.com/documentation/xctest/xcuielementtypequeryprovider
Labels are referred to as staticTexts in XCTest UI testing.
app.staticTexts["myLabel"].exists
You can use NSPredicates to query elements with specified labels:
app.textViews.element(matching: NSPredicate(format: "label LIKE theLabelYoureLookingFor"))
I have a small test project that I'm trying to import into my production project. The ViewController that is causing the problem consists of a back ground image and a Scroll View (SV).
3 images appear in the test SV, yet only 2 appear in production. There appears to be a gap where the first image should appear.
Please note. The first image is the background image for the VC. I set it, then delete it from the array that feeds the scrollview.
Here are the two ViewControllers. Please note I embedded the VC in a TabBar and NavBar controller in test because that is what I have int production.
What is most puzzling is the code is exactly the same. The image URL's are the same. But the number of UIImageViews added to the scrollView are different. Note the last print statement in the code:
func setupList() {
print(foodPairings.count)
let imageStringURL = foodPairings[0].imageURL
let encodedURL = imageStringURL.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
guard let imageURL: URL = URL(string: encodedURL!) else {return}
bgImage.af_setImage(withURL: imageURL)
foodPairings.removeFirst()
print(foodPairings.count)
print(foodPairings.indices)
for i in foodPairings.indices {
let imageView = UIImageView()
let imageStringURL = foodPairings[i].imageURL
let encodedURL = imageStringURL.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
guard let postURL: URL = URL(string: encodedURL!) else {return}
imageView.af_setImage(withURL: postURL, placeholderImage: #imageLiteral(resourceName: "Placeholder"), filter: nil, progress: nil, imageTransition: .noTransition, runImageTransitionIfCached: false, completion: nil)
imageView.tag = i
imageView.contentMode = .scaleAspectFill
imageView.isUserInteractionEnabled = true
imageView.layer.cornerRadius = 20.0
imageView.layer.masksToBounds = true
listView.addSubview(imageView)
//attach tap detector
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTapImageView)))
}
print(listView.subviews.count)
listView.backgroundColor = UIColor.clear
positionListItems()
}
The print statements in Test result in:
4 3
0..<3 4
Production prints the following:
4 3
0..<3 5
Why is listView.subviews.count different in production?
I have resolved this, but it is not clear what the source of the issue was.
In Test, my Scroll View had one subview even prior to me adding the array of UIImageViews. In Production, the Scroll View had two subviews prior to me adding the images in the array.
Just prior to the loop where I add my subviews, I remove all subviews from the ScrollView:
listView.subviews.forEach({ $0.removeFromSuperview() })
for i in foodPairings.indices {
print("SubviewCount: \(listView.subviews.count)")
let imageView = UIImageView()
...
Both my Test and Production macs are running XCode 9 Swift 4. Still remains a mystery why subview count was different.
I have written the same behaviour in two ways. The first one, doesn't work:
var barbutton = (left) ? navigationItem.leftBarButtonItem : navigationItem.rightBarButtonItem
barbutton = UIBarButtonItem(customView: button)
opposite to:
if (left) {
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: button)
} else {
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
}
What is the difference?
As #Martin points out, the two code sequences are very different in function.
The first sets barbutton to the contents of either leftBarButtonItem or rightBarButtonItem and then discards that value and sets (the temporary variable) to a newly created button.
The second sets either leftBarButtonItem or rightBarButtonItem, depending on left to a newly created button.
There really isn't much way to shorten your code and still achieve the desired effect (without also obscuring the code).
You could use:
(left ? navigationItem.setLeftBarButtonItem : navigationItem.setRightBarButtonItem)(UIBarButtonItem(customView: button), animated: false)
But that's just weird.