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.