As Martin Flowler described in his article TestPyramid, the UI should be the highest (and not the only) layer of the tests. They are a safety net and errors detected on this layer can point to missing test’s on a lower layer. Automated UI test’s tend to be brittle, because every change in the UI can result in failing test’s.
Since version 7 XCode supports UI tests, with test classes deriving from XCTestCase:
- Control the device (rotate, press hardware buttons) – XCUIDevice
- Control start / stop the app – XCUIAppication
- Query elements – XCUIElementQuery
- Interact with elements / verify state – XCUIElement
- Simulate a remote – XCUIRemote
- Interact by coordinates – XCUICoordinate
Here’s an example test:
class PersonalProductivityAssistantUITests : XCTestCase {
var app = XCUIApplication()
var toolbarAddActivityButton: XCUIElement?
var activityInputField: XCUIElement?
//...
var timeLogSaveButton: XCUIElement?
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launch()
activityInputField = app.textFields["textEditActivity"]
toolbarAddActivityButton = app.toolbars.buttons["Log Time"]
// ...
timeLogSaveButton = app.navigationBars["Time Log"].buttons["Save"]
}
override func tearDown() {
super.tearDown()
app.terminate()
}
}
func testCanAddAndEditAndDeleteActivityFromTable() {
//
// Add
//
// Open the add time log view
waitForElementToAppear(toolbarAddActivityButton!)
toolbarAddActivityButton!.tap()
toolbarAddActivityButton!.tap()
// Type new time log informations
waitForElementToAppear(activityInputField!)
let initialActivityName = getActivityNameWithDateTime()
typeActivityName(initialActivityName)
labelActivity?.tap()
buttonPickDateTimeFrom?.tap()
setDatePickerValues(monthAndDay: "Aug 1", hour: "10", minute: "30", amPm: "AM")
setDateTimeFromButton?.tap()
buttonPickDateTimeUntil?.tap()
setDatePickerValues(monthAndDay: "Aug 1", hour: "11", minute: "15", amPm: "AM")
setDateTimeUntilButton?.tap()
timeLogSaveButton?.tap()
// Verify element has been added
XCTAssert(getTableStaticTextElement(initialActivityName).exists)
//
// Edit
//
// tap the actiity to open the add/edit segue
getTableStaticTextElement(initialActivityName).tap()
let changedActivityName = "\(getActivityNameWithDateTime()) #test"
waitForElementToAppear(activityInputField!)
clearAndTypeActivityName(changedActivityName)
timeLogSaveButton?.tap()
// Verify element has been modifed
XCTAssert(getTableStaticTextElement(changedActivityName).exists)
//
// Delete
//
// Swipe up until the new element ist visible
doSwipeUpUntilTableStaticTextIsHittable(changedActivityName)
// Swipe left and push delete button
doDeleteTableRow(changedActivityName)
// Verify the element has been deleted
XCTAssert(!getTableStaticTextElement(changedActivityName).exists)
}
This is how it looks like, when it runs in the debugger:
You can find the full test in the latest version of my learning app PersonalProductivityAssistant @github.
Learn more about recording UI tests.