有没有办法在Swift XCTest UI中的测试之间重置应用程序?

Las*_*awk 65 xctest swift xcode7 xcode-ui-testing

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

或者有没有办法在Swift中调用shell脚本?然后我可以调用xcrun中间的测试方法来重置模拟器.

Cha*_*and 71

您可以添加"运行脚本"阶段来构建测试目标中的阶段,以便在针对它运行单元测试之前卸载应用程序,但不幸的是,这不是在测试用例之间.

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

更新


在测试之间,您可以在tearDown阶段通过Springboard 删除应用程序.虽然,这确实需要使用XCTest的私有头.(此处可从Facebook的WebDriverAgent获取标头转储.)

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

斯威夫特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()
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

斯威夫特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)

            // 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()
        }
    }
 }
Run Code Online (Sandbox Code Playgroud)

然后:

override func tearDown() {
    Springboard.deleteMyApp()
    super.tearDown()
}
Run Code Online (Sandbox Code Playgroud)

私有标头是在Swift桥接头中导入的.你需要导入:

// Private headers from XCTest
#import "XCUIApplication.h"
#import "XCUIElement.h"
Run Code Online (Sandbox Code Playgroud)

注意:从Xcode 10开始,XCUIApplication(bundleIdentifier:)Apple现在公开了,不再需要私有标头.

  • 从 iOS 13.4 开始,当我在 `Springboard.deleteMyApp()` 之后调用 `app.launch()` 时,出现错误:`请求被服务委托 (SBMainWorkspace) 拒绝,原因是:NotFound ("Application "com .serpentisei.studyjapanese“对于 FrontBoard 来说是未知的”)。` (7认同)
  • 上述错误似乎是在 Xcode 11.4 中引入的,无论模拟器 iOS 版本如何。每当您启动应用程序、使用上述技术删除应用程序,然后尝试再次启动它时(即使这是跨单独的测试),都会发生这种情况。我已提交 FB7666257。 (3认同)
  • 很棒的答案!获得"MyAppName"有更聪明的方法吗?我尝试使用`NSBundle-bundleWithIdentifier/Path`,但测试应用程序没有对应用程序包的引用.我的项目有很多目标,每个目标都有不同的名称,我希望能够在所有目标中使用Springboard类. (2认同)
  • 小小的“ x”按钮的可访问性标识符为“ DeleteButton”,可以通过长按后运行“ icon.buttons [“ DeleteButton”]。tap()”来轻按,而不是使用“ CGVector”。 (2认同)
  • 无论如何,Apple 回复了我的反馈请求,并确认该问题现已在最新版本的 Xcode 中得到修复。 (2认同)

l -*_*c l 36

此时,Xcode 7&8中的公共API和模拟器没有出现任何可从模拟器的"重置内容和设置" 调用setUp()tearDown() XCText子类的方法.

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

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

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

macos> xcrun simctl --help
# can uninstall a single application
macos> xcrun simctl uninstall --help  
# Usage: simctl uninstall <device> <app identifier>
Run Code Online (Sandbox Code Playgroud)
  1. Xcode架构动作.添加xcrun simctl erase all(或xcrun simctl erase <DEVICE_UUID>)或类似于Scheme Test部分.选择产品>方案>编辑方案...菜单.展开Scheme Test部分.在"测试"部分下选择"预执行".单击(+)添加"新建运行脚本操作".该命令xcrun simctl erase all可以直接输入,无需任何外部脚本.

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

A. 应用程序UI.[UI测试]提供重置按钮或其他重置应用程序的UI操作.UI元素可通过行使XCUIApplicationXCTest例程setUp(),tearDown()testSomething().

B. 发射参数.[UI测试]正如Victor Ronin所说,可以从测试中传递一个论点setUp()......

class AppResetUITests: XCTestCase {

