C’è un modo per resettare l’app tra i test nell’interfaccia utente di Swift XCTest?

Esiste una chiamata API all’interno di XCTest che posso inserire in setUP () o tearDown () per reimpostare l’app tra i test? Ho osservato la syntax del punto di XCUIApplication e tutto ciò che ho visto era il .launch ()

O c’è un modo per chiamare uno script di shell in Swift? Potrei quindi chiamare xcrun tra i metodi di test per resettare il simulatore.

È ansible aggiungere una fase “Esegui script” per creare fasi nella destinazione del test per disinstallare l’app prima di eseguire test di unità contro di essa, ma sfortunatamente non si tratta di casi di test .

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

Aggiornare


Tra una prova e l’altra , puoi eliminare l’app tramite la Springboard nella fase di strappo. Anche se, questo richiede l’uso di un’intestazione privata da XCTest. (Il dump della testata è disponibile da WebDriverAgent di Facebook qui .)

Ecco alcuni esempi di codice da una class Springboard per eliminare un’applicazione da Springboard tenendo premuto:

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

E poi:

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

Le intestazioni private sono state importate nell’intestazione del bridging Swift. Dovrai importare:

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

A questo punto, l’API pubblica in Xcode 7 e 8 e il simulatore non sembrano avere alcun metodo richiamabile dalle setUp() di setUp() e tearDown() a “Reset Contents and Settings” per il simulatore.

Esistono altri possibili approcci che utilizzano API pubbliche:

  1. Codice dell’applicazione Aggiungi del codice dell’applicazione myResetApplication() per mettere l’applicazione in uno stato conosciuto. Tuttavia, il controllo dello stato del dispositivo (simulatore) è limitato dalla sandbox dell’applicazione … che non è di grande aiuto al di fuori dell’applicazione. Questo approccio è OK per cancellare la persistenza controllabile dell’applicazione.

  2. Script di Shell . Esegui i test da uno script di shell. Usa xcrun simctl erase all o xcrun simctl uninstall o simile tra ogni esecuzione di prova per resettare il simulatore (o disinstallare l’app) . vedi StackOverflow: “Come posso resettare il simulatore iOS dalla riga di comando?”

  macos> xcrun simctl --help # can uninstall a single application macos> xcrun simctl uninstall --help # Usage: simctl uninstall   
  1. Xcode Schema Action . Aggiungi xcrun simctl erase all (o xcrun simctl erase ) o simile alla sezione Test schema. Seleziona il prodotto> Schema> Modifica schema … menu. Espandi la sezione Test schema. Seleziona Pre-azioni nella sezione Test. Fai clic su (+) Aggiungi “Azione Script di nuova esecuzione”. Il comando xcrun simctl erase all può essere digitato direttamente senza richiedere alcuno script esterno.

Opzioni per invocare 1. Codice applicazione per reimpostare l’applicazione:

A. Interfaccia utente dell’applicazione . [Test dell’interfaccia utente] Fornire un pulsante di ripristino o un’altra azione dell’interfaccia utente che ripristina l’applicazione. L’elemento UI può essere esercitato tramite XCUIApplication nelle routine setUp() , tearDown() o testSomething() .

B. Avvia parametro . [Test dell’interfaccia utente] Come notato da Victor Ronin, un argomento può essere passato dal setUp() test setUp()

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

… per essere ricevuto AppDelegate

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

C. Parametro Schema Xcode . [Test UI, Test unità] Selezionare il menu Prodotto> Schema> Modifica schema …. Espandi la sezione Esegui schema. (+) Aggiungi alcuni parametri come MY_UI_TEST_MODE . Il parametro sarà disponibile in NSProcessInfo.processInfo() .

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

Z. Chiamata diretta . [Unit Test] I bundle di test unitari vengono iniettati nell’applicazione in esecuzione e possono chiamare direttamente alcune routine myResetApplication() nell’applicazione. Avvertenza: i test dell’unità predefiniti vengono eseguiti dopo il caricamento della schermata principale. vedere Test sequenza di carico. Tuttavia, i bundli di test dell’interfaccia utente vengono eseguiti come un processo esterno all’applicazione sottoposta a test. Quindi, ciò che funziona nel Test unità fornisce un errore di collegamento in un test dell’interfaccia utente.

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

