有没有办法在Xcode 7中的Swift XCTest UI中的testing之间重置应用程序?

在XCTest中是否有API调用,我可以放入setUP()或tearDown()来重置testing之间的应用程序? 我查看了XCUIApplication的点语法,我只看到了.launch()

OR有没有办法在Swift中调用shell脚本? 然后我可以调用xcrun中间的testing方法来重置模拟器。

您可以添加一个“运行脚本”阶段在testing目标中构build阶段,然后在运行unit testing之前卸载应用程序,但不幸的是这不在testing用例之间

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

更新


在testing之间,你可以在tearDown阶段通过Springboard 删除应用程序 。 虽然,这确实需要使用XCTest的私有头文件。 ( 在这里可以从Facebook的WebDriverAgent获得标题转储。)

以下是Springboard类的一些示例代码,通过点击并按住从Springboard删除应用程序:

 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) // 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() } } } 

接着:

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

私人头文件被导入Swift桥头。 您需要导入:

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

此时,Xcode 7&8和Simulator中的公共API没有任何方法可以从setUp()tearDown() XCText子类调用到模拟器的“Reset Contents and Settings”。

还有其他可能的方法使用公共API:

  1. 应用程序代码 添加一些myResetApplication()应用程序代码,使应用程序处于已知状态。 但是,设备(模拟器)状态控制受应用程序沙箱的限制,这在应用程序之外帮助不大。 这种方法对于清除应用程序可控持久性是可以的。

  2. Shell脚本 。 从shell脚本运行testing。 使用xcrun simctl erase allxcrun simctl uninstall <device> <app identifier>或每次testing运行之间的类似操作来重置模拟器(或卸载应用程序) 。 请参阅StackOverflow:“如何从命令行重置iOS模拟器?”

  macos> xcrun simctl --help # can uninstall a single application macos> xcrun simctl uninstall --help # Usage: simctl uninstall <device> <app identifier> 
  1. Xcode架构操作 。 添加xcrun simctl erase all (或xcrun simctl erase <DEVICE_UUID> )或类似的schemetesting部分。 select产品>计划>编辑计划…菜单。 展开计划testing部分。 在Test部分下selectPre-actions。 点击(+)添加“新build脚本操作”。 命令xcrun simctl erase all可以直接input而不需要任何外部脚本。

调用选项1.应用程序代码重置应用程序:

A. 应用程序UI[UItesting]提供重置button或其他UI操作,以重置应用程序。 UI元素可以通过XCUIApplication例程setUp()tearDown()testSomething()中的XCTest来执行。

B. 启动参数[UItesting]正如Victor Ronin指出的,一个参数可以从testingsetUp()传递…

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

…被AppDelegate收到…

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

C. Xcodescheme参数[用户界面testing,unit testing]select产品>scheme>编辑scheme…菜单。 展开Scheme Run部分。 (+)添加一些像MY_UI_TEST_MODE参数。 该参数将在NSProcessInfo.processInfo()可用。

 // ... in application let args = NSProcessInfo.processInfo().arguments if args.contains("MY_UI_TEST_MODE") { myResetApplication() } 

直接电话[unit testing]unit testing包被注入正在运行的应用程序,并可以直接调用应用程序中的一些myResetApplication()例程。 警告:默认unit testing在主屏幕加载后运行。 请参阅testing加载顺序但是,UItesting包作为被testing应用程序的外部进程运行。 所以,unit testing中的工作在UItesting中会出现链接错误。

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

更新为swift 3.1 / xcode 8.3

在testing目标中创build桥接头:

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

更新了Springboard类

 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() } } } } 

