Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Is there an API call within XCTest that I can put into the setUP() or tearDown() to reset the app between tests? I looked in the dot syntax of XCUIApplication and all I saw was the .launch()

OR is there a way to call a shell script in Swift? I could then call xcrun in-between test methods to reset the simulator.

like image 308
Laser Hawk Avatar asked Oct 13 '15 16:10

Laser Hawk


People also ask

What is the difference between XCTest and XCUITest?

Tests are run directly from Xcode and are written with either Swift or Objective C. XCUITest makes use of the application's accessibility functionality, which allows tests to interact with the app as a real user would.

Is XCTest a unit testing framework?

Use the XCTest framework to write unit tests for your Xcode projects that integrate seamlessly with Xcode's testing workflow.

How do I test my SwiftUI app?

To test SwiftUI applications, don't test SwiftUI code. The app should say "Hello, <user> !" when given a user and fallback to "Hello, world!" otherwise.


1 Answers

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.

like image 168
Chase Holland Avatar answered Oct 12 '22 15:10

Chase Holland