如何使用 XCTest delete/reset 来自 iOS 13 的应用程序?

How to delete/reset an app from iOS 13 with XCTest?

最近我开始使用 XCTest 测试一个 iOS 应用程序,但我发现了一些困难,主要困难是在每个测试中删除或重置应用程序内容 class。

我目前正在使用 XCode 11 并尝试 delete/reset 来自 iOS 13 的应用程序用于每个测试 class,我已经尝试过:

这一步在我的测试中非常重要,因为在每次测试中我都需要创建配置文件并登录,所以在下一次测试中我需要从头开始安装应用程序

尝试按应用程序图标的时间比以前的 iOS 版本长一点。

    let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

    func deleteMyApp() {
        XCUIApplication().terminate()

        let icon = springboard.icons["YourAppName"]
        if icon.exists {
            let iconFrame = icon.frame
            let springboardFrame = springboard.frame
            icon.press(forDuration: 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()

            springboard.alerts.buttons["Delete"].tap()
        }
    }
enum Springboard {

    static let springboardApp = XCUIApplication(bundleIdentifier: "com.apple.springboard")
    static let appName = "appName"

    static func deleteApp() {
        XCUIApplication().terminate()

        let icon = springboardApp.icons[appName]
        if icon.exists {
            icon.press(forDuration: 3)
            icon.buttons["DeleteButton"].tap()

            springboardApp.alerts.buttons["Delete"].tap()
        }
    }
 }

更新了 Roman 在 iPad.

上支持 iOS 13 的回答

通过插入 otherElements["Home screen icons"],代码适用于 iPhone 和 iPad。

class Springboard {

    static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

    class func deleteMyApp() {
        XCUIApplication().terminate()

         // Insert otherElements["Home screen icons"] here
        let icon = springboard.otherElements["Home screen icons"].icons["Workoutimer"]
        if icon.exists {
            let iconFrame = icon.frame
            let springboardFrame = springboard.frame
            icon.press(forDuration: 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()

            springboard.alerts.buttons["Delete"].tap()
            
            XCUIDevice.shared.press(XCUIDevice.Button.home)
        }
    }
 }

其他

iPad,“最近启动的应用程序”将添加到 Dock 的右侧。如果您仅按标识符搜索图标,此 iPad 特定行为会导致 XCUITest 找到两个元素。

Your app will be added here, too.

要处理该问题,请指定 otherElements["Home screen icons"] 并排除“Dock”的元素。

这是 iOS 14 的快速而肮脏的解决方案:

let appName = "You app name as it appears on the home screen"

// Put the app in the background
XCUIDevice.shared.press(XCUIDevice.Button.home)

let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
if springboard.icons[appName].waitForExistence(timeout: 5) {
    springboard.icons[appName].press(forDuration: 1.5);
}

if springboard.collectionViews.buttons["Remove App"].waitForExistence(timeout: 5) {
    springboard.collectionViews.buttons["Remove App"].tap()
}

if springboard.alerts["Remove “\(appName)”?"].scrollViews.otherElements.buttons["Delete App"].waitForExistence(timeout: 5) {
    springboard.alerts["Remove “\(appName)”?"].scrollViews.otherElements.buttons["Delete App"].tap()
}

if springboard.alerts["Delete “\(appName)”?"].scrollViews.otherElements.buttons["Delete"].waitForExistence(timeout: 5) {
    springboard.alerts["Delete “\(appName)”?"].scrollViews.otherElements.buttons["Delete"].tap()
}

iOS14

iOS 14

的工作解决方案
import XCTest

let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

func deleteMyApp() {
    XCUIApplication().terminate()

    let bundleDisplayName = "MyApp"

    let icon = springboard.icons[bundleDisplayName]
    if icon.exists {
        icon.press(forDuration: 1)

        let buttonRemoveApp = springboard.buttons["Remove App"]
        if buttonRemoveApp.waitForExistence(timeout: 5) {
            buttonRemoveApp.tap()
        } else {
            XCTFail("Button \"Remove App\" not found")
        }

        let buttonDeleteApp = springboard.alerts.buttons["Delete App"]
        if buttonDeleteApp.waitForExistence(timeout: 5) {
            buttonDeleteApp.tap()
        }
        else {
            XCTFail("Button \"Delete App\" not found")
        }

        let buttonDelete = springboard.alerts.buttons["Delete"]
        if buttonDelete.waitForExistence(timeout: 5) {
            buttonDelete.tap()
        }
        else {
            XCTFail("Button \"Delete\" not found")
        }
    }
}

class HomeUITests: XCTestCase {
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        deleteMyApp()

        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use recording to get started writing UI tests.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTApplicationLaunchMetric()]) {
                XCUIApplication().launch()
            }
        }
    }
}

iOS 14岁及以下

func deleteMyApp() {
XCUIApplication().terminate()

let bundleDisplayName = "App Name"

XCUIDevice.shared.press(.home)

let icon = springboard.icons[bundleDisplayName]

if icon.waitForExistence(timeout: 5) {
    
    XCUIDevice.shared.press(.home)

    
    let value = springboard.pageIndicators.element(boundBy: 0).value as? String
    
    let f = value!.last?.wholeNumberValue
    for _ in (1...f!){
        if(icon.isHittable){
            break
        }
        else{
            springboard.swipeLeft()
        }
    }


    let systemVersion = UIDevice.current.systemVersion.prefix(2)

    switch systemVersion {
    case "13":
        icon.press(forDuration: 1)

        let rrd = springboard.buttons["Delete App"]
        XCTAssert(rrd.waitForExistence(timeout: 5))
        rrd.tap()
        let rrd2 = springboard.buttons["Delete"]
        XCTAssert(rrd2.waitForExistence(timeout: 5))
        rrd2.tap()
        
    case "14":
        icon.press(forDuration: 1)

        let rrd = springboard.buttons["Remove App"]
        XCTAssert(rrd.waitForExistence(timeout: 5))
        rrd.tap()
        let rrd2 = springboard.buttons["Delete App"]
        XCTAssert(rrd2.waitForExistence(timeout: 5))
        rrd2.tap()
        let rrd3 = springboard.buttons["Delete"]
        XCTAssert(rrd3.waitForExistence(timeout: 5))
        rrd3.tap()
        
    default:
        XCTFail("Did not handle")
    }
   
}}

您可以简单地通过跳板执行此操作,就像您自己卸载构建一样。 这里是 class 带有 deleteApp 功能,它会在你需要的时候随时卸载构建。

 class Springboard {
    
    static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
    
    class func deleteApp () {
        //terminate app and activate Springboard
        XCUIApplication().terminate()
        springboard.activate()
        
        //tap on app icon
        let appIcon = springboard.icons.matching(identifier: "App Display Name").firstMatch
        
        if appIcon.exists {
            
        appIcon.press(forDuration: 2.0)
            
           //Access first alert button (Remove App)
        let _ = springboard.alerts.buttons["Remove App"].waitForExistence(timeout: 1.0)
        springboard.buttons["Remove App"].tap()
        
        //Access second alert button (Delete App)
        let _ = springboard.alerts.buttons["Delete App"].waitForExistence(timeout: 1.0)
        springboard.buttons["Delete App"].tap()
        
        //Access second alert button (Delete)
        let _ = springboard.alerts.buttons["Delete"].waitForExistence(timeout: 1.0)
        springboard.buttons["Delete"].tap()

        }
    }
}

乌斯盖:

 func test_YourTestName() {
    Springboard.deleteApp()
}