Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

Обновить


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

Вот пример кода из класса 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"

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

Ответ 2

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

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

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

  2. Shell Script. Запустите тесты из сценария оболочки. Для сброса симулятора (или удаления приложения) используйте xcrun simctl erase all или xcrun simctl uninstall <device> <app identifier> или аналогичные между каждым запуском теста. fooobar.com/questions/114007/...

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

Б. Запустите параметр. [Тест пользовательского интерфейса] Как отметил Виктор Ронин, аргумент может быть передан из теста 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() в работающее приложение и могут напрямую вызывать некоторую myResetApplication() в приложении. Предостережение: модульные тесты по умолчанию запускаются после загрузки основного экрана. см. Последовательность тестовой загрузки. Однако комплекты тестов пользовательского интерфейса выполняются как процесс, внешний по отношению к тестируемому приложению. Итак, то, что работает в модульном тесте, дает ошибку ссылки в тесте пользовательского интерфейса.

class AppResetUnitTests: XCTestCase {

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

Ответ 3

Обновлен для 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

обновленный класс Springboard

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

Ответ 4

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

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

    если NSProcessInfo.processInfo(). arguments.contains( "YOUR_FLAG_NAME_HERE" ) { // Делаем очистку здесь }

Ответ 5

Я использовал @ODM ответ, но модифицировал его для работы в Swift 4. NB: некоторые ответы 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()
        }
    }
  }
}

Ответ 6

Я использовал ответ @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()
            }
        }
    }
}

Ответ 7

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

Ответ 8

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

Ответ 9

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

static let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!

class func deleteApp() {
    XCUIApplication().terminate()

    springboard.activate()
    let icons = springboard.icons.matching(identifier: "YourAppTitle")

    let icon = icons.firstMatch
    icon.press(forDuration: 1.3)

    springboard.buttons["Rearrange Apps"].tap()

    Thread.sleep(forTimeInterval: 1)

    icon.buttons["DeleteButton"].tap()

    let deleteButton = springboard.alerts.buttons["Delete"].firstMatch
    XCTAssert(deleteButton.waitForExistence(timeout: 3))
    deleteButton.tap()

}

Ответ 10

Опираясь на ответы 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()

Ответ 11

Обновление 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)
    }
}

}

Ответ 12

Вот версия 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];
}