source

Swift XCTest UI에서 테스트 사이에 앱을 재설정할 수 있는 방법이 있습니까?

manycodes 2023. 11. 2. 21:55
반응형

Swift XCTest UI에서 테스트 사이에 앱을 재설정할 수 있는 방법이 있습니까?

테스트 사이에 앱을 재설정하기 위해 setUP() 또는 tearDown()에 넣을 수 있는 XCT 테스트 내 API 호출이 있습니까?저는 XCUIA Application의 dot 구문을 살펴보았는데 .launch()만 보였습니다.

아니면 스위프트에서 셸 스크립트를 호출하는 방법이 있습니까?그러면 xcrun in-between test methods를 호출하여 시뮬레이터를 리셋할 수 있습니다.

"스크립트 실행" 단계를 추가하여 테스트 대상에 단계를 만들어 앱에 대한 단위 테스트를 실행하기 전에 앱을 제거할 수 있습니다. 하지만 안타깝게도 이 단계는 테스트 사례 사이에 있는 것은 아닙니다.

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

#스위프트 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()
            }
        }
    }
 }

그 다음은:

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

개인 헤더를 Swift bridging 헤더로 가져왔습니다.다음을 가져와야 합니다.

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

참고: Xcode 10 기준,XCUIApplication(bundleIdentifier:)이제 Apple에 의해 노출되므로 개인 헤더가 더 이상 필요하지 않습니다.

현재 Xcode, Simulator 및 Swift Package Manager에서 제공하는 공개 API는 호출할 수 있는 방법이 없습니다.setUp()그리고.tearDown() XCTextsubclasses to "Reset Contents and Settings"(시뮬레이터 내용 및 설정 재설정)으로 이동합니다.

공용 API를 사용하는 다른 가능한 접근 방식이 있습니다.

  1. 응용 프로그램 코드.추가하기myResetApplication()응용 프로그램을 알려진 상태로 만들기 위한 응용 프로그램 코드입니다.그러나 장치(시뮬레이터) 상태 제어는 애플리케이션 샌드박스에 의해 제한됩니다...응용 프로그램 밖에서는 큰 도움이 되지 않습니다.이 방법은 응용프로그램 제어 가능한 저항을 제거하는 데 적합합니다.

  2. 셸 스크립트.셸 스크립트에서 테스트를 실행합니다.xcrun simctl erase all아니면xcrun simctl uninstall <device> <app identifier>또는 시뮬레이터를 재설정(또는 제거)하기 위한 각 테스트 실행 간에 유사합니다.StackOverflow: "명령줄에서 iOS 시뮬레이터를 재설정하려면 어떻게 해야 합니까?"를 참조하십시오.

xcrun simctl --help
# Uninstall a single application
xcrun simctl uninstall --help  
xcrun simctl uninstall <device> <app identifier>

# Erase a device's contents and settings.
xcrun simctl erase <device>
xcrun simctl erase all      # all existing devices

# Grant, revoke, or reset privacy and permissions
simctl privacy <device> <action> <service> [<bundle identifier>]
  1. Xcode 스키마 스크립트 작업. 추가xcrun simctl erase all(또는xcrun simctl erase <DEVICE_UUID>테스트 또는 빌드 섹션과 같은 Xcode Scheme 섹션과 유사한 명령입니다.Product > Scheme > Edit Scheme … 메뉴를 선택합니다.Scheme Test 섹션을 펼칩니다.테스트 섹션 아래에 있는 Pre-actions(사전 작업)을 선택합니다.(+) "New Run Script Action"을 추가합니다.명령어xcrun simctl erase all외부 스크립트 없이 직접 입력할 수 있습니다.

1을 호출하기 위한 옵션. 응용 프로그램을 재설정하기 위한 응용 프로그램 코드:

가. 어플리케이션 UI [UI Test] 어플리케이션을 리셋하는 리셋 버튼 또는 기타 UI 동작을 제공합니다.UI 요소는 다음을 통해 실행할 수 있습니다.XCUIApplication인에XCTest일상적인setUp(),tearDown()아니면testSomething().

B. 매개변수를 실행합니다.[UI Test] Victor Ronin이 언급한 바와 같이, 그 테스트에서 주장이 통과될 수 있습니다.setUp()...

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 = ProcessInfo.processInfo.arguments
    if args.contains("MY_UI_TEST_MODE") {
      myResetApplication()
    }

C. Xcode Scheme 매개 변수[UI Test, Unit Test] Product > Scheme > Edit Scheme … 메뉴를 선택합니다.구성표 실행 섹션을 펼칩니다. (+) 다음과 같은 매개변수 추가MY_UI_TEST_MODE. 매개 변수는 다음에서 사용할 수 있습니다.ProcessInfo.processInfo.

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

D. 직접 통화.[Unit Test] Unit Test 번들은 실행 중인 어플리케이션에 주입되어 일부를 직접 호출할 수 있습니다.myResetApplication()지원서의 일상적인 작업주의 사항:기본 단위 테스트는 주 화면이 로드된 후에 실행됩니다.테스트 로드 시퀀스를 참조하십시오. 그러나 UI 테스트 번들은 테스트 대상 응용 프로그램 외부의 프로세스로 실행됩니다.따라서 단위 테스트에서 작동하는 것은 UI 테스트에서 링크 오류를 발생시킵니다.

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용으로 업데이트됨

테스트 대상에 브리징 헤더 생성:

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

@interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
@end

업데이트된 스프링보드 클래스

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

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

앱에 스스로 "정리"를 요청할 수 있습니다.

  • 당신이 사용.XCUIApplication.launchArguments깃발을 꽂다

  • AppDelegate에서 다음을 확인합니다.

    NSProcessInfo.processInfo().arguments.에 포함된 경우("YOUR_FLAG_NAME_HERE") { // 여기서 정리하기 }

@ODM 답변을 사용했는데 스위프트 4에서 작동하도록 수정했습니다.NB: 일부 S/O 답변은 Swift 버전을 차별화하지 않으며, 때로는 상당히 근본적인 차이가 있습니다.아이폰7 시뮬레이터와 아이패드 에어 시뮬레이터를 세로 방향으로 테스트해봤는데 제 앱에 잘 맞았습니다.

스위프트 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()
        }
    }
  }
}

@Chase Holland 답변을 사용하고 Settings 앱을 사용하여 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()
            }
        }
    }
}

앱을 제거하기 위한 많은 답변이 있습니다.setUp아니면tearDown당신의 시험에.

그러나 테스트 대상에 실행 스크립트 단계를 추가하여 테스트를 시작하기 전에 앱을 쉽게 제거할 수 있습니다.

이렇게 하려면:

  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

Xcode 11.4부터, 만일 당신이 원하는 것이 권한을 재설정하는 것이라면, 당신은 다음을 사용할 수 있습니다.resetAuthorizationStatus(for:)…을 예로 들어서XCUIApplication, https://developer.apple.com/documentation/xctest/xcuiapplication/3526066-resetauthorizationstatusforresou 를 참조하십시오.

사용할 수도 있습니다.simctl필요한 경우 Xcode 11.4 Release Notes에서 인용:

simctl은 이제 개인 정보 보호 권한 수정을 지원합니다.개인 정보 보호 권한을 수정하여 테스트 목적으로 알려진 상태를 만들 수 있습니다.예를 들어, 예제 앱이 아무런 프롬프트 없이 사진 라이브러리에 액세스할 수 있도록 하려면:
xcrun simctl privacy <device> grant photos com.example.app

앱을 설치한 적이 없는 것처럼 모든 권한을 기본값으로 재설정하려면:
xcrun simctl privacy <device> reset all com.example.app.

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

iOS 11 sims an up의 경우 "x" 아이콘과 @Code Monkey가 제안한 수정 사항을 탭하는 곳을 약간 수정했습니다.수정은 10.3과 11.2 폰 심 모두에서 잘 작동합니다.참고로 swift 3를 사용하고 있습니다.좀 더 쉽게 수정할 수 있는 코드를 복사해서 붙여넣어야겠다고 생각했습니다.:)

import XCTest

class Springboard {

    static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.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["My Test App"]
        if icon.exists {
            let iconFrame = icon.frame
            let springboardFrame = springboard!.frame
            icon.press(forDuration: 1.3)

            springboard!.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + 3 * UIScreen.main.scale) / springboardFrame.maxX, dy: (iconFrame.minY + 3 * UIScreen.main.scale) / springboardFrame.maxY)).tap()

            springboard!.alerts.buttons["Delete"].tap()
        }
    }
}

답변의 종류가 매우 다양하여 추가해야 할지조차 확신할 수 없지만, 누군가가 보편적인 해결책을 필요로 할 경우:

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

이것은 iOS 12.1 & 시뮬레이터에서 나에게 잘 맞는 것 같습니다.

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

    // Force delete the app from the springboard
    let icon = springboard.icons[appName]
    if icon.exists {
        icon.press(forDuration: 2.0)

        icon.buttons["DeleteButton"].tap()
        sleep(2)
        springboard.alerts["Delete “\(appName)”?"].buttons["Delete"].tap()
        sleep(2)

        XCUIDevice.shared.press(.home)
    }
}

iOS 13.1/Swift 5.1 UI 기반 삭제

static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!

class func deleteApp() {
    XCUIApplication().terminate()
    XCUIDevice.shared.press(.home)
    XCUIDevice.shared.press(.home)

    let icon = springboard.icons["YourApplication"]
    if !icon.exists { return }

    springboard.swipeLeft()
    springboard.activate()
    Thread.sleep(forTimeInterval: 1.0)

    icon.press(forDuration: 1.3)
    springboard.buttons["Rearrange Apps"].eventuallyExists().tap()

    icon.buttons["DeleteButton"].eventuallyExists().tap()
    springboard.alerts.buttons["Delete"].eventuallyExists().tap()

    XCUIDevice.shared.press(.home)
    XCUIDevice.shared.press(.home)
}

스위프트 4에 대한 크레이그 피셔의 답변 업데이트 중입니다.iPad용으로 조경용으로 업데이트되었으며, 아마 남은 조경용으로만 작동할 것입니다.

가져오기 XCT 테스트

클래스 스프링보드 {

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

class func deleteMyApp(name: String) {        
    // Force delete the app from the springboard
    let icon = springboard.icons[name]
    if icon.exists {
        let iconFrame = icon.frame
        let springboardFrame = springboard.frame
        icon.press(forDuration: 2.0)

        var portaitOffset = 0.0 as CGFloat
        if XCUIDevice.shared.orientation != .portrait {
            portaitOffset = iconFrame.size.width - 2 * 3 * UIScreen.main.scale
        }

        let coord = springboard.coordinate(withNormalizedOffset: CGVector(dx: (iconFrame.minX + portaitOffset + 3 * UIScreen.main.scale) / springboardFrame.maxX, dy: (iconFrame.minY + 3 * UIScreen.main.scale) / springboardFrame.maxY))
        coord.tap()

        let _ = springboard.alerts.buttons["Delete"].waitForExistence(timeout: 5)
        springboard.alerts.buttons["Delete"].tap()

        XCUIDevice.shared.press(.home)
    }
}

}

앱 삭제 및 경고 재설정을 위한 위 답변의 목표 C 버전은 다음과 같습니다(iOS 11 및 12에서 테스트됨).

- (void)uninstallAppNamed:(NSString *)appName {

    [[[XCUIApplication alloc] init] terminate];

    XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"];
    [springboard activate];
    XCUIElement *icon = springboard.otherElements[@"Home screen icons"].scrollViews.otherElements.icons[appName];

    if (icon.exists) {
        [icon pressForDuration:2.3];
        [icon.buttons[@"DeleteButton"] tap];
        sleep(2);
        [[springboard.alerts firstMatch].buttons[@"Delete"] tap];
        sleep(2);
        [[XCUIDevice sharedDevice] pressButton:XCUIDeviceButtonHome];
        sleep(2);
    }
}

..

- (void)resetWarnings {

    XCUIApplication *settings = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.Preferences"];
    [settings activate];
    sleep(2);
    [settings.tables.staticTexts[@"General"] tap];
    [settings.tables.staticTexts[@"Reset"] tap];
    [settings.tables.staticTexts[@"Reset Location & Privacy"] tap];

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
        [settings.buttons[@"Reset"] tap];
    } else {
        [settings.buttons[@"Reset Warnings"] tap];
    }
    sleep(2);
    [settings terminate];
}

이것은 모든 OS 버전(iOS11, 12 및 13)에서 나에게 적합합니다.

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

    func deleteApp() {
        XCUIApplication().terminate()
        springboard.activate()

        let icon = springboard.icons[appName]

        if icon.exists {
            icon.firstMatch.press(forDuration: 5)
            icon.buttons["DeleteButton"].tap()

            let deleteConfirmation = springboard.alerts["Delete “\(appName)”?"].buttons["Delete"]
            XCTAssertTrue(deleteConfirmation.waitForExistence(timeout: 5), "Delete confirmation not shown")
            deleteConfirmation.tap()
        }
    }

몇 가지 실험 끝에 다양한 iOS 버전을 포함하는 보다 명확한 솔루션 구현을 마쳤습니다.

import XCTest

private enum Constants {
  static let springboardBundleIdentifier = "com.apple.springboard"
  static let appIconName = "Your App Name"
  static let appIconPressShortDuration: TimeInterval = 2.0
  static let appIconPressLongDuration: TimeInterval = 3.0
  static let deleteAppButton = "Delete App"
  static let removeAppButton = "Remove App"
  static let deleteButton = "Delete"
  static let deleteButtonVectorOffset: CGFloat = 3.0
}

final class SpringboardManager {

  private static let springboard = XCUIApplication(bundleIdentifier: Constants.springboardBundleIdentifier)

  static func deleteApp(_ app: XCUIApplication) {
    if app.exists && app.isHittable {
      XCUIDevice.shared.press(.home)
    }

    app.terminate()

    self.deleteAppIfNeeded(with: Constants.appIconName)
    sleep(1)
  }

  private static func deleteAppIfNeeded(with iconName: String) {
    let appIcon = self.springboard.icons[iconName]

    guard appIcon.exists else {
      return
    }

    appIcon.press(forDuration: Constants.appIconPressShortDuration)

    if let deleteListButton = self.deleteListButton() {
      deleteListButton.tap()
      self.pressDeleteAlertButtons()
    } else {
      appIcon.press(forDuration: Constants.appIconPressLongDuration)
      self.pressDeleteTopLeftButton(for: appIcon)
      self.pressDeleteAlertButtons()
    }
  }

}

private extension SpringboardManager {

  static func pressDeleteAlertButtons() {
    self.pressDeleteAlertButton(self.deleteAppAlertButton())
    self.pressDeleteAlertButton(self.deleteAlertButton())
  }

  static func pressDeleteAlertButton(_ button: XCUIElement?) {
    guard let button = button else {
      return
    }

    button.tap()
  }

  static func pressDeleteTopLeftButton(for appIcon: XCUIElement) {
    let iconFrame = appIcon.frame
    let springboardFrame = self.springboard.frame

    let deleteButtonVector = CGVector(
      dx: (iconFrame.minX + Constants.deleteButtonVectorOffset) / springboardFrame.maxX,
      dy: (iconFrame.minY + Constants.deleteButtonVectorOffset) / springboardFrame.maxY)

    let deleteButtonCoordinate = self.springboard.coordinate(withNormalizedOffset: deleteButtonVector)
    deleteButtonCoordinate.tap()
  }

}

private extension SpringboardManager {

  static func deleteListButton() -> XCUIElement? {
    sleep(1)
    
    let removeListButton = self.springboard.buttons[Constants.removeAppButton]
    let deleteListButton = self.springboard.buttons[Constants.deleteAppButton]

    if removeListButton.exists {
      return removeListButton
    } else if deleteListButton.exists {
      return deleteListButton
    }

    return nil
  }

  static func deleteAppAlertButton() -> XCUIElement? {
    sleep(1)

    let deleteAppButton = self.springboard.alerts.buttons[Constants.deleteAppButton]

    if deleteAppButton.exists {
      return deleteAppButton
    }

    return nil
  }

  static func deleteAlertButton() -> XCUIElement? {
    sleep(1)

    let deleteButton = self.springboard.alerts.buttons[Constants.deleteButton]

    if deleteButton.exists {
      return deleteButton
    }

    return nil
  }

}

언급URL : https://stackoverflow.com/questions/33107731/is-there-a-way-to-reset-the-app-between-tests-in-swift-xctest-ui

반응형