Gibt es eine Möglichkeit, die App zwischen Tests in der Swift XCTest-Benutzeroberfläche zurückzusetzen?


74

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.


1
Komisch, dass ich diese Frage nicht finden konnte, als ich diese schrieb. Ich beschuldige SO für schlechte Abfrageergebnisse. Wie auch immer, zögern Sie nicht, diesen "Betrüger" zu löschen. Ich habe das Problem vor einiger Zeit mit einer eleganten Lösung mit Überholspur / gitlab-ci.ymlDatei gelöst .
Laser Hawk

2
Wie haben Sie es geschafft, es mit der Datei gitlab-ci.yml zu lösen? Könnten Sie bitte etwas teilen.
Dhruv

Antworten:


83

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 .


2
Gute Antwort! Gibt es eine intelligentere Möglichkeit, "MyAppName" zu erhalten? Ich habe es versucht 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.
Avi

1
Kann dies nach dem Start eines Tests durchgeführt werden? Wenn ja, wie kann ich die App neu installieren?
Guig

3
Die kleine "x" -Taste hat die Eingabehilfen-ID "DeleteButton" und kann durch Ausführen icon.buttons["DeleteButton"].tap()nach langem Drücken gedrückt werden, anstatt die zu verwenden CGVector.
pmick

6
Ab iOS 13.4 erhalte ich jetzt eine Fehlermeldung, wenn ich app.launch()danach Springboard.deleteMyApp()The request was denied by service delegate (SBMainWorkspace) for reason: NotFound ("Application "com.serpentisei.studyjapanese" is unknown to FrontBoard").
anrufe

3
Der obige Fehler scheint in Xcode 11.4 eingeführt zu sein, unabhängig von der iOS-Version des Simulators. Es tritt immer dann auf, wenn Sie Ihre Anwendung starten, die App mit der oben beschriebenen Technik löschen und dann erneut versuchen, sie zu starten (auch wenn dies über separate Tests hinweg erfolgt). Ich habe FB7666257 eingereicht.
Chris Vasselli

40

Zu diesem Zeitpunkt wird die öffentliche API ist in Xcode und dem Simulator erscheint keine Methode aufrufbar hat aus setUp()und tearDown() XCTextSubklassen „Reset Inhalte und Einstellungen“ für den Simulator.

