it-swarm.com.ru

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

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

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

53
JJacquet

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

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

Обновление


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

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

Свифт 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()
        }
    }
 }

Свифт 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()
        }
    }
 }

А потом:

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

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

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

Note: Начиная с Xcode 10, XCUIApplication(bundleIdentifier:) теперь предоставляется Apple, а частные заголовки больше не нужны.

59
Chase Holland

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

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

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

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

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

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

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

B. Запустить параметр. [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
35
l --marc l

Обновлено для 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()
      }
     }
    }
   }
14
JustinM

Я использовал @ ODManswer , но изменил его, чтобы он работал для 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()
        }
    }
  }
}
9
DeeMickSee

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

  • Вы используете XCUIApplication.launchArguments, чтобы установить флаг
  • В AppDelegate вы проверяете

    if NSProcessInfo.processInfo (). arguments.contains ("YOUR_FLAG_NAME_HERE") { // Уборка здесь }

9
Victor Ronin

Я использовал @Chase Holland answer и обновил класс 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()
            }
        }
    }
}
7
odm

Для 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()
        }
    }
}
3
Craig Fisher

Кажется, это работает для меня на 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)
    }
}
1
Peacemoon

Опираясь на ответы Чейза Холланда и 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()
1
bj97301

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

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

класс Springboard {

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
Aaron