  override func setUp() {
    // ...
    let app = XCUIApplication()
    app.launchArguments = ["MY_UI_TEST_MODE"]
    app.launch()
Run Code Online (Sandbox Code Playgroud)

......由接收AppDelegate...

class AppDelegate: UIResponder, UIApplicationDelegate {

  func application( …didFinishLaunchingWithOptions… ) -> Bool {
    // ...
    let args = NSProcessInfo.processInfo().arguments
    if args.contains("MY_UI_TEST_MODE") {
      myResetApplication()
    }
Run Code Online (Sandbox Code Playgroud)

C. Xcode方案参数.[UI测试,单元测试] 选择产品>方案>编辑方案...菜单.展开"方案运行"部分.(+)添加一些参数MY_UI_TEST_MODE.该参数将在NSProcessInfo.processInfo().

// ... in application
let args = NSProcessInfo.processInfo().arguments
if args.contains("MY_UI_TEST_MODE") {
    myResetApplication()
}
Run Code Online (Sandbox Code Playgroud)

Z. 直接电话.[单元测试]单元测试包被注入到正在运行的应用程序中,可以直接调用myResetApplication()应用程序中的一些例程.警告:在加载主屏幕后运行默认单元测试.请参阅测试加载顺序但是,UI测试包作为被测应用程序外部的进程运行.因此,单元测试中有效的方法会在UI测试中出现链接错误.

class AppResetUnitTests: XCTestCase {

  override func setUp() {
    // ... Unit Test: runs.  UI Test: link error.
    myResetApplication() // visible code implemented in application
Run Code Online (Sandbox Code Playgroud)


Jus*_*inM 15

更新了swift 3.1/xcode 8.3

在测试目标中创建桥接头:

#import <XCTest/XCUIApplication.h>
#import <XCTest/XCUIElement.h>

@interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
@end
Run Code Online (Sandbox Code Playgroud)

更新了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()
      }
     }
    }
   }
Run Code Online (Sandbox Code Playgroud)


小智 12

适用于 iOS 13.2 的解决方案

final class Springboard {

    private static var springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")

    class func deleteApp(name: String) {
        XCUIApplication().terminate()

        springboardApp.activate()

        sleep(1)

        let appIcon = springboardApp.icons.matching(identifier: name).firstMatch
        appIcon.press(forDuration: 1.3)

        sleep(1)

        springboardApp.buttons["Delete App"].tap()

        let deleteButton = springboardApp.alerts.buttons["Delete"].firstMatch
        if deleteButton.waitForExistence(timeout: 5) {
            deleteButton.tap()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Vic*_*nin 10

您可以要求您的应用程序"清理"自己

  • XCUIApplication.launchArguments用来设置一些标志
  • 在AppDelegate中,您可以查看

    如果NSProcessInfo.processInfo().arguments.contains("YOUR_FLAG_NAME_HERE"){//在此处清理}


Dee*_*See 9

我使用@ ODM 答案,但修改它以适用于Swift 4.注意:一些S/O答案没有区分Swift版本,这有时会有相当的根本差异.我已经在iPhone 7模拟器和iPad Air模拟器上进行了纵向测试,它适用于我的应用程序.

斯威夫特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()
        }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我不得不进一步改变这一点,因为它因缩放变化而无法在"Plus"型号手机上使用.如果用"3*UIScreen.main.scale"替换常量"3",那么它可以正常工作. (3认同)

Ole*_*ets 8

从 Xcode 11.4 开始,如果您只想重置权限,则可以resetAuthorizationStatus(for:)在 实例上使用XCUIApplication,请参阅 https://developer.apple.com/documentation/xctest/xcuiapplication/3526066-resetauthorizationstatusforresou

simctl如果需要,您也可以使用,引用自Xcode 11.4 发行说明

simctl 现在支持修改隐私权限。您可以修改隐私权限以创建已知状态以进行测试。例如,允许示例应用程序在没有任何提示的情况下访问照片库:
xcrun simctl privacy <device> grant photos com.example.app

要将所有权限重置为默认值,因为如果应用程序以前从未被安装:
xcrun simctl privacy <device> reset all com.example.app

  • 令人烦恼的是,这似乎不适用于通知权限。 (4认同)

odm*_*odm 7

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

import XCTest

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"]
        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()

            // Press home once make the icons stop wiggling
            XCUIDevice.sharedDevice().pressButton(.Home)
            // Press home again to go to the first page of the springboard
            XCUIDevice.sharedDevice().pressButton(.Home)
            // Wait some time for the animation end
            NSThread.sleepForTimeInterval(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()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


vme*_*yer 7

我看到很多关于在测试中setUptearDown测试中卸载应用程序的答案。

但是,您可以通过在测试目标中添加运行脚本阶段,在启动测试之前轻松卸载应用程序。

这样做:

  1. 选择您的应用程序的 Xcode 项目
  2. 选择您的测试目标
  3. 选择“构建阶段”
  4. 点击“+”和“新运行脚本阶段”

然后,用# Type a script or drag a script file from your workspace to insert its path.命令替换占位符:

xcrun simctl boot ${TARGET_DEVICE_IDENTIFIER}
xcrun simctl uninstall ${TARGET_DEVICE_IDENTIFIER} YOUR_APP_BUNDLE
Run Code Online (Sandbox Code Playgroud)


Luc*_*uca 7

iOS14 的工作解决方案

final class Springboard {

    private static var springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")

    class func deleteApp(name: String) {
        XCUIApplication().terminate()

        springboardApp.activate()

        sleep(1)

        let appIcon = springboardApp.icons.matching(identifier: name).firstMatch
        appIcon.press(forDuration: 1.3)

        sleep(1)

        springboardApp.buttons["Remove App"].tap()

        let deleteButton = springboardApp.alerts.buttons["Delete App"].firstMatch
        if deleteButton.waitForExistence(timeout: 5) {
            deleteButton.tap()
            springboardApp.alerts.buttons["Delete"].tap()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


emi*_*min 5

答案有很多变体,甚至不确定我是否应该补充这一点,但以防万一有人需要一个通用的解决方案:

iOS 14.6 和 15 测试版

    class func deleteApp() {
    XCUIApplication().terminate()
    
    // Force delete the app from the springboard
    let icon = springboard.icons["APP_NAME"]
    if icon.exists {
        icon.press(forDuration: 1.3)
        
        springboard.buttons["Remove App"].tap()
        springboard.alerts.buttons["Delete App"].tap()
        springboard.alerts.buttons["Delete"].tap()
        
        // Press home once to make the icons stop wiggling
        XCUIDevice.shared.press(.home)
    }
}
Run Code Online (Sandbox Code Playgroud)