Есть ли способ перезагрузить приложение между тестами в Swift XCTest UI?

Есть ли вызов API в XCTest, который я могу поместить в setUP() или tearDown() для сброса приложения между тестами? Я посмотрел в точечный синтаксис XCUIApplication, и все, что я видел, было.launch()

ИЛИ есть ли способ вызвать сценарий оболочки в Swift? Затем я мог бы вызвать промежуточные методы тестирования xcrun для сброса симулятора.

19 ответов

Вы можете добавить фазу "Выполнить сценарий", чтобы построить фазы в тестовой цели, чтобы удалить приложение перед запуском юнит-тестов , хотя, к сожалению, это не между тестовыми случаями.

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

Обновить


Между тестами вы можете удалить приложение через Springboard на этапе tearDown. Хотя это требует использования частного заголовка от XCTest. (Дамп заголовка доступен из WebDriverAgent Facebook здесь.)

Вот пример кода из класса Springboard для удаления приложения из Springboard с помощью нажатия и удержания:

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

А потом:

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

Частные заголовки были импортированы в заголовок моста Swift. Вам нужно будет импортировать:

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

В это время общедоступный API в Xcode 7 & 8 и симуляторе не появляется, имеет какой-либо метод, вызываемый из setUp() а также tearDown()XCText подклассы "Сбросить содержимое и настройки" для симулятора.

Есть и другие возможные подходы, которые используют публичные API:

  1. Код приложения. Добавь немного myResetApplication() код приложения для перевода приложения в известное состояние. Однако управление состоянием устройства (симулятора) ограничено изолированной программной средой приложения... которая не сильно помогает за пределами приложения. Этот подход подходит для очистки управляемой приложением персистентности.

  2. Shell Script. Запустите тесты из сценария оболочки. использование xcrun simctl erase all или же xcrun simctl uninstall <device> <app identifier> или подобное между каждым тестовым прогоном для сброса симулятора (или удаления приложения). см. Stackru: "Как я могу сбросить симулятор iOS из командной строки?"

    macos> xcrun simctl --help
    # can uninstall a single application
    macos> xcrun simctl uninstall --help  
    # Usage: simctl uninstall <device> <app identifier>
  1. Действие схемы XCode. добавлять xcrun simctl erase all (или же xcrun simctl erase <DEVICE_UUID>) или аналогично разделу "Проверка схемы". Выберите меню "Продукт"> "Схема"> "Редактировать схему…". Разверните раздел "Проверка схемы". Выберите "Предварительные действия" в разделе "Тест". Нажмите (+), чтобы добавить "New Run Script Action". Команда xcrun simctl erase all можно вводить напрямую, не требуя какого-либо внешнего скрипта.

Варианты вызова 1. Код приложения для сброса приложения:

А. Интерфейс приложения. [UI Test] Предоставьте кнопку сброса или другое действие пользовательского интерфейса, которое сбрасывает приложение. Элемент пользовательского интерфейса может быть реализован через XCUIApplication в XCTest подпрограммы setUp(), tearDown() или же testSomething(),

Б. Запустите параметр. [UI Test] Как отметил Виктор Ронин, аргумент может быть передан из теста setUp()...

class AppResetUITests: XCTestCase {

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

... быть принятым AppDelegate...

class AppDelegate: UIResponder, UIApplicationDelegate {

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

C. Параметр схемы XCode. [UI Test, Unit Test] Выберите меню "Продукт"> "Схема"> "Редактировать схему…". Разверните раздел "Выполнение схемы". (+) Добавить некоторый параметр, например MY_UI_TEST_MODE, Параметр будет доступен в NSProcessInfo.processInfo(),

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

Z. Прямой звонок. [Unit Test] Пакеты модульных тестов внедряются в работающее приложение и могут напрямую вызывать некоторые myResetApplication() рутина в приложении. Предостережение: модульные тесты по умолчанию запускаются после загрузки основного экрана. см. Последовательность тестовой загрузки. Однако комплекты тестов пользовательского интерфейса выполняются как процесс, внешний по отношению к тестируемому приложению. Итак, то, что работает в модульном тесте, дает ошибку ссылки в тесте пользовательского интерфейса.

class AppResetUnitTests: XCTestCase {

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

Обновлено для swift 3.1 / xcode 8.3

создать промежуточный заголовок в тестовой цели:

#import <XCTest/XCUIApplication.h>
#import <XCTest/XCUIElement.h>

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

обновленный класс трамплина

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

Решение для 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()
        }
    }
}

