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

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 XCTestclass 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 XCTestclass 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)            // Tap the little "X" button at approximately where it is. The X is not exposed directly            springboard.coordinateWithNormalizedOffset(CGVectorMake((iconFrame.minX + 3) / springboardFrame.maxX, (iconFrame.minY + 3) / springboardFrame.maxY)).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.


At this time, the public API in Xcode and the Simulator does not appear have any method callable from setUp() and tearDown() XCText subclasses to "Reset Contents and Settings" for the simulator.

There are other possible approaches which use public APIs:

  1. Application Code. Add some myResetApplication() application code to put the application in a known state. However, device (simulator) state control is limited by the application sandbox ... which is not much help outside the application. This approach is OK for clearing application controllable persistance.

  2. Shell Script. Run the tests from a shell script. Use xcrun simctl erase all or xcrun simctl uninstall <device> <app identifier> or similar between each test run to reset the simulator (or uninstall the app). see StackOverflow: "How can I reset the iOS Simulator from the command line?"

xcrun simctl --help# Uninstall a single applicationxcrun simctl uninstall --help  xcrun simctl uninstall <device> <app identifier># Erase a device's contents and settings.xcrun simctl erase <device>xcrun simctl erase all      # all existing devices# Grant, revoke, or reset privacy and permissionssimctl privacy <device> <action> <service> [<bundle identifier>]
  1. Xcode Schema Script Action. Add xcrun simctl erase all (or xcrun simctl erase <DEVICE_UUID>) or similar commands to an Xcode Scheme section such as the Test or Build section. Select the Product > Scheme > Edit Scheme… menu. Expand the Scheme Test section. Select Pre-actions under the Test section. Click (+) add "New Run Script Action". The command xcrun simctl erase all can be typed in directly without requiring any external script.

Options for invoking 1. Application Code to reset the application:

A. Application UI. [UI Test] Provide a reset button or other UI action which resets the application. The UI element can be exercised via XCUIApplication in XCTest routines setUp(), tearDown() or testSomething().

B. Launch Parameter. [UI Test] As noted by Victor Ronin, an argument can be passed from the test setUp() ...

class AppResetUITests: XCTestCase {  override func setUp() {    // ...    let app = XCUIApplication()    app.launchArguments = ["MY_UI_TEST_MODE"]    app.launch()

... to be received by the AppDelegate ...

class AppDelegate: UIResponder, UIApplicationDelegate {  func application( didFinishLaunchingWithOptions ) -> Bool {    // ...    let args = ProcessInfo.processInfo.arguments    if args.contains("MY_UI_TEST_MODE") {      myResetApplication()    }

C. Xcode Scheme Parameter. [UI Test, Unit Test] Select the Product > Scheme > Edit Scheme… menu. Expand the Scheme Run section. (+) Add some parameter like MY_UI_TEST_MODE. The parameter will be available in ProcessInfo.processInfo.

// ... in applicationlet args = ProcessInfo.processInfo.argumentsif args.contains("MY_UI_TEST_MODE") {    myResetApplication()}

Z. Direct Call. [Unit Test] Unit Test Bundles are injected into the running application and can directly call some myResetApplication() routine in the application. Caveat: Default unit tests run after the main screen has loaded. see Test Load Sequence However, UI Test Bundles runs as a process external to the application under test. So, what works in the Unit Test gives a link error in a UI Test.

class AppResetUnitTests: XCTestCase {  override func setUp() {    // ... Unit Test: runs.  UI Test: link error.    myResetApplication() // visible code implemented in application


Updated for swift 3.1 / xcode 8.3

create bridging header in test target:

#import <XCTest/XCUIApplication.h>#import <XCTest/XCUIElement.h>@interface XCUIApplication (Private)- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;- (void)resolve;@end

updated Springboard class

class Springboard {   static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!   static let settings = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.Preferences")!/**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}"] /// change to correct app name   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()     // Press home once make the icons stop wiggling     XCUIDevice.shared().press(.home)     // Press home again to go to the first page of the springboard     XCUIDevice.shared().press(.home)     // Wait some time for the animation end     Thread.sleep(forTimeInterval: 0.5)      let settingsIcon = springboard.icons["Settings"]      if settingsIcon.exists {       settingsIcon.tap()       settings.tables.staticTexts["General"].tap()       settings.tables.staticTexts["Reset"].tap()       settings.tables.staticTexts["Reset Location & Privacy"].tap()       settings.buttons["Reset Warnings"].tap()       settings.terminate()      }     }    }   }