Waldo sessions now support scripting! – Learn more
Testing

An Introduction to XCUI Methods for UI Testing

Nabendu Biswas
Nabendu Biswas
An Introduction to XCUI Methods for UI Testing
November 29, 2022
7
min read

In this post, we're going to learn about XCUITest and XCUITest methods, which help us perform tests. We're going to walk through examples of these by creating test cases for a small calculator app. The code for the same app used in this project can be taken from here. For full XCUITest documentation, check out Apple's site.

But before creating the app, let's learn what XCUITest is first.

XCUITest is the iOS automation testing framework released by Apple in 2015

What Is XCUITest in iOS?

XCUITest is the iOS automation testing framework released by Apple in 2015. It's based on XCTest, which is the Apple default testing framework for unit testing. With XCUITest, you can do automation testing for iOS apps, like what we do with Appium.

XCUITest is written on the Mac-only IDE (Integrated Development Environment) called XCode. The test cases can be written in either Swift or Objective-C, the languages used to write native iOS apps. We can't test hybrid apps created using React Native with XCUITest.

How Do I Use XCUITest?

We don't need to install anything for running XCUITest in XCode, because it's built-in. But we need to first do some setup before moving on to write our test cases.

Setup

After downloading the sample calculator app, double-click on Kalculator.xcodeproj to open it in XCode.

downloading calculator app

Once the project is open in XCode, click on Product -> Clean Build Folder. This is required for a clean start.

clean build folder

Now, click on Product -> Build. This is required to build the project.

build project

Now, run the project by clicking on the Run icon. It'll open the project in the default simulator.

default simulator

To do the test setup, first, click on the root of the app. This is Kalculator in our case. It'll open a screen, where you can click on the + icon at the bottom. And this will open a pop-up, in which you can click on UI Testing Bundle.

ui testing bundle

On the next screen, we'll keep everything as default. And after, that click on the Finish button.

clicking finish button

Now, a new folder of KalculatorUITests will be created with the required files.

kalculatoruitests

Preparing for Test Cases

Now, inside the KalculatorUITests folder, create three new folders: Utils, PageObjects, and Tests. After that, inside the Tests folder, create a new Swift File.

creating new swift file

Now, we'll give this new file the name of NumSelectTests and click on the Create button.

click on create button

Next, in the NumSelectTests file, put the below code. Here, we're inheriting from XCTestCase first. After that, we're overriding the setUp() function, which will run before every test.


        import XCTest
        class NumSelectTests: XCTestCase {
            override func setUp() {
             super.setUp()
             continueAfterFailure = false
             XCUIApplication().launch()
          }
        
          override func tearDown() {
          //
          }
        
          func testAllButtons(){
          }
        }

We also have an empty function of tearDown() and a function to test, which we're going to complete later. You can learn more about the setUp() and tearDown() functions in our earlier post here.

setup and tear down function

Next, create a file BaseTest in the PageObjects folder. This file mainly contains the boilerplate code needed to perform XCUITest. Here, we have a public class of BaseTest, which is using XCUIApplication and XCUIElement. We're going to learn about them in the next section.


import XCTest
class Logger{
  func log(_ mlog: String){
   NSLog(mlog)
  }
}

public class BaseTest {
   typealias Completion = (() -> Void)?
   let app = XCUIApplication()
   let log = Logger().log
   required init(timeout: TimeInterval=10, completion: Completion = nil){
     log("waiting \(timeout)s for \(String(describing: self)) existence")
     XCTAssert(rootElement.waitForExistence(timeout: timeout),
     "Page \(String(describing: self)) waited, but not loaded")
   completion?()
  }

  var rootElement: XCUIElement {
   fatalError("Subclass should overrride rootElement")
 }
}
xcui elements

Now, we'll refer to the different fields through XCUIElement in our BaseTest file.


 //Button
func button(_ name: String) -> XCUIElement{
  return app.buttons[name]
}

//Navigation Bar
func navBar(_ name: String) -> XCUIElement{
  return app.navigationBars[name]
}

//SecureField
func secureField(_ name: String) -> XCUIElement{
  return app.secureTextFields[name]
}

//TextField
func textField(_ name: String) -> XCUIElement{
  return app.textFields[name]
}

//TextView
func textView(_ name: String) -> XCUIElement{
  return app.textViews[name]
}

//Text
func text(_ name: String) -> XCUIElement{
  return app.staticTexts[name]
}
code sample

Getting IDs Through Accessibility Inspector

For testing, we need the unique ID for the element of our app. To get the unique ID, we can use the Accessibility Inspector. So, open it up by clicking on XCode -> Open Developer Tool -> Accessibility Inspector.

accessibility inspector

The Accessibility Inspector will now be opened. We've also opened the calculator app in our simulator. Here, we need to click on the target icon in the upper-right corner of Accessibility Inspector.

navigation

Next, in the simulator, click on the different elements to get their unique ID. Here, we've clicked on Digit 1 to get its Label. Here, the Label is the unique ID, which is 1 for this element.