Aggiornato per swift 3.1 / xcode 8.3

crea un’intestazione di bridging nella destinazione del test:

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

class Springboard aggiornata

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

Puoi chiedere alla tua app di “ripulire” se stessa

  • Si utilizza XCUIApplication.launchArguments per impostare alcuni flag
  • In AppDelegate controlli

    if NSProcessInfo.processInfo (). arguments.contains (“YOUR_FLAG_NAME_HERE”) {// Fai una pulizia qui}

Ho utilizzato la risposta @Chase Holland e aggiornato la class Springboard seguendo lo stesso approccio per reimpostare il contenuto e le impostazioni utilizzando l’app Impostazioni. Questo è utile quando è necessario ripristinare le windows di dialogo dei permessi.

 importa XCTest

 class Springboard {
     static let springboard = XCUIApplication (privateWithPath: nil, bundleID: "com.apple.springboard")
     static let settings = XCUIApplication (privateWithPath: nil, bundleID: "com.apple.Preferences")

     / **
      Termina ed elimina l'app tramite trampolino di lancio
      * /
     class func deleteMyApp () {
         XCUIApplication (). Terminate ()

         // Risolvi la query per il trampolino invece di lanciarlo
         springboard.resolve ()

         // Forza elimina l'app dal trampolino
         let icon = springboard.icons ["MyAppName"]
         se icon.exists {
             let iconFrame = icon.frame
             lascia springboardFrame = springboard.frame
             icon.pressForDuration (1.3)

             // Tocca il piccolo tasto "X" approssimativamente dove si trova.  La X non è esposta direttamente
             springboard.coordinateWithNormalizedOffset (CGVectorMake ((iconFrame.minX + 3) / springboardFrame.maxX, (iconFrame.minY + 3) / springboardFrame.maxY)). tap ()

             springboard.alerts.buttons [ "Elimina"]. rubinetto ()

             // Premi a casa una volta che le icone smettono di dimenare
             XCUIDevice.sharedDevice (). Pressbutton (.Home)
             // Premi di nuovo a casa per andare alla prima pagina del trampolino
             XCUIDevice.sharedDevice (). Pressbutton (.Home)
             // Aspetta un po 'di tempo per l'animazione
             NSThread.sleepForTimeInterval (0.5)

             let settingsIcon = springboard.icons ["Impostazioni"]
             if settingsIcon.exists {
                 settingsIcon.tap ()
                 settings.tables.staticTexts [ "generali"]. rubinetto ()
                 settings.tables.staticTexts [ "Reset"]. toccare ()
                 settings.tables.staticTexts ["Reset Location & Privacy"]. tap ()
                 settings.buttons ["Reset Warnings"]. tap ()
                 settings.terminate ()
             }
         }
     }
 }

Ho usato la risposta @ ODM , ma l’ho modificata per funzionare con Swift 4. NB: alcune risposte S / O non differenziano le versioni Swift, che a volte hanno differenze piuttosto fondamentali. Ho provato questo su un simulatore di iPhone 7 e un simulatore di iPad Air con orientamento verticale, e ha funzionato per la mia app.

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

Per i sim di iOS 11 in su, ho apportato una leggera modifica per toccare l’icona “x” e dove si tocca la correzione suggerita da @Code Monkey. La correzione funziona bene su entrambi i sim di telefono 10.3 e 11.2. Per la cronaca, sto usando swift 3. Ho pensato di usare un codice per copiare e incollare per trovare la correzione un po ‘più 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() } } } 

Basandosi su Chase Holland e le risposte di Odm, sono stato in grado di evitare il lungo touch e +3 offset bs eliminando l’app in impostazioni come 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: String) { 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: String?) { 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[String(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() } } } 

Uso:

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