Existe-t-il un moyen de réinitialiser l’application entre les tests de l’interface utilisateur de Swift XCTest?

Y a-t-il un appel API dans XCTest que je peux mettre dans setUP () ou tearDown () pour réinitialiser l’application entre les tests? J’ai regardé dans la syntaxe à sharepoint XCUIApplication et tout ce que j’ai vu était le .launch ()

OU existe-t-il un moyen d’appeler un script shell dans Swift? Je pourrais alors appeler des méthodes de test entre les deux systèmes pour réinitialiser le simulateur.

    Vous pouvez append une phase “Exécuter le script” pour créer des phases dans votre cible de test afin de désinstaller l’application avant de lancer les tests unitaires, mais ce n’est malheureusement pas le cas .

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

    Mettre à jour


    Entre les tests, vous pouvez supprimer l’application via le tremplin dans la phase d’arrêt. Bien que cela nécessite l’utilisation d’un en-tête privé de XCTest. (Le vidage des en-têtes est disponible sur le WebDriverAgent de Facebook ici .)

    Voici un exemple de code d’une classe Springboard pour supprimer une application de Springboard via un clic:

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

    Et alors:

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

    Les en-têtes privés ont été importés dans l’en-tête de pontage Swift. Vous devrez importer:

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

    À ce jour, l’API publique de Xcode 7 & 8 et le simulateur n’apparaissent pas avec une méthode appelable depuis les sous- setUp() et tearDown() XCText pour “Réinitialiser le contenu et les parameters” du simulateur.

    Il existe d’autres approches possibles qui utilisent des API publiques:

    1. Code d’application Ajoutez du code d’application myResetApplication() pour placer l’application dans un état connu. Cependant, le contrôle de l’état du périphérique (simulateur) est limité par le sandbox de l’application … ce qui n’est pas très utile en dehors de l’application. Cette approche est correcte pour effacer la persistance contrôlable de l’application.

    2. Script shell Exécutez les tests à partir d’un script shell. Utilisez xcrun simctl erase all ou xcrun simctl uninstall ou similaire entre chaque test pour réinitialiser le simulateur (ou désinstallez l’application) . voir StackOverflow: “Comment réinitialiser le simulateur iOS à partir de la ligne de commande?”

      macos> xcrun simctl --help # can uninstall a single application macos> xcrun simctl uninstall --help # Usage: simctl uninstall   
    1. Action du schéma Xcode . Ajoutez xcrun simctl erase all (ou xcrun simctl erase ) ou similaire à la section Test de schéma. Sélectionnez le menu Product> Scheme> Edit Scheme…. Développez la section Test de schéma. Sélectionnez Pré-actions dans la section Test. Cliquez sur (+) pour append “Nouvelle action de script d’exécution”. La commande xcrun simctl erase all peut être tapé directement sans nécessiter de script externe.

    Options pour appeler 1. Code d’application pour réinitialiser l’application:

    A. Interface utilisateur de l’application . [Test de l’interface utilisateur] Fournit un bouton de réinitialisation ou une autre action de l’interface utilisateur qui réinitialise l’application. L’élément d’interface utilisateur peut être XCUIApplication via XCUIApplication dans les routines setUp() , tearDown() ou testSomething() .

    B. Lancer le paramètre . [Test de l’interface utilisateur] Comme indiqué par Victor Ronin, un argument peut être transmis depuis le test setUp()

     class AppResetUITests: XCTestCase { override func setUp() { // ... let app = XCUIApplication() app.launchArguments = ["MY_UI_TEST_MODE"] app.launch() 

    … à recevoir par l’ AppDelegate

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

    C. Paramètre de schéma Xcode . [UI Test, Unit Test] Sélectionnez le menu Product> Scheme> Edit Scheme…. Développez la section Run Scheme. (+) Ajoutez un paramètre tel que MY_UI_TEST_MODE . Le paramètre sera disponible dans NSProcessInfo.processInfo() .

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

    Z. Appel direct . Les tests unitaires [Unit Test] sont injectés dans l’application en cours d’exécution et peuvent appeler directement une routine myResetApplication() dans l’application. Avertissement: Les tests unitaires par défaut s’exécutent après le chargement de l’écran principal. voir Séquence de test de charge Cependant, les ensembles de tests de l’interface utilisateur s’exécutent en tant que processus externe à l’application testée. Donc, ce qui fonctionne dans le test unitaire donne une erreur de lien dans un test d’interface utilisateur.

     class AppResetUnitTests: XCTestCase { override func setUp() { // ... Unit Test: runs. UI Test: link error. myResetApplication() // visible code implemented in application 

    Mise à jour pour swift 3.1 / xcode 8.3

    créer un en-tête de pontage dans la cible de test:

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

    classe de tremplin mise à jour

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

    Vous pouvez demander à votre application de se “nettoyer”

    • Vous utilisez XCUIApplication.launchArguments pour définir un indicateur
    • Dans AppDelegate vous vérifiez

      if NSProcessInfo.processInfo (). arguments.contains (“YOUR_FLAG_NAME_HERE”) {// Faites un nettoyage ici}

    J’ai utilisé la réponse @Chase Holland et mis à jour la classe Springboard en suivant la même approche pour réinitialiser le contenu et les parameters à l’aide de l’application Paramètres. Ceci est utile lorsque vous devez réinitialiser les boîtes de dialog des permissions.

     importer XCTest
    
     classe tremplin {
         static let springboard = XCUIApplication (privateWithPath: nil, bundleID: "com.apple.springboard")
         static let settings = XCUIApplication (privateWithPath: nil, bundleID: "com.apple.Preferences")
    
         / **
          Terminer et supprimer l'application via le tremplin
          * /
         class func deleteMyApp () {
             XCUIApplication (). Terminate ()
    
             // Résoudre la requête pour le tremplin plutôt que de la lancer
             springboard.resolve ()
    
             // Forcer la suppression de l'application du tremplin
             let icon = springboard.icons ["MyAppName"]
             si icon.exists {
                 laissez iconFrame = icon.frame
                 laissez springboardFrame = springboard.frame
                 icon.pressForDuration (1.3)
    
                 // Appuyez sur le petit bouton "X" approximativement à l'endroit où il se trouve.  Le X n'est pas exposé directement
                 springboard.coordinateWithNormalizedOffset (CGVectorMake ((iconFrame.minX + 3) / springboardFrame.maxX, (iconFrame.minY + 3) / springboardFrame.maxY)).
    
                 springboard.alerts.buttons ["Supprimer"]. tap ()
    
                 // Appuyez une fois sur la touche pour que les icons arrêtent de se tortiller
                 XCUIDevice.sharedDevice (). PressButton (.Home)
                 // Appuyez à nouveau sur home pour aller à la première page du tremplin
                 XCUIDevice.sharedDevice (). PressButton (.Home)
                 // Attendez un peu la fin de l'animation
                 NSThread.sleepForTimeInterval (0.5)
    
                 let settingsIcon = springboard.icons ["Paramètres"]
                 si settingsIcon.exists {
                     settingsIcon.tap ()
                     settings.tables.staticTexts ["General"]. tap ()
                     settings.tables.staticTexts ["Reset"]. tap ()
                     settings.tables.staticTexts ["Réinitialiser l'emplacement et la confidentialité"]. tap ()
                     settings.buttons ["Reset Warnings"]. tap ()
                     settings.terminate ()
                 }
             }
         }
     }
    

    J’ai utilisé la réponse @ ODM , mais je l’ai modifiée pour fonctionner avec Swift 4. NB: certaines réponses S / O ne différencient pas les versions Swift, qui ont parfois des différences assez fondamentales. J’ai testé cela sur un simulateur iPhone 7 et un simulateur iPad Air en orientation portrait, et cela a fonctionné pour mon application.

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

    Pour iOS 11 sims up, j’ai apporté une légère modification pour taper sur l’icône “x” et où nous tapons sur le correctif suggéré par @Code Monkey. Le correctif fonctionne bien sur les simulations téléphoniques 10.3 et 11.2. Pour mémoire, j’utilise swift 3. Je pensais que je pourrais copier et coller du code pour trouver le correctif un peu plus facile. 🙂

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

    En s’appuyant sur les réponses de Chase Holland et d’odm, j’ai pu éviter le long tap et le décalage de +3 bs en supprimant l’application dans des parameters tels que dis:

     import XCTest class Springboard { static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") static let settings = XCUIApplication(bundleIdentifier: "com.apple.Preferences") static let isiPad = UIScreen.main.traitCollection.userInterfaceIdiom == .pad class func deleteApp(name: Ssortingng) { XCUIApplication().terminate() if !springboard.icons[name].firstMatch.exists { return } settings.launch() goToRootSetting(settings) settings.tables.staticTexts["General"].tap() settings.tables.staticTexts[(isiPad ? "iPad" : "iPhone") + " Storage"].tap() while settings.tables.activityIndicators["In progress"].exists { sleep(1) } let appTableCellElementQuery = settings.tables.staticTexts.matching(identifier: name) appTableCellElementQuery.element(boundBy: appTableCellElementQuery.count - 1).tap() settings.tables.staticTexts["Delete App"].tap() isiPad ? settings.alerts.buttons["Delete App"].tap() : settings.buttons["Delete App"].tap() settings.terminate() } /** You may not want to do this cuz it makes you re-trust your computer and device. **/ class func resetLocationAndPrivacySetting(passcode: Ssortingng?) { settings.launch() goToRootSetting(settings) settings.tables.staticTexts["General"].tap() settings.tables.staticTexts["Reset"].tap() settings.tables.staticTexts["Reset Location & Privacy"].tap() passcode?.forEach({ char in settings.keys[Ssortingng(char)].tap() }) isiPad ? settings.alerts.buttons["Reset"].tap() : settings.buttons["Reset Settings"].tap() } class func goToRootSetting(_ settings: XCUIApplication) { let navBackButton = settings.navigationBars.buttons.element(boundBy: 0) while navBackButton.exists { navBackButton.tap() } } } 

    Usage:

     Springboard.deleteApp(name: "AppName") Springboard.resetLocationAndPrivacySetting()