Why XCTest?

As you may have heard during “Hey Siri” event in 2015 Apple introduced its own interpretation of testing framework, called XCTest (or XCUITest if we’re talking about UI testing). I was surprised to find out that even though it has been already 2 years from its release major part of the Test developers are still being skeptical about this framework.

From my perspective this instrument has gotten everything you need for writing automated tests against iOS/tvOS platforms and I wouldn’t go with anything else unless I need cross-platform (iOS and Android) support. And even then I would consider separation of the frameworks. But that’s another topic for discussion.

Here is why I would suggest XCTest over other tools for UI testing of iOS and tvOS applications:

  • No additional components required to be installed. Starting from Xcode version 7 Apple ships XCTest prebuilt into its development kit and all you need to do is just activate it. I will show how to do that in further reading.
  • Swift/Objective-C native language support. If you think of it, it’s pretty big advantage. Since there is no learning curve or language barrier iOS developers and Automation engineers could support/review/extend each other’s test scripts and collaborate on creating new ones.
  • Easy CI integration. Xcode allows to execute tests via command line and this is well used by all major CI systems, like Jenkins, TeamCity, Travis or Bamboo. All of them could be easily set up to run iOS functional tests and collect their results.
  • Speed. There is no secret that all of the iOS UI test frameworks available on the market are based on XCUITest infrastructure (well, sometimes it may be its ancestor - UI Automation). Obviously something on top would work slower than its lower layer by itself. Finding elements, extracting their attributes, typing text etc - everything is a bit faster in pure XCTest.
  • Test Recorder. I don’t use this one but it might be beneficial for beginners to leverage Recorder. One can write actual test steps agains his app and after that see their implementation as an UI test. Yes, it won’t be perfect but with minor changes we could use it in our framework. That said it’s great for learning purposes.
  • tvOS support. For the moment there are no reliable frameworks out there which can easily support testing on Apple TV devices. Thus if you want to verify tvOS app, XCUITest is the only way to go. One of my next posts will be completely dedicated to Apple TV testing.

How to get started writing tests with XCTest

It’s pretty simple. All we have to do is to add extra target to our Xcode project.

how to configure XCUITest
Select Xcode project file and add new target to your project
iOS UI testing setup
Find iOS UI Testing Bundle among Target templates
Configure Swift for XCTest UI tests
Choose programming language you want to use in your UI tests

After you went through all the steps above you should see new target was added to your project along with some autogenerated sources. They are not very useful but may give us an idea how to start our app before the test and close it afterwards.

iOS UI Testing target was added to Xcode project
New iOS UI Testing target was added to Xcode project

Introduction to XCUITest API

XCTest is rather light-weight library, all of its classes are conveniently located in one file - XCTest. Here are the main ones of them:

  • XCUIElementQuery - class for querying app’s UI hierarchy to find UI element. For simplicity we could treat element query as an array of all elements, which match to the given criteria. XCUIElementQuery can be chained with other queries.
    let queryForButton: XCUIElementQuery = app.buttons.descendants(matching: .any)
    
  • XCUIElement - one of the main classes in XCTest library. It represents UI element in iOS app user interface hierarchy. Lots of stuff can be done to it (we can click on it, read and verify its attribute values, swipe it, drag-and-drop it, input text into it etc).
    let firstButton: XCUIElement = app.buttons.firstMatch
    firstButton.click()
    let imageWithId: XCUIElement = app.images["identifier"]
    image.swipeUp()
    
  • XCUIElement.Type - enum for types of the UI elements which can be located. For instance .image, .button, .alert, .any etc. Types are often used with XCUIElementQuery to build query for finding UI element.
    let cellType: XCUIElement.`Type` = .cell
    let queryForImageType = app.descendants(matching: .image)
    
  • XCUIApplication - one of most important classes for building successful UI test framework. It represents iOS application and basically is the only way to interact with it. It will allow us to start and terminate the app, locate elements inside it and verify their attributes. Since XCUIApplication extends XCUIElement class, you may consider it as a root element in app hierarchy.
    let app = XCUIApplication()
    app.launch()
    
  • XCUIScreen - represents physical screen(s) of the iOS/tvOS/macOS device. Its main purpose is snapping screenshots in test runtime.
  • XCUIScreenshot - a captured image of a device screen or its part.
    // Take a screenshot of an app's first button.
    let windowScreenshot = app.button.firstMatch.screenshot()
    app.terminate()
    // Take a screenshot of the screen after app was terminated
    let mainScreenScreenshot = XCUIScreen.main.screenshot()
    
  • XCUIDevice - as you can guess this class represents device which tests are being run on. It’s not frequently used in testing, but you can do some interesting stuff with it. For example, it can help you emulate hardware buttons, check screen orientation and interact with Siri, which was added in the latest release.
    let device = XCUIDevice.shared
    device
    .siriService
    .activate(voiceRecognitionText: "Hey Siri! How are you?")
    
  • XCUIRemote - this class simulates interactions done via physical remote controller (e.g. on Apple TV devices).
    let remote = XCUIRemote.shared
    remote.press(.playPause)
    

The classes above are the most used by majority of developers in their test frameworks. But of course these are not all of them. Full XCTest API for user interface tests can be found in official documentation.

XCTest UI test example

Assuming we’ve got familiar with main classes in XCUITest library, let’s go ahead and create our first ever test script.

class SimpleTest: XCTestCase {
 // Creating app instance
 let app = XCUIApplication()

 override func setUp() {
  // Setting continue after failure strategy
  continueAfterFailure = false
  // Launching the app
  app.launch()
 }

 func testForm() {
  // Finding text field
  let editText = app.textFields["edit_text"]
  // Sending text to field
  editText.typeText("123456")

  let submitButton = app.buttons["submit"]
  // Tapping on button
  submitButton.tap()

  let alert = app.alerts.firstMatch
  // Waiting for alert to appear
  _ = alert.waitForExistence(timeout: TimeInterval(10))
  XCTAssertEqual(alert.label, "Success!")
 }

 override func tearDown() {
  // Taking screenshot after test
  let screenshot = XCUIScreen.main.screenshot()
  let fullScreenshotAttachment = XCTAttachment(screenshot: screenshot)
  fullScreenshotAttachment.lifetime = .keepAlways
  add(fullScreenshotAttachment)
  // Closing the app
  app.terminate()
 }
}

This test is looking for the text field at first, then inputs some text into it, presses the button and waits for alert to appear. When alert appears, it will verify its text.

There was a basic wait used in the code snippet above. If you want to find more about advanced techniques of doing that, check out my recent post about Waits in XCUITest.

That’s it for today’s XCTest UI Testing Basics. If you have something to add please share it in the comments.

Feel free to share!

Leave a Comment