Skip to content

Instantly share code, notes, and snippets.

@JasonCanCode
Last active November 13, 2018 21:16
Show Gist options
  • Save JasonCanCode/5c29d3a0cde535583c5d0a3d00d0fa43 to your computer and use it in GitHub Desktop.
Save JasonCanCode/5c29d3a0cde535583c5d0a3d00d0fa43 to your computer and use it in GitHub Desktop.
An easier way to access UI elements or check for existence within a UI Test.
extension XCTestCase {
var app: XCUIApplication { return XCUIApplication() }
func checkExistenceOfElements(_ typesAndTexts: [(XCUIElement.ElementType, String)], timeout: TimeInterval = 3) {
for (type, text) in typesAndTexts {
checkExistenceOfElement(type, text, timeout: timeout)
}
}
func checkExistenceOfElement(_ type: XCUIElement.ElementType, _ text: String, timeout: TimeInterval = 3) {
XCTAssertTrue(element(ofType: type, withText: text, timeout: timeout).exists, "Element with text \"\(text)\" could not be found.")
}
/// Search for element in multiple locations within the view hierarchy
func element(ofType type: XCUIElement.ElementType, withText text: String, timeout: TimeInterval = 3) -> XCUIElement {
assert(type != .other, "It is not safe to use ElementType \"other\" in this helper method as it can create a recursive loop")
let shouldOnlyUsePredicates: Bool = text.count > 128
let inDescendants = app.descendants(matching: type).descendants(matching: type).matching(labelPredicate(forText: text)).firstMatch
let closeMatch = app.descendants(matching: type).element(matching: labelPredicate(forText: text)).firstMatch
var possibleElements: [XCUIElement] = [inDescendants, closeMatch]
if !shouldOnlyUsePredicates {
let inScrollView = app.scrollViews.otherElements.descendants(matching: type)[text]
let inApp = app.descendants(matching: type)[text]
let inTableView = app.tables.descendants(matching: type)[text]
let inTabBar = app.tabBars.descendants(matching: type)[text]
let inNavBar = app.navigationBars.descendants(matching: type)[text]
let lastDitchEffort = app.staticTexts[text]
possibleElements.append(contentsOf: [inScrollView, inApp, inTableView, inTabBar, inNavBar, lastDitchEffort])
}
let matchFilter: (XCUIElement) -> Bool = { $0.exists && $0.elementType == type }
var elapsedTime: TimeInterval = 0
while elapsedTime < timeout {
if let validElement = possibleElements.lazy.filter(matchFilter).first {
return validElement
}
usleep(200000) // sleep for .2 seconds
elapsedTime += 0.2
}
return app.descendants(matching: type)["Intentionally returning an element that does not exist"]
}
/// Breaks down multi-line text for predicate use
private func labelPredicate(forText text: String) -> NSPredicate {
let linesOfText = text.components(separatedBy: "\n")
var format = "label CONTAINS '\(linesOfText.first!)'"
if linesOfText.count > 1 {
for i in 1..<linesOfText.count where !linesOfText[i].isEmpty {
format += " AND label CONTAINS '\(linesOfText[i])'"
}
}
return NSPredicate(format: format)
}
}
@JasonCanCode
Copy link
Author

Example usage:

checkExistenceOfElements([
    (.navigationBar, "Make Payment"),
    (.textField, "Card Number, required"),
    (.button, "Take a photo to scan credit card number"),
    (.staticText, "Expiration Date"),
    (.image, "credit_card_icon")
])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment