Program Tip

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

programtip 2020. 11. 26. 19:47
반응형

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


테스트 사이에 앱을 재설정하기 위해 setUP () 또는 tearDown ()에 넣을 수있는 XCTest 내에 API 호출이 있습니까? XCUIApplication의 도트 구문을 살펴본 결과 .launch () 만 보았습니다.

또는 Swift에서 쉘 스크립트를 호출하는 방법이 있습니까? 그런 다음 시뮬레이터를 재설정하기 위해 테스트 메소드 사이에서 xcrun을 호출 할 수 있습니다.


"Run Script"단계를 추가하여 테스트 대상에 단계를 빌드하여 앱에 대해 단위 테스트를 실행하기 전에 앱을 제거 할 수 있습니다. 안타깝게도 이는 테스트 사례 사이에 해당되지 않습니다 .

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

            // 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 10 XCUIApplication(bundleIdentifier:)부터는 이제 Apple에서 공개되며 개인 헤더는 더 이상 필요하지 않습니다 .


현재 Xcode 7 및 8 공용 API 및 시뮬레이터에는 시뮬레이터에 대한 "콘텐츠 및 설정 재설정" 에서 호출 할 수있는 메서드 setUp()tearDown() XCText하위 클래스 가없는 것으로 보입니다 .

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

  1. 응용 프로그램 코드 . 일부 myResetApplication()애플리케이션 코드를 추가하여 애플리케이션을 알려진 상태로 만듭니다. 그러나 장치 (시뮬레이터) 상태 제어는 응용 프로그램 샌드 박스에 의해 제한됩니다. 이는 응용 프로그램 외부에서별로 도움이되지 않습니다. 이 접근 방식은 애플리케이션 제어 가능한 지속성을 지우는 데 적합합니다.

  2. 쉘 스크립트 . 쉘 스크립트에서 테스트를 실행하십시오. 사용 xcrun simctl erase all하거나 xcrun 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>
  1. Xcode 스키마 작업 . Scheme Test 섹션을 추가 xcrun simctl erase all(또는 xcrun simctl erase <DEVICE_UUID>)하거나 유사합니다. Product> Scheme> Edit Scheme… 메뉴를 선택합니다. 체계 테스트 섹션을 확장합니다. 테스트 섹션에서 사전 조치를 선택하십시오. [+)를 클릭하여 "New Run Script Action"을 추가합니다. 명령 xcrun simctl erase all은 외부 스크립트없이 직접 입력 할 수 있습니다.

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

A. 응용 프로그램 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()

...에서 받기 위해 AppDelegate...

class AppDelegate: UIResponder, UIApplicationDelegate {

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

C. Xcode 체계 매개 변수 . [UI 테스트, 단위 테스트] 제품> 체계> 체계 편집… 메뉴를 선택합니다. Scheme Run 섹션을 확장합니다. (+) MY_UI_TEST_MODE. 매개 변수는에서 사용할 수 있습니다 NSProcessInfo.processInfo().

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

Z. 직접 통화 . [단위 테스트] 단위 테스트 번들은 실행중인 애플리케이션에 삽입되며 애플리케이션의 일부 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

업데이트 된 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에서

    if NSProcessInfo.processInfo (). arguments.contains ( "YOUR_FLAG_NAME_HERE") {// 여기서 정리}


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

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

iOS 11 심즈의 경우 "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 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 / Swift 5.1 UI 기반 삭제

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

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

    springboard.activate()
    let icons = springboard.icons.matching(identifier: "YourAppTitle")

    let icon = icons.firstMatch
    icon.press(forDuration: 1.3)

    springboard.buttons["Rearrange Apps"].tap()

    Thread.sleep(forTimeInterval: 1)

    icon.buttons["DeleteButton"].tap()

    let deleteButton = springboard.alerts.buttons["Delete"].firstMatch
    XCTAssert(deleteButton.waitForExistence(timeout: 3))
    deleteButton.tap()

}

Swift 4에 대한 Craig Fishers 답변 업데이트. 가로 모드에서 iPad 용으로 업데이트되었습니다. 아마도 왼쪽 가로에서만 작동합니다.

XCTest 가져 오기

class Springboard {

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

}


다음은 앱 삭제 및 경고 재설정에 대한 위 답변의 Objective 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];
}

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

반응형