Es gibt andere mögliche Ansätze, die öffentliche APIs verwenden:

  1. 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.

  2. Shell-Skript . Führen Sie die Tests über ein Shell-Skript aus. Verwenden Sie xcrun simctl erase alloder xcrun 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>]
  1. Xcode-Schemaskriptaktion . Hinzufügen xcrun simctl erase all(oder xcrun 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 Befehl xcrun simctl erase allkann 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 , XCUIApplicationin XCTestRoutinen setUp(), tearDown()oder testSomething().

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 sein NSProcessInfo.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

xcrun simctl erase allist ein toller Vorschlag - danke!
Bill

Anstelle der dritten Lösung können Sie Ihre App in der Testziel-Erstellungsphase problemlos deinstallieren. Siehe meine Antwort.
Vmeyer

15

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

Funktioniert perfekt!
Wilmar

Wirklich nett ! Funktioniert perfekt
Sn0wfreeze

1
Wenn ich dies auf einem Gerät ausführe, erhalte ich manchmal die Meldung "Diesem Computer vertrauen?" Warnung, die verhindert, dass meine App gestartet wird.
kjam

Funktioniert dies immer noch im neuesten Xcode / XCtest? Und wenn ja, wie / wo initiieren Sie die deleteMyApp ()?
BadmintonCat

... funktioniert! Tolle!
BadmintonCat

11

Sie können Ihre App bitten, sich selbst zu "bereinigen"

  • Sie verwenden XCUIApplication.launchArguments, um ein Flag zu setzen

  • In AppDelegate überprüfen Sie

    if NSProcessInfo.processInfo (). argument.contains ("YOUR_FLAG_NAME_HERE") {// Hier bereinigen}


Dies ist ein großer Schritt, um die Methode launchArgruments zu verstehen. Vielen Dank für diesen Einblick. Es führte mich zu nshipster.com/launch-arguments-and-environment-variables. Bitte entschuldigen Sie meine Noobness hier. Wenn ich das Schema bearbeite und ein Startargument erstelle, wo und wie lege ich die Besonderheiten dieses neu erstellten Arguments fest? Ich sehe, wie man es als Token an die Tests übergibt, aber wie in meinem Fall möchte ich ein Skript ausführen, das den Status des Simulators zurücksetzt. Könnten Sie eine detailliertere Erklärung zur Erstellung des eigentlichen Arguments geben?
Laser Hawk

@jermobileqa Zunächst müssen Sie sich nicht entschuldigen. Ich bin etwas in einem ähnlichen Boot wie Sie. Ich habe heute buchstäblich angefangen, neue UI-Tests zu verwenden. Und ich habe gesucht, wie ich genau dieses Problem lösen kann. Ich bin aktuell gesetzt XCUIApplication.launchArguments in setUp Methode für meine Tests und überprüfe es in AppDelegate in func Anwendung. Ich habe das Schema nicht geändert. Infolgedessen kann ich mit Command + U einfach Tests von XCode ausführen. Dieses Argument wird verwendet, und meine Anwendung bereinigt alles, was es beibehalten hat.
Victor Ronin

Wie setze ich App-Berechtigungen zurück?
LetynSOFT

9

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

3
Ich musste dies weiter ändern, da es auf einem "Plus" -Modelltelefon aufgrund von Skalierungsänderungen nicht funktioniert. Wenn Sie die Konstanten "3" durch "3 * UIScreen.main.scale" ersetzen, funktioniert dies ordnungsgemäß.
SlashDevSlashGnoll

Ich kann anscheinend nicht in der Lage sein, meine iPads dazu zu bringen, die x-Taste zu drücken. Hat jemand Glück auf einem iPad gehabt?
bj97301

Ich konnte dies beheben, wie in meiner Antwort unten gezeigt.
bj97301

9

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

8

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

XCUIApplication(privateWithPath: …)ist in Swift 3 nicht ausgesetzt, wie es aussieht?
Buildsceeded am

1
@buildsucceeded Sie müssen einen Bridging-Header erstellen und private Header importieren. Überprüfen Sie meine Antwort auf die richtige Implementierung.
JustinM

6

Ich sehe viele Antworten, um Ihre App in setUpoder tearDownvon 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:

  1. Wählen Sie das Xcode-Projekt Ihrer Anwendung aus
  2. Wählen Sie Ihr Testziel
  3. Wählen Sie "Phasen erstellen"
  4. Tippen Sie auf "+" und "New Run Script Phase".

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

Haben Sie eine Idee, wie Sie die ID des Klons erhalten, in dem der Test ausgeführt wird? Sie möchten diesen Klon nur löschen, während andere Klone noch ihre Tests ausführen
Alexandre G

Hallo @AlexandreG, über welchen Klon sprichst du? Sprechen Sie über den Simulator?
Vmeyer

Ja, wenn Sie Xcode 10+ Paralleltests verwenden, werden die Tests auf Simulator-Klonen ausgeführt, die ihre eigenen IDs haben. Mit Hilfe anderer habe ich herausgefunden, wie man sie löscht stackoverflow.com/questions/52660037/…, aber ich weiß nicht, wie ich herausfinden soll , welche vor dem Test gelöscht werden sollen
Alexandre G

Ich weiß nicht, wie Sie diese Klon-IDs erhalten können. Xcode sollte jedoch Klone Ihres Zielsimulators erstellen. Wenn Sie also Ihre App auf dem Zielsimulator löschen, sollte sie auch auf Klonen gelöscht werden.
vmeyer

1
Wenn Sie Klone wirklich verwalten möchten, können Sie mithilfe der CLI selbst Simulatoren erstellen xcrun simctl createund dann Ihre Tests auf diesen Simulatoren starten, wobei mehrere Ziele als xcodebuild testBefehl festgelegt werden. Wenn es nicht funktioniert, versuchen Option -only-testing:von xcodebuild test-without-buildingzu trennen UITests selbst.
vmeyer

5

Ab Xcode 11.4, wenn alles , was Sie wollen , ist Berechtigungen zurückgesetzt, die Sie verwenden können resetAuthorizationStatus(for:)auf Instanz XCUIApplicationfinden https://developer.apple.com/documentation/xctest/xcuiapplication/3526066-resetauthorizationstatusforresou

Sie können simctlbei Bedarf auch verwenden , zitiert aus den Xcode 11.4-Versionshinweisen :

simctl unterstützt jetzt das Ändern von Datenschutzberechtigungen. Sie können Datenschutzberechtigungen ändern, um bekannte Status zu Testzwecken zu erstellen. So können Sie beispielsweise einer Beispiel-App ohne Aufforderung den Zugriff auf die Fotobibliothek ermöglichen:
xcrun simctl privacy <device> grant photos com.example.app

So setzen Sie alle Berechtigungen auf die Standardeinstellungen zurück, als wäre die App noch nie zuvor installiert worden :
xcrun simctl privacy <device> reset all com.example.app.


4
Ärgerlicherweise scheint dies nicht für Benachrichtigungsberechtigungen zu gelten.
Bencallis

@bencallis Hey, hast du eine Möglichkeit gefunden, die Benachrichtigungsberechtigung zurückzusetzen, ohne die App zu löschen?
DmitrievichR

Ärgerlicherweise nicht!
Bencallis

4

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

3

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

3

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

3
Funktioniert das zuverlässig für Sie? Es gibt zeitweise Probleme, den "DeleteButton" für mich zu finden.
Bencallis

@bencallis gleiche
Ryan Maloney

du hast recht, es ist auch für mich schuppig. Ich denke darüber nach, den gesamten Simulator zu löschen, bis wir nicht mehr den richtigen Weg finden.
Akos Komuves

Ich sehe auch falsche Koordinaten für das Symbol. I ist x: -2, y: 4. In diesem Fall ist deleteButton nicht vorhanden. Ich habe versucht, Elemente Baum zu aktualisieren, aber es hilft nicht.
Degard

Ich habe die Methode aktualisiert, bitte versuchen Sie diese. Im Moment funktioniert es für mich. Aber 13.2 vor der Haustür und das wird veraltet sein, denke ich
Akos Komuves

0

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

}}


0

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];
}

0

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()
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.