Is there a way to reset the app between tests in Swift XCTest UI?

You can add a “Run Script” phase to build phases in your test target to uninstall the app before running unit tests against it, unfortunately this is not between test cases, though.

/usr/bin/xcrun simctl uninstall booted com.mycompany.bundleId

Update


Between tests, you can delete the app via the Springboard in the tearDown phase. Although, this does require use of a private header from XCTest. (Header dump is available from Facebook’s WebDriverAgent here.)

Here is some sample code from a Springboard class to delete an app from Springboard via tap and hold:

#Swift 4:

import XCTest

class Springboard {

    static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
    
    /**
     Terminate and delete the app via springboard
     */
    class func deleteMyApp() {
        XCUIApplication().terminate()
        
         // Force delete the app from the springboard
        let icon = springboard.icons["Citizen"]
        if icon.exists {
            let iconFrame = icon.frame
            let springboardFrame = springboard.frame
            icon.press(forDuration: 1.3)
        
            // Tap the little "X" button at approximately where it is. The X is not exposed directly
            springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX, dy: (iconFrame.minY + 3) / springboardFrame.maxY)).tap()
        
            springboard.alerts.buttons["Delete"].tap()
        }
    }
 }

#Swift 3-:

import XCTest

class Springboard {

    static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")
    
    /**
     Terminate and delete the app via springboard
     */
    class func deleteMyApp() {
        XCUIApplication().terminate()
        
        // Resolve the query for the springboard rather than launching it
        springboard.resolve()
        
        // Force delete the app from the springboard
        let icon = springboard.icons["MyAppName"]
        if icon.exists {
            let iconFrame = icon.frame
            let springboardFrame = springboard.frame
            icon.pressForDuration(1.3)
            
            if #available(iOS 13.0, *) {
                springboard.buttons["Remove App"].tap()
                springboard.alerts.buttons["Delete App"].tap()
                springboard.alerts.buttons["Delete"].tap()
            } else {
            
                // Tap the little "X" button at approximately where it is. The X is not exposed directly
                let xPosition = CGVector(dx: (iconFrame.minX + 3) / springboardFrame.maxX,
                                     dy: (iconFrame.minY + 3) / springboardFrame.maxY)
                springboard.coordinate(withNormalizedOffset: xPosition).tap()
                springboard.alerts.buttons["Delete"].tap()
            }
        }
    }
 }

And then:

override func tearDown() {
    Springboard.deleteMyApp()
    super.tearDown()
}

The private headers were imported in the Swift bridging header. You’ll need to import:

// Private headers from XCTest
#import "XCUIApplication.h"
#import "XCUIElement.h"

Note: As of Xcode 10, XCUIApplication(bundleIdentifier:) is now exposed by Apple, and the private headers are no longer needed.

Leave a Comment