unique id

Writing and Executing Test Cases

Now, we'll write our test cases. So, create the file CalcTest inside the PageObjects folder. Here, we're first inheriting from the BaseTest class. Next, we're overriding the rootElement to have staticTexts of the digit 1.

After that, we created a oneButton variable, which will get the element with an ID of 1. We've also created a tapOne function, which will tap on button 1.


import XCTest

public class CalcPage: BaseTest {
  override var rootElement: XCUIElement{
    return app.staticTexts["1"]
  }

  lazy var oneButton = app.staticTexts["1"]

 @discardableResult
 func tapOne(completion: Completion = nil) -> Self {
   log("One button tap done")
   oneButton.tap()
   return self
 }
}
writing and executing test

Now, in the NumSelectTests file, we'll call the CalcPage class and then the tapOne function.

tapone function

To run our only test, first, go to the test tab by clicking on the tick icon. After that, in the testAllButtons, click on the play button to run the test.

testallbuttons

Back in the file CalcTest, we've added three more buttons, using the unique IDs for them taken from our earlier interactions with Accessibility Inspector. We've also added the functions for these three buttons.


lazy var plusButton = app.staticTexts["+"]
lazy var fiveButton = app.staticTexts["5"]
lazy var equalButton = app.staticTexts["="]

@discardableResult
func tapFive(completion: Completion = nil) -> Self {
  log("Five button tap done")
  fiveButton.tap()
  return self
}

@discardableResult
func tapPlus(completion: Completion = nil) -> Self {
  log("Plus button tap done")
  plusButton.tap()
  return self
}

@discardableResult
func tapEqual(completion: Completion = nil) -> Self {
  log("Equal button tap done")
  equalButton.tap()
  return self
}
code sample

Now, we add these new function calls in the NumSelectTests file.

numselecttestfile

Running the test case again will open the simulator, and all the buttons will be clicked as if a real user is performing an interaction. And we'll also get the Test Succeeded message.

test succeeded

XCUIApplication and XCUIElement

With the XCUI methods XCUIApplication and XCUIElement, we can easily perform our automation testing in iOS apps. The XCUIApplication method is used to launch our app, which is required to start the testing. With the XCUIElement, we can access all elements of our app.

We have to use XCUIApplication in the file NumSelectTests. Here, we've used it to launch the application.


XCUIApplication().launch()

This is the proxy of the app which is launched.

proxy of app launched

We've used XCUIElement in the BaseTest file. These are the actual elements of the application. These methods are there to select these elements in different ways.

select element in different ways

Later on in CalcPage, we're using the staticTexts from the BaseTest file. Here, we're passing the ID, which we got from Accessibility Inspector.

There are various ways to interact with the element. We're using the tap() to interact with the button of 1 in our calculator app. This will tap button 1 on our calculator.

Some of the other click-based functions and UI interactions are listed below.


    //Click-based functions
        tap()
        doubleTap()
        twoFingerTap()
        //UI Interactions
        swipeLeft()
        swipeRight()
        swipeUp()
        swipeDown()
static text in variables

General Advice for XCUI Methods

Always use best practices when accessing and utilizing XCUI methods. We can't do much in XCUIApplication, as it's only used to launch the application. But we can use different location strategies to find elements by XCUIElement. One of the best ways to get the elements is using Accessibility Identifiers.

These identifiers are generally set by the developer during the coding. The code can look like the below example.


    digitOne.accessibilityIdentifier = "oneButton"

But in iOS apps, they can be set later by the tester through Storyboard, as well. Below, in our calculator app, we opened the Main file. After that, we selected the digit 1 and gave the Accessibility Identifier by giving the name in the Identifier box.

accessibility identifier

Now, we can access button 1 through this new Accessibility Identifier in our CalcPage file. This is the better way to access it, as we can give it any name we want.


    lazy var oneButtonAI = app.buttons["oneButton"]

    @discardableResult
      func tapOneAI(completion: Completion = nil) -> Self {
       log("One button tap done by Accessibility Identifier")
       oneButtonAI.tap()
      return self
     }
discardable result

What You've Learned

In this post, you learned about the powerful XCUI methods XCUIApplication and XCUIElement in XCUITest. You learned to do automation testing in iOS apps using XCUITest. You also learned to test a sample app with XCUITest, as well as some general advice for using XCUIElement.

If you want to test your apps more easily through an intuitive automation framework, then you should try Waldo. It’s a testing platform that only requires the APK or IPA (iPhone Application) file. Once you upload the APK file, you can interact with the app like a real user. Waldo automatically generates test cases and gives easy-to-use reports of the test cases.

Automated E2E tests for your mobile app

Waldo provides the best-in-class runtime for all your mobile testing needs.
Get true E2E testing in minutes, not months.

Reproduce, capture, and share bugs fast!

Waldo Sessions helps mobile teams reproduce bugs, while compiling detailed bug reports in real time.