Вы можете попросить ваше приложение "очистить" себя

  • Ты используешь XCUIApplication.launchArguments установить какой-то флаг
  • В AppDelegate вы проверяете

    if NSProcessInfo.processInfo().arguments.contains("YOUR_FLAG_NAME_HERE") { // Выполнить очистку здесь}

Я вижу много ответов на удаление вашего приложения в setUp или tearDown вашего теста.

Но вы можете легко удалить свое приложение перед запуском тестов, добавив этап сценария запуска в свою тестовую цель.

Для этого:

  1. Выберите проект Xcode вашего приложения
  2. Выберите свою тестовую цель
  3. Выберите "Фазы сборки"
  4. Нажмите на "+" и "Фаза нового запуска сценария".

Затем замените заполнитель # Type a script or drag a script file from your workspace to insert its path. командой:

xcrun simctl boot ${TARGET_DEVICE_IDENTIFIER}
xcrun simctl uninstall ${TARGET_DEVICE_IDENTIFIER} YOUR_APP_BUNDLE

Я использовал ответ @ odm, но изменил его, чтобы он работал для Swift 4. Примечание: некоторые ответы S/O не дифференцируют версии Swift, которые иногда имеют довольно фундаментальные различия. Я проверил это на симуляторе iPhone 7 и iPad Air в портретной ориентации, и это сработало для моего приложения.

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

Начиная с Xcode 11.4, если все, что вам нужно, это сбросить разрешения, вы можете использовать resetAuthorizationStatus(for:) по примеру XCUIApplicationсм. https://developer.apple.com/documentation/xctest/xcuiapplication/3526066-resetauthorizationstatusforresou

Вы также можете использовать simctlпри необходимости, цитируется из Xcode 11.4 Release Notes:

simctl теперь поддерживает изменение разрешений конфиденциальности. Вы можете изменить разрешения конфиденциальности, чтобы создать известные состояния для целей тестирования. Например, чтобы разрешить приложению-примеру получить доступ к библиотеке фотографий без каких-либо запросов:
xcrun simctl privacy <device> grant photos com.example.app

Чтобы сбросить все разрешения до значений по умолчанию, как если бы приложение никогда раньше не устанавливалось:
xcrun simctl privacy <device> reset all com.example.app.

Я использовал ответ @Chase Holland и обновил класс Springboard, следуя тому же подходу, чтобы сбросить содержимое и настройки с помощью приложения "Настройки". Это полезно, когда вам нужно сбросить разрешения диалогов.

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

Рабочее решение для iOS14

      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["Remove App"].tap()

        let deleteButton = springboardApp.alerts.buttons["Delete App"].firstMatch
        if deleteButton.waitForExistence(timeout: 5) {
            deleteButton.tap()
            springboardApp.alerts.buttons["Delete"].tap()
        }
    }
}

Есть так много вариантов ответа, даже не уверен, стоит ли добавлять к нему, но на случай, если кому-то понадобится универсальное решение:

iOS 14.6 и 15 бета

          class func deleteApp() {
    XCUIApplication().terminate()
    
    // Force delete the app from the springboard
    let icon = springboard.icons["APP_NAME"]
    if icon.exists {
        icon.press(forDuration: 1.3)
        
        springboard.buttons["Remove App"].tap()
        springboard.alerts.buttons["Delete App"].tap()
        springboard.alerts.buttons["Delete"].tap()
        
        // Press home once to make the icons stop wiggling
        XCUIDevice.shared.press(.home)
    }
}

Для iOS 11 sims up я сделал очень маленькую модификацию, чтобы нажать значок "x" и место, где мы нажимаем в соответствии с исправлением, предложенным Monkey @Code. Фикс хорошо работает как на 10.3, так и на 11.2 телефонных симках. Для записи, я использую Swift 3. Думаю, что я прошел через некоторый код, чтобы скопировать и вставить, чтобы найти исправление немного проще.:)

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

