Skip to content

So far with our XCUITest 101 series, we have covered most of the topics around setting up XCUITest framework and making it scalable and maintainable. However, we haven’t covered the XCUITest API and the other features of XCUITest framework that we can use in our iOS app testing. In this post, we will cover the basics of XCUITest API and how to utilize those API to write UI tests for iOS apps.

XCUITest API

Apple announced the Xcode UI Testing which covers most of the underlying architecture of the XCUITest framework. It’s highly recommended that one should watch this session until the end to understand the XCUITest API in detail. In summary, XCUITest has three main classes:

  • XCUIApplication – This class is responsible for launching the app under test. Since Xcode 9, we can also perform multi-app testing as well.
  • XCUIElementQuery – This class is responsible for finding elements in the view using different types of queries. The query should return a unique UI element.
  • XCUIElement – This class is responsible for UI elements and perform some actions on the UI elements.

These are the basic classes provided by the XCUITest framework. Now, we will see some examples of how to use those API.

No time to read the blog? Take away this free XCUITest 101 ebook

Launching/Terminating App

If we want to launch/terminate the target app which is the main app, we can easily do that using the launch or terminate method.

XCUIApplication().launch()
XCUIApplication().terminate()

If we want to launch an app with certain launch arguments or launch environments that have been set up in the main app. e.g we can launch an app with launchArgument as UITest using the launchArguments method.

XCUIApplication().launchArguments = ["UITest"]

Similarly, we can launch an app with launch environments as below

XCUIApplication().launchEnvironment = ["UITest": "NO"]

The only difference between launchArgument & launchEnvironment is that the argument is the array of parameters and the environment is a dictionary passed to them. You can read more about the Launch Arguments and Launch Environments here.

Launch Different App

Since Xcode 9, we can launch a different app, if we have a bundle identifier of the app. If you want to launch the Safari app, for example, then you can do that using

XCUIApplication(bundleIdentifier: "com.apple.mobilesafari").launch()

Find UI Elements

While writing XCUITest tests for iOS apps, it’s important to find an element in the view. We have to use XCUIElementQuery to find the unique element on the page. There are different techniques to reach a unique element. The most common one is Subscripting. If we want to find all the buttons in the current view, for example, we can print out all the buttons using XCUIApplication().buttons but if you want to access with the Search identifier, then we have to use subscript like this

XCUIApplication().buttons["Search"]

This is applicable for other UI elements like Static Text, Collections Views, etc. There are other techniques to find elements using predicates and indexes mentioned in the official API here.

Determine State Of the Element

Once we’ve found the UI Element using the abovementioned query, the next step would be determining the state of the element or perform some action on it. We can easily check if the element exists in the view hierarchy using exists method as below.

XCUIApplication().buttons["Search"].exists

This method will return boolean depending on the existence of the button in the view hierarchy. There are other properties that we can verify as if the element is hittable or enabled etc.

While asynchronous UI tests are under execution, we have to wait for the UI element to appear on the screen. We can easily wait for the element to appear on the screen using the waitForExistance method on the UIElement. When we tap on the Search button, for instance, then search results will take some time to load. We can wait for the Search Results using

XCUIApplication(). StaticText["Search Result"].waitForExistance(timeout: 5)

This will return true if search results appear in 5 seconds.

Perform Action On the Element

The XCUITest API gives loads of options to perform actions on the XCUIElement if the state of the UI element is actionable. There are various methods with different actions are available as part of the XCUIElement class. For example, we can tap on the element using tap method

XCUIApplication().buttons["Search"].tap()

There are various other API available to perform the other actions like typing the text in the text field, swipe up, swipe down, pinch, adjust the picker wheel value, etc. If you want to know more about different options, there is an excellent blog post UI testing Cheat Sheet with examples here.

Asserting the UIElement

In most of the cases, there is no need to add explicit assertions while finding elements or performing an action on the UI elements. The test will fail automatically if the expected UI element not found on the screen. However, in some cases, we need to add some explicit assertions. Apple has provided XCTAssert and XCTAssertTrue macro to check if the state of the UI element is as expected. For example, we can assert that the Search button exists or not using

XCTAssertTrue(XCUIApplication().buttons["Search"].exists)

The assertion will fail if there isn’t any Search button in the existing view.

Now we have covered the basics of the XCUITest API and how we can use those API in our existing apps. Though it’s hard to cover each method in a post, we have touched all the basic methods – those are commonly used in the XCUITest scripting. If you want to try it out on iOS devices in the cloud, you can check out what Bitbar Testing has to offer.

Conclusion

Apple’s XCUITest framework provides convenient API so that test developers can write clean and concise tests for their apps. Test developers just need to understand and explore the XCUITest API and use them effectively in their workflow. We’ll cover how to set up XCUITest for iOS continuous integration.

Shashikant Jagtap

Mobile DevOps Engineer