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

7

10

Recently I started testing an iOS app using XCTest but I found some difficulties, the main difficulty was deleting or resetting the app content in each test class.

I'm currently using XCode 11 and trying to delete/reset an app from iOS 13 for each test class, I've already tried:

  • Delete app through springboard
  • Delete app by going to the app settings

This step is really important in my tests because in each test I need to create a profile and log in, so in the next test I need to have the app just installed from scratch

Mashburn answered 22/10, 2019 at 17:18 Comment(0)
T
3

Try to press the app icon a little longer than in previous iOS versions.

    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()
        }
    }
Territerrible answered 22/10, 2019 at 17:58 Comment(5)
This worked perfectly for all iOS versions. Thank you :)Mashburn
I'm glad it helped you. Good luck!Territerrible
The app icon is not on the first screen, seems we need firstly swipe left?Manufacturer
this is not working in iOS 16 real device.Wain
In the later versions of iOS, there are two consecutive dialogs: first one with the button "Delete App," the other one with the button "Delete." See newer answers below.Azotobacter
I
12

iOS 14

The working solution for 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()
            }
        }
    }
}
Incretion answered 12/3, 2021 at 10:27 Comment(0)
T
3

Try to press the app icon a little longer than in previous iOS versions.

    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()
        }
    }
Territerrible answered 22/10, 2019 at 17:58 Comment(5)
This worked perfectly for all iOS versions. Thank you :)Mashburn
I'm glad it helped you. Good luck!Territerrible
The app icon is not on the first screen, seems we need firstly swipe left?Manufacturer
this is not working in iOS 16 real device.Wain
In the later versions of iOS, there are two consecutive dialogs: first one with the button "Delete App," the other one with the button "Delete." See newer answers below.Azotobacter
S
2

Updated Roman's answer to support iOS 13 on iPad.

By inserting otherElements["Home screen icons"], the code works both of iPhone and 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)
        }
    }
 }

Misc.

On iPad, "recent launched app" will be added to the right side of the Dock. This iPad specific behavior leads XCUITest finds two elements if you search icons only by its identifier.

Your app will be added here, too.

To handle that, specify otherElements["Home screen icons"] and it excludes elements of the "Dock".

Stormystorting answered 30/8, 2020 at 10:58 Comment(0)
S
1
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()
        }
    }
 }
Story answered 30/7, 2020 at 8:45 Comment(0)
C
1

you can simply do this via springboard same as you uninstall build by yourself. Here is the class with deleteApp function which will uninstall build at anytime you need.

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

        }
    }
}

Usgae:

 func test_YourTestName() {
    Springboard.deleteApp()
}
Correspondent answered 13/4, 2022 at 6:12 Comment(1)
Doesn't seem to work. The method doesn't tap "Remove App" for a reason, even though it visibly appears on the screen.Azotobacter
B
0

This is a quick and dirty solution for 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()
}
Bestow answered 25/1, 2021 at 18:51 Comment(1)
After pressing home button, the device goes to first screen. However, the testing app is on second screen, springboard.icons[appName] would be unhittable, which means cannot be pressed.Manufacturer
C
-1

iOS 14 and below

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")
    }
   
}}
Charmian answered 7/7, 2021 at 20:13 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.