Existe-t-il un appel d'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 de points de XCUIApplication et tout ce que j'ai vu était le .launch ()
OU y a-t-il un moyen d'appeler un script shell dans Swift? Je pourrais ensuite appeler xcrun entre deux méthodes de test pour réinitialiser le simulateur.
Vous pouvez ajouter une phase "Exécuter le script" pour créer des phases dans votre cible de test afin de désinstaller l'application avant d'exécuter des tests unitaires. malheureusement ce n'est pas entre les cas de test, bien que.
/usr/bin/xcrun simctl uninstall booted com.mycompany.bundleId
Mettre à jour
Entre les tests, vous pouvez supprimer l'application via le Springboard dans la phase d'arrachement. Bien que cela nécessite l'utilisation d'un en-tête privé de XCTest. (Le vidage d'en-tête est disponible à partir de WebDriverAgent de Facebook ici .)
Voici un exemple de code d'une classe Springboard permettant de supprimer une application de Springboard en maintenant le doigt appuyé:
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()
}
}
}
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 rapide. Vous aurez besoin d'importer:
// Private headers from XCTest
#import "XCUIApplication.h"
#import "XCUIElement.h"
Note: à partir de Xcode 10, XCUIApplication(bundleIdentifier:)
est maintenant exposé par Apple et les en-têtes privés sont n'est plus nécessaire.
Actuellement, les API publique de Xcode 7 et 8 et le simulateur ne semblent pas avoir de méthode appelable à partir des sous-classes setUp()
et tearDown()
XCText
pour "Réinitialiser le contenu et les paramètres" du simulateur.
Il existe d'autres approches possibles utilisant des API publiques:
Code d'application. Ajoutez du code d'application myResetApplication()
pour mettre l'application dans un état connu. Cependant, le contrôle de l'état du périphérique (simulateur) est limité par le bac à sable de l'application ... qui n'est pas d'une grande aide en dehors de l'application. Cette approche convient pour effacer la persistance contrôlable par l'application.
Script Shell. Exécutez les tests à partir d'un script Shell. Utilisez xcrun simctl erase all
ou xcrun simctl uninstall <device> <app identifier>
ou similaire entre chaque série de tests pour réinitialiser le simulateur (ou désinstaller l'application). voir StackOverflow: "Comment puis-je 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 <device> <app identifier>
xcrun simctl erase all
(ou xcrun simctl erase <DEVICE_UUID>
) 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 (+) ajouter "Nouvelle action de script d'exécution". La commande xcrun simctl erase all
peut être saisie directement sans nécessiter de script externe.Options d'appel de 1. Code d'application pour réinitialiser l'application:
A. Application UI. [UI Test] _ Fournissez 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 exercé via XCUIApplication
dans les routines XCTest
setUp()
, tearDown()
ou testSomething()
.
B. Paramètre de lancement. (Test d'interface utilisateur) Comme l'a noté Victor Ronin, un argument peut être transmis à partir du test setUp()
...
class AppResetUITests: XCTestCase {
override func setUp() {
// ...
let app = XCUIApplication()
app.launchArguments = ["MY_UI_TEST_MODE"]
app.launch()
... à recevoir par le AppDelegate
...
class AppDelegate: UIResponder, UIApplicationDelegate {
func application( …didFinishLaunchingWithOptions… ) -> Bool {
// ...
let args = NSProcessInfo.processInfo().arguments
if args.contains("MY_UI_TEST_MODE") {
myResetApplication()
}
C. Xcode Scheme Parameter. [Test d'interface utilisateur, Test unitaire]} _ Sélectionnez le menu Produit> Schéma> Modifier le schéma…. Développez la section Scheme Run. (+) Ajoutez un paramètre comme 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. [Unit Test] Les ensembles de tests d'unités 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 sont exécutés après le chargement de l'écran principal. voir Test de la séquence de charge Cependant, les ensembles de tests d'interface utilisateur s'exécutent en tant que processus externe à l'application testée. Ainsi, ce qui fonctionne dans le test unitaire donne une erreur de liaison 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 <XCTest/XCUIApplication.h>
#import <XCTest/XCUIElement.h>
@interface XCUIApplication (Private)
- (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID;
- (void)resolve;
@end
classe Springboard 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()
}
}
}
}
J'ai utilisé le @ ODManswer , mais je l'ai modifié pour fonctionner avec Swift 4. NB: certaines réponses S/O ne différencient pas les versions de Swift, qui présentent 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()
}
}
}
}
Vous pouvez demander à votre application de se "nettoyer"
XCUIApplication.launchArguments
pour définir un indicateurDans AppDelegate, vous cochez
if NSProcessInfo.processInfo (). arguments.contains ("YOUR_FLAG_NAME_HERE") { // nettoie ici }
J'ai utilisé @Chase Holland answer et mis à jour la classe Springboard en suivant la même approche pour réinitialiser le contenu et les paramètres à l'aide de l'application Paramètres. Ceci est utile lorsque vous devez réinitialiser les boîtes de dialogue d'autorisations.
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()
}
}
}
}
Pour iOS 11 sims up, j'ai fait une modification très légère pour appuyer sur l'icône "x" et pour sélectionner le correctif suggéré par @Code Monkey. La correction fonctionne bien sur les sims de téléphone 10.3 et 11.2. Pour mémoire, j'utilise Swift 3. Je pensais avoir copié et collé du code pour trouver le correctif un peu plus facilement. :)
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()
}
}
}
Cela semble fonctionner pour moi sur iOS 12.1 et simulateur
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)
}
}
En me basant sur les réponses de Chase Holland et d'odm, j'ai pu éviter le tap long et +3 offset bs en supprimant l'application dans des paramètres 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: 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()
}
}
}
Usage:
Springboard.deleteApp(name: "AppName")
Springboard.resetLocationAndPrivacySetting()
Mise à jour de Craig Fishers pour Swift 4. Mis à jour pour iPad en mode paysage, ne fonctionne probablement que pour le paysage à gauche.
importer XCTest
classe Tremplin {
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)
}
}
}
Voici une version Objective C des réponses ci-dessus pour supprimer une application et réinitialiser les avertissements (testés sur iOS 11 et 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];
}