你可以问你的应用程序来“清理”自己

  • 您使用XCUIApplication.launchArguments来设置一些标志
  • 在AppDelegate你检查

    如果NSProcessInfo.processInfo()。arguments.contains(“YOUR_FLAG_NAME_HERE”){//做一个清理}

我使用了@Chase Holland 答案,并使用相同的方法更新了Springboard类,以使用“设置”应用重置内容和设置。 这在您需要重置权限对话框时非常有用。

导入XCTest

 class Springboard {
     static let springboard = XCUIApplication(privateWithPath:nil,bundleID:“com.apple.springboard”)
     static let settings = XCUIApplication(privateWithPath:nil,bundleID:“com.apple.Preferences”)

     / **
     通过跳板终止并删除应用程序
      * /
     class func deleteMyApp(){
         XCUIApplication()。结束()

         //parsing跳板的查询,而不是启动它
         springboard.resolve()

         //从跳板强制删除应用程序
        让图标= springboard.icons [“MyAppName”]
        如果icon.exists {
            让iconFrame = icon.frame
            让springboardFrame = springboard.frame
             icon.pressForDuration(1.3)

             //点击大约在哪里的小“X”button。  X不直接暴露
             ()(iconFrame.minX + 3)/ springboardFrame.maxX,(iconFrame.minY + 3)/ springboardFrame.maxY))。tap()

             springboard.alerts.buttons [ “删除”]。轻按()

             //按回家一次使图标停止摇摆
             XCUIDevice.sharedDevice()。轻触式(。家里)
             //再按回家跳到跳板的第一页
             XCUIDevice.sharedDevice()。轻触式(。家里)
             //等一段时间animation结束
             NSThread.sleepForTimeInterval(0.5)

            让settingsIcon = springboard.icons [“设置”]
            如果settingsIcon.exists {
                 settingsIcon.tap()
                 settings.tables.staticTexts [ “常规”]。轻按()
                 settings.tables.staticTexts [ “复位”]。轻按()
                 settings.tables.staticTexts [“Reset Location&Privacy”]。tap()
                 settings.buttons [“Reset Warnings”]。tap()
                 settings.terminate()
             }
         }
     }
 }

我使用@ ODM 答案 ,但修改它为Swift 4工作。注意:一些S / O答案不区分Swift版本,有时有相当根本的区别。 我已经在iPhone 7模拟器和iPad Air模拟器上进行了纵向testing,并且适用于我的应用程序。

斯威夫特4

 import XCTest import Foundation class Springboard { let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let settings = XCUIApplication(bundleIdentifier: "com.apple.Preferences") /** Terminate and delete the app via springboard */ func deleteMyApp() { XCUIApplication().terminate() // Resolve the query for the springboard rather than launching it springboard.activate() // Rotate back to Portrait, just to ensure repeatability here XCUIDevice.shared.orientation = UIDeviceOrientation.portrait // Sleep to let the device finish its rotation animation, if it needed rotating sleep(2) // Force delete the app from the springboard // Handle iOS 11 iPad 'duplication' of icons (one nested under "Home screen icons" and the other nested under "Multitasking Dock" let icon = springboard.otherElements["Home screen icons"].scrollViews.otherElements.icons["YourAppName"] if icon.exists { let iconFrame = icon.frame let springboardFrame = springboard.frame icon.press(forDuration: 2.5) // 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() // Wait some time for the animation end Thread.sleep(forTimeInterval: 0.5) //springboard.alerts.buttons["Delete"].firstMatch.tap() springboard.buttons["Delete"].firstMatch.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) // Handle iOS 11 iPad 'duplication' of icons (one nested under "Home screen icons" and the other nested under "Multitasking Dock" let settingsIcon = springboard.otherElements["Home screen icons"].scrollViews.otherElements.icons["Settings"] if settingsIcon.exists { settingsIcon.tap() settings.tables.staticTexts["General"].tap() settings.tables.staticTexts["Reset"].tap() settings.tables.staticTexts["Reset Location & Privacy"].tap() // Handle iOS 11 iPad difference in error button text if UIDevice.current.userInterfaceIdiom == .pad { settings.buttons["Reset"].tap() } else { settings.buttons["Reset Warnings"].tap() } settings.terminate() } } } }