Кажется, это работает для меня на iOS 12.1 и симулятор

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

Удаление iOS 13.1/Swift 5.1 на основе пользовательского интерфейса

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

Опираясь на ответы Chase Holland и odm, я смог избежать длинных нажатий и +3 смещений bs, удалив приложение в настройках, таких как 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()
        }
    }
}

Использование:

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

Обновление Craig Fishers отвечает за Swift 4. Обновлен для iPad в альбомной ориентации, вероятно, работает только для оставленной альбомной ориентации.

импортировать XCTest

трамплин класса {

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

}

После некоторых экспериментов я закончил реализацию более четкого решения, охватывающего разные версии iOS:

      import XCTest

private enum Constants {
  static let springboardBundleIdentifier = "com.apple.springboard"
  static let appIconPressShortDuration: TimeInterval = 2.0
  static let appIconPressLongDuration: TimeInterval = 3.0
  static let deleteAppButton = "Delete App"
  static let removeAppButton = "Remove App"
  static let deleteButton = "Delete"
  static let deleteButtonVectorOffset: CGFloat = 3.0
}

final class Springboard {
  
  static let springboard = XCUIApplication(bundleIdentifier: Constants.springboardBundleIdentifier)
  
  static func deleteApp() {
    let app = XCUIApplication()
    if app.state != .notRunning {
      app.terminate()
    }
    
    let appIcon = self.springboard.icons[app.label]
    guard appIcon.exists else {
      return
    }
    
    appIcon.press(forDuration: Constants.appIconPressShortDuration)
    
    if let deleteListButton = self.deleteListButton() {
      deleteListButton.tap()
      self.pressDeleteAlertButtons()
    } else {
      appIcon.press(forDuration: Constants.appIconPressLongDuration)
      self.pressDeleteTopLeftButton(for: appIcon)
      self.pressDeleteAlertButtons()
    }
  }
  
}

private extension Springboard {
  
  static func pressDeleteAlertButtons() {
    self.pressDeleteAlertButton(self.deleteAppAlertButton())
    self.pressDeleteAlertButton(self.deleteAlertButton())
  }
  
  static func pressDeleteAlertButton(_ button: XCUIElement?) {
    guard let button = button,
          button.waitForExistence(timeout: TestConstants.minWaitingTime)
    else {
      return
    }
    
    button.tap()
  }
  
  static func pressDeleteTopLeftButton(for appIcon: XCUIElement) {
    let iconFrame = appIcon.frame
    let springboardFrame = self.springboard.frame
    
    let deleteButtonVector = CGVector(
      dx: (iconFrame.minX + Constants.deleteButtonVectorOffset) / springboardFrame.maxX,
      dy: (iconFrame.minY + Constants.deleteButtonVectorOffset) / springboardFrame.maxY)
    
    let deleteButtonCoordinate = self.springboard.coordinate(withNormalizedOffset: deleteButtonVector)
    deleteButtonCoordinate.tap()
  }
  
}

private extension Springboard {
  
  static func deleteListButton() -> XCUIElement? {
    let removeListButton = self.springboard.buttons[Constants.removeAppButton]
    let deleteListButton = self.springboard.buttons[Constants.deleteAppButton]
    
    if removeListButton.exists {
      return removeListButton
    } else if deleteListButton.exists {
      return deleteListButton
    }
    
    return nil
  }
  
  static func deleteAppAlertButton() -> XCUIElement? {
    let deleteAppButton = self.springboard.alerts.buttons[Constants.deleteAppButton]
    
    if deleteAppButton.exists {
      return deleteAppButton
    }
    
    return nil
  }
  
  static func deleteAlertButton() -> XCUIElement? {
    let deleteButton = self.springboard.alerts.buttons[Constants.deleteButton]
    
    if deleteButton.exists {
      return deleteButton
    }
    
    return nil
  }
  
}

Вот версия Objective C вышеперечисленных ответов для удаления приложения и сброса предупреждений (проверено на iOS 11 и 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];
}

Это работает для меня во всех версиях ОС (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()
        }
    }
Другие вопросы по тегам