Gibt es in XCTest einen API-Aufruf, den ich in setUP () oder tearDown () einfügen kann, um die App zwischen den Tests zurückzusetzen? Ich habe in der Punktsyntax von XCUIApplication nachgesehen und alles, was ich sah, war die .launch ()
ODER gibt es eine Möglichkeit, ein Shell-Skript in Swift aufzurufen? Ich könnte dann xcrun zwischen den Testmethoden aufrufen, um den Simulator zurückzusetzen.
swift
xcode7
xctest
xcode-ui-testing
Laser Hawk
quelle
quelle
gitlab-ci.yml
Datei gelöst .Antworten:
Sie können eine "Skript ausführen" -Phase hinzufügen, um Phasen in Ihrem Testziel zu erstellen, in denen die App deinstalliert wird, bevor Unit-Tests ausgeführt werden.
Dies liegt jedoch leider nicht zwischen Testfällen./usr/bin/xcrun simctl uninstall booted com.mycompany.bundleId
Aktualisieren
Zwischen den Tests können Sie die App in der TearDown-Phase über das Springboard löschen. Dies erfordert jedoch die Verwendung eines privaten Headers von XCTest. (Der Header-Dump ist hier im WebDriverAgent von Facebook verfügbar .)
Hier ist ein Beispielcode aus einer Springboard-Klasse zum Löschen einer App aus Springboard durch Tippen und Halten:
Swift 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() } } }
Swift 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() } } }
Und dann:
override func tearDown() { Springboard.deleteMyApp() super.tearDown() }
Die privaten Header wurden in den Swift-Bridging-Header importiert. Sie müssen importieren:
// Private headers from XCTest #import "XCUIApplication.h" #import "XCUIElement.h"
Hinweis : Ab Xcode 10 wird
XCUIApplication(bundleIdentifier:)
es jetzt von Apple verfügbar gemacht, und die privaten Header werden nicht mehr benötigt .quelle
NSBundle-bundleWithIdentifier/Path
, aber die Test-App hat keinen Verweis auf das Anwendungspaket. Mein Projekt hat viele Ziele, jedes mit einem anderen Namen, und ich möchte die Springboard-Klasse für alle Ziele verwenden können.icon.buttons["DeleteButton"].tap()
nach langem Drücken gedrückt werden, anstatt die zu verwendenCGVector
.app.launch()
danachSpringboard.deleteMyApp()
The request was denied by service delegate (SBMainWorkspace) for reason: NotFound ("Application "com.serpentisei.studyjapanese" is unknown to FrontBoard").
Zu diesem Zeitpunkt wird die öffentliche API ist in Xcode und dem Simulator erscheint keine Methode aufrufbar hat aus
setUp()
undtearDown()
XCText
Subklassen „Reset Inhalte und Einstellungen“ für den Simulator.Es gibt andere mögliche Ansätze, die öffentliche APIs verwenden:
Anwendungscode . Fügen Sie einen
myResetApplication()
Anwendungscode hinzu, um die Anwendung in einen bekannten Zustand zu versetzen. Die Statussteuerung des Geräts (Simulators) wird jedoch durch die Anwendungssandbox eingeschränkt. Dies ist außerhalb der Anwendung keine große Hilfe. Dieser Ansatz ist zum Löschen der von der Anwendung steuerbaren Persistenz in Ordnung.Shell-Skript . Führen Sie die Tests über ein Shell-Skript aus. Verwenden Sie
xcrun simctl erase all
oderxcrun simctl uninstall <device> <app identifier>
ähnliches zwischen jedem Testlauf, um den Simulator zurückzusetzen (oder die App zu deinstallieren) . Siehe StackOverflow: "Wie kann ich den iOS-Simulator über die Befehlszeile zurücksetzen?"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>]
xcrun simctl erase all
(oderxcrun simctl erase <DEVICE_UUID>
) oder ähnlicher Befehle zu einem Xcode-Schema-Abschnitt wie dem Test- oder Build-Abschnitt. Wählen Sie das Menü Produkt> Schema> Schema bearbeiten…. Erweitern Sie den Abschnitt Schematest. Wählen Sie im Abschnitt Test die Option Voraktionen. Klicken Sie auf (+) und fügen Sie "New Run Script Action" hinzu. Der Befehlxcrun simctl erase all
kann direkt eingegeben werden, ohne dass ein externes Skript erforderlich ist.Optionen zum Aufrufen 1. Anwendungscode zum Zurücksetzen der Anwendung:
A. Benutzeroberfläche der Anwendung . [UI-Test] Geben Sie eine Schaltfläche zum Zurücksetzen oder eine andere UI-Aktion an, mit der die Anwendung zurückgesetzt wird. Das Oberflächenelement kann über ausgeübt werden ,
XCUIApplication
inXCTest
RoutinensetUp()
,tearDown()
odertestSomething()
.B. Parameter starten . [UI-Test] Wie von Victor Ronin bemerkt, kann ein Argument aus dem Test übergeben werden
setUp()
...class AppResetUITests: XCTestCase { override func setUp() { // ... let app = XCUIApplication() app.launchArguments = ["MY_UI_TEST_MODE"] app.launch()
... von der
AppDelegate
...class AppDelegate: UIResponder, UIApplicationDelegate { func application( …didFinishLaunchingWithOptions… ) -> Bool { // ... let args = NSProcessInfo.processInfo().arguments if args.contains("MY_UI_TEST_MODE") { myResetApplication() }
C. Xcode-Schema-Parameter . [UI-Test, Komponententest] Wählen Sie das Menü Produkt> Schema> Schema bearbeiten…. Erweitern Sie den Abschnitt Scheme Run. (+) Fügen Sie einige Parameter wie hinzu
MY_UI_TEST_MODE
. Der Parameter wird in verfügbar seinNSProcessInfo.processInfo()
.// ... in application let args = NSProcessInfo.processInfo().arguments if args.contains("MY_UI_TEST_MODE") { myResetApplication() }
Z. Direktanruf . [Unit Test] Unit Test Bundles werden in die laufende Anwendung eingefügt und können direkt eine
myResetApplication()
Routine in der Anwendung aufrufen . Vorsichtsmaßnahme: Standard-Unit-Tests werden ausgeführt, nachdem der Hauptbildschirm geladen wurde. Siehe Testladereihenfolge . UI-Testpakete werden jedoch als Prozess außerhalb der zu testenden Anwendung ausgeführt. Was also im Unit Test funktioniert, führt zu einem Linkfehler in einem UI-Test.class AppResetUnitTests: XCTestCase { override func setUp() { // ... Unit Test: runs. UI Test: link error. myResetApplication() // visible code implemented in application
quelle
xcrun simctl erase all
ist ein toller Vorschlag - danke!Aktualisiert für Swift 3.1 / Xcode 8.3
Überbrückungsheader im Testziel erstellen:
#import <XCTest/XCUIApplication.h> #import <XCTest/XCUIElement.h> @interface XCUIApplication (Private) - (id)initPrivateWithPath:(NSString *)path bundleID:(NSString *)bundleID; - (void)resolve; @end
aktualisierte Springboard-Klasse
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() } } } }
quelle
Sie können Ihre App bitten, sich selbst zu "bereinigen"
Sie verwenden
XCUIApplication.launchArguments
, um ein Flag zu setzenIn AppDelegate überprüfen Sie
if NSProcessInfo.processInfo (). argument.contains ("YOUR_FLAG_NAME_HERE") {// Hier bereinigen}
quelle
Ich habe die @ ODM- Antwort verwendet , sie jedoch so geändert, dass sie für Swift 4 funktioniert. NB: Einige S / O-Antworten unterscheiden die Swift-Versionen nicht, die manchmal ziemlich grundlegende Unterschiede aufweisen. Ich habe dies auf einem iPhone 7-Simulator und einem iPad Air-Simulator im Hochformat getestet und es hat für meine App funktioniert.
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() } } } }
quelle
Lösung für 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() } } }
quelle
Ich habe die Antwort von @Chase Holland verwendet und die Springboard-Klasse nach demselben Ansatz aktualisiert, um den Inhalt und die Einstellungen mithilfe der Einstellungen-App zurückzusetzen. Dies ist nützlich, wenn Sie Berechtigungsdialoge zurücksetzen müssen.
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() } } } }
quelle
XCUIApplication(privateWithPath: …)
ist in Swift 3 nicht ausgesetzt, wie es aussieht?Ich sehe viele Antworten, um Ihre App in
setUp
odertearDown
von Ihrem Test zu deinstallieren .Sie können Ihre App jedoch problemlos deinstallieren, bevor Sie Ihre Tests starten, indem Sie Ihrem Testziel eine Ausführungsskriptphase hinzufügen.
Um dies zu tun:
Ersetzen Sie dann den Platzhalter
# Type a script or drag a script file from your workspace to insert its path.
durch den Befehl:xcrun simctl boot ${TARGET_DEVICE_IDENTIFIER} xcrun simctl uninstall ${TARGET_DEVICE_IDENTIFIER} YOUR_APP_BUNDLE
quelle
xcrun simctl create
und dann Ihre Tests auf diesen Simulatoren starten, wobei mehrere Ziele alsxcodebuild
test
Befehl festgelegt werden. Wenn es nicht funktioniert, versuchen Option-only-testing:
vonxcodebuild test-without-building
zu trennen UITests selbst.Ab Xcode 11.4, wenn alles , was Sie wollen , ist Berechtigungen zurückgesetzt, die Sie verwenden können
resetAuthorizationStatus(for:)
auf InstanzXCUIApplication
finden https://developer.apple.com/documentation/xctest/xcuiapplication/3526066-resetauthorizationstatusforresouSie können
simctl
bei Bedarf auch verwenden , zitiert aus den Xcode 11.4-Versionshinweisen :quelle
Für iOS 11-Sims und -Up habe ich eine geringfügige Änderung vorgenommen, um auf das "x" -Symbol zu tippen und wo wir gemäß dem vorgeschlagenen Fix @Code Monkey tippen. Fix funktioniert gut auf 10.3- und 11.2-Telefonsimulationen. Für die Aufzeichnung verwende ich Swift 3. Ich dachte, ich würde einen Code durchgehen, um ihn zu kopieren und einzufügen, um das Update ein wenig einfacher zu finden. :) :)
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() } } }
quelle
Dies scheint für mich unter iOS 12.1 und Simulator zu funktionieren
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) } }
quelle
Löschen auf der Basis von iOS 13.1 / Swift 5.1- Benutzeroberfläche
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) }
quelle
Aktualisieren der Antwort von Craig Fishers für Swift 4. Aktualisiert für das iPad in der Landschaft, funktioniert wahrscheinlich nur für die verbleibende Landschaft.
XCTest importieren
Klasse Sprungbrett {
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) } }
}}
quelle
Hier ist eine Objective C-Version der obigen Antworten zum Löschen einer App und zum Zurücksetzen von Warnungen (getestet unter iOS 11 und 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]; }
quelle
Dies funktioniert für mich in allen Betriebssystemversionen (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() } }
quelle