UI Testing Quick Guide

Have you tried to use Xcode to write UI tests or automate your screenshots with fastlane? The documentation for iOS UI Testing leaves much room for improvement. The class documentation doesn’t help unless you already know what to do. Here’s my reference guide to get you started:

Setup UI Tests

  1. Create the UI Test target when you create the Xcode project or add it later with File > New > Target...:

    Create UI Test Bundle

    Note: You can write UI tests in Swift even if you’re testing an application written in Objective-C.

  2. Use the Test action in the scheme editor (Product > Scheme > Edit Scheme...) to control which tests are enabled:

    Scheme Test Action

    Use the Options button to run tests in parallel (faster) and randomize order to uncover any dependencies between tests:

    Test options

    New tests are enabled by default.

  3. Setup and launch application:

    // UITests.swift
    import XCTest
    
    class UITests: XCTestCase {
    
      let app = XCUIApplication()
    
      override func setUp() {
        continueAfterFailure = false
        app.launch()
      }
    
      override func tearDown() { ... }
    
      func testSomething() { ... }
    }
    

Writing UI Tests

Test User Interface with two buttons and a label

func testStart() {
  let startButton = app.buttons["startButton"]
  let stateLabel = app.staticTexts["stateLabel"]
  startButton.tap()
  XCTAssertEqual(stateLabel.label, "Running")
}

Xcode recording

Record test

If your writing your tests manually (without recording) try printing the the element subtree for the application to see what you can query for (element types, identifier and label):

print(app.debugDescription)

Element subtree:
  Application, 0x600003a4cf70, pid: 25509, label: 'MyApp'
    Window (Main), 0x600003a4d1e0, {{0.0, 0.0}, {375.0, 667.0}}
      Other, 0x600003a4d2b0, {{0.0, 0.0}, {375.0, 667.0}}
        NavigationBar, 0x600003a4d380, {{0.0, 20.0}, {375.0, 44.0}}, identifier: 'Test'
          Other, 0x600003a4d450, {{170.5, 32.0}, {34.0, 20.5}}, label: 'Test'
        Other, 0x600003a4d520, {{0.0, 0.0}, {375.0, 667.0}}
          Other, 0x600003a4d5f0, {{0.0, 0.0}, {375.0, 667.0}}
            Other, 0x600003a4d6c0, {{0.0, 0.0}, {375.0, 667.0}}, identifier: 'rootView'
              Other, 0x600003a4d790, {{20.0, 84.0}, {335.0, 92.5}}
                StaticText, 0x600003a4d860, {{20.0, 84.0}, {335.0, 31.5}}, identifier: 'stateLabel', label: 'Waiting...'
                Other, 0x600003a4d930, {{20.0, 135.5}, {335.0, 41.0}}
                  Button, 0x600003a4da00, {{20.0, 135.5}, {163.5, 41.0}}, identifier: 'stopButton', label: 'Stop'
                  Button, 0x600003a4dad0, {{191.5, 135.5}, {163.5, 41.0}}, identifier: 'startButton', label: 'Start'

Accessibility Identifier

Set the accessibility identifier on an element you want to query rather than relying on a text label or title which might be localized:

Accessibility identifier

let startButton = app.buttons["startButton"]

Note: Element must be enabled for accessibility identifier to be visible to UI test.

System Alerts

To dismiss system alerts that might otherwise interrupt UI tests, add to setUp():

addUIInterruptionMonitor(withDescription: "System Dialog") { (alert) -> Bool in
   // Tap "Allow" button
   alert.buttons["Allow"].tap()
   return true
 }
 // Need to interact with App
 app.tap()

See Handling System Alerts In UI Tests for further details on handling different types of alerts.

XCUIApplication

XCUIApplication is a proxy for your App (the App runs in a separate process):

Use launch(), activate(), or terminate to change the state of the App being tested:

XCUIDevice

XCUIElementQuery

Querying the application proxy for elements:

XCUIElement

You interact with and test properties of an XCUIElement:

XCUIElementAttributes

Properties of an XCUIElement that you can query or test:

XCUICoordinate

Create a coordinate location on screen relative to a reference element (must exist):

let rootView = app.otherElements["rootView"]
let target = rootView.coordinate(withNormalizedOffset: CGVector(dx: 10, dy: 10))

Tap, double tap or press at coordinate location:

target.tap()
target.doubleTap()
target.press(forDuration: 3)

Create a destination offset from existing coordinate and then drag to it:

// Drag down 100 points
let endPoint = target.withOffset(CGVector(dx: 0, dy: 100))
target.press(forDuration: 3, thenDragTo: endPoint)

Learn More

Some other recent posts on UI Testing:

Still fighting Xcode?

Sign up to get my iOS posts and news direct to your inbox and I'll also send you my free UI Testing Cheat Sheet

    Unsubscribe at any time. See privacy policy.

    Archives Categories