2015-10-13 33 views
42

XCTest中是否有API調用,我可以放入setUP()或tearDown()來重置測試之間的應用程序?我查看了XCUIApplication的點語法,我只看到了.launch()有沒有辦法在Xcode 7中的Swift XCTest UI中的測試之間重置應用程序?

或者有沒有辦法在Swift中調用shell腳本?然後我可以調用xcrun中間的測試方法來重置模擬器。

+0

的可能的複製[我如何在Xcode 7 UI測試每次測試後重置應用程序數據?](http://stackoverflow.com/questions/32351149/how-do-i-reset-每個測試後的應用程序數據 - 用-xcode-7-ui-testing) –

+1

有趣的我在找到這個問題時找不到這個問題。我責怪糟糕的查詢結果。任何人,隨時刪除這個「笨蛋」,我前一段時間解決了這個問題,使用快速通道/'gitlab-ci.yml'文件的優雅解決方案。 – JJacquet

+0

聽起來很酷@JJacquet會檢查出來 –

回答

31

此時,在Xcode 7 & 8的公共API和模擬器不出現具有從setUp()tearDown()XCText可調用子類的任何方法,以「復位內容和設置」模擬器。

存在使用公共API其他可能的方法:

  1. 應用代碼。添加一些myResetApplication()應用程序代碼以使應用程序處於已知狀態。但是,設備(模擬器)狀態控制受應用程序沙箱的限制......在應用程序之外沒有太多幫助。這種方法對於清除應用程序可控持久性是可以的。

  2. Shell腳本。從shell腳本運行測試。在每次測試運行之間使用xcrun simctl erase allxcrun simctl uninstall <device> <app identifier>或類似軟件重置模擬器(或卸載應用程序)see StackOverflow: "How can I reset the iOS Simulator from the command line?"

macos> xcrun simctl --help 
    # can uninstall a single application 
    macos> xcrun simctl uninstall --help 
    # Usage: simctl uninstall <device> <app identifier> 
  • Xcode的模式動作。添加xcrun simctl erase all(或xcrun simctl erase <DEVICE_UUID>)或類似的計劃測試部分。選擇產品>計劃>編輯計劃...菜單。展開Scheme測試部分。選擇測試部分下的預先操作。點擊(+)添加「新建腳本動作」。命令xcrun simctl erase all可以直接輸入,而不需要任何外部腳本。用於調用1.應用程序代碼重置應用

  • 選項:

    A. 應用程序UI[用戶界面測試]提供重置按鈕或其他UI操作來重置應用程序。 UI元素可以通過XCTest例程setUp()tearDown()testSomething()中的XCUIApplication來行使。

    B. 啓動參數[UI試驗]如由Victor羅寧所指出的,一個參數可以從測試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測試,單元測試]選擇產品>方案>編輯方案...菜單。展開Scheme Run部分。 (+)添加一些參數,如MY_UI_TEST_MODE。該參數將在NSProcessInfo.processInfo()中提供。

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

    Z. 直接呼叫[單元測試]單元測試包被注入到正在運行的應用程序中,並可以直接在應用程序中調用一些myResetApplication()例程。警告:默認單元測試在主屏幕加載後運行。 see Test Load Sequence但是,UI測試包作爲被測試應用程序的外部進程運行。所以,單元測試中的工作會在UI測試中出現鏈接錯誤。

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

    檢查第三種解決方案 - 它不起作用。 Xcode 7. – RaffAl

    +0

    'xcrun simctl erase all'是一個巨大的建議 - 謝謝! – Bill

    7

    你可以問問你的應用程序,以 「清理」 本身

    • 您使用XCUIApplication.launchArguments設置一些標誌
    • 在AppDelegate中您檢查

      如果NSProcessInfo.processInfo()。 arguments.contains(「YOUR_FLAG_NAME_HERE」){ //在這裏做一個清理 }

    +0

    這是向我瞭解launchArgruments方法邁出的巨大一步。謝謝你的洞察力。它引導我去http://nshipster.com/launch-arguments-and-environment-variables/ 請原諒我在這裏的noobness。如果我編輯該方案並創建啓動參數,以及如何設置新創建的參數的具體內容?我看到如何將它作爲標記傳遞給測試,但就像我的情況一樣,我想運行一個腳本來重置模擬器的狀態。你能否更詳細地解釋一下實際論證的創造? – JJacquet

    +0

    @jermobileqa首先,不需要道歉。我在某種程度上與你相似。我今天從字面上開始使用新的UI測試。我正在研究如何解決這個問題。我目前在我的測試的setUp方法中設置了XCUIApplication.launchArguments,並在func應用程序的AppDelegate中檢查它。我沒有修改架構。因此,我只需使用Command + U從XCode運行測試,它將使用此參數,我的應用程序將清除它所保留的所有內容。 –

    53

    不幸的是,這不在測試用例之間,雖然,但在運行單元測試之前卸載應用程序,您可以添加「運行腳本」階段以構建測試目標階段。

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

    更新


    測試之間,你可以通過在拆卸階段的跳板刪除的應用。雖然,這確實需要使用來自XCTest的私有頭文件。 (報頭轉儲可從Facebook's WebDriverAgent here。)

    下面是從一個跳板類一些示例代碼經由抽頭從跳板刪除應用和保持:

    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" 
    
    +0

    這工作得很好。您只需將WebDriveAgent中的PrivateHeaders文件夾添加到您的項目中(無需添加全部內容),然後製作一個橋接標頭,以#import提供所需的標頭。 –

    +3

    這應該是公認的答案。 –

    +0

    很好的回答!有沒有更聰明的方法來獲取「MyAppName」?我試過使用'NSBundle-bundleWithIdentifier/Path',但測試應用程序沒有對應用程序包的引用。我的項目有很多目標,每個都有不同的名稱,我希望能夠跨所有目標使用Springboard類。 – Avi

    6

    我用@Chase荷蘭answer和更新的跳板類遵循相同的方法來重新使用設置應用程序的內容和設置。這在您需要重置權限對話框時非常有用。

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

    'XCUIApplication(privateWithPath:...)'在Swift 3中沒有公開,它看起來像? – buildsucceeded

    +1

    @buildsucceeded你需要創建一個橋接頭並導入私有頭文件。檢查我的答案是否正確實施。 – JustinM

    14

    更新爲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() 
         } 
        } 
        } 
        } 
    
    +0

    完美無缺! – Wilmar

    +0

    真的很好!完美運行 – Sn0wfreeze

    +1

    在設備上運行時,有時會得到'信任此計算機?'警報,這會阻止我的應用程序啓動。 – kjam

    3

    我用@ODManswer,但修改它用於夫特4. NB工作:一些S/O答案沒有區分Swift版本,這些版本有時具有相當根本性的差異。我已經在iPhone 7模擬器和iPad Air模擬器上進行了縱向測試,並且適用於我的應用。

    斯威夫特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() 
         } 
        } 
        } 
    } 
    
    +1

    我不得不進一步改變這一點,因爲它不適用於「Plus」型號的手機,原因是縮放更改。如果你用「3 * UIScreen.main.scale」替換常量「3」,那麼它可以正常工作。 –

    +0

    它不適用於iPhone X – Degard

    +0

    @CodeMonkey對iPhone X的評論也很好,謝謝... – Degard

    0

    對於iOS模擬遊戲11的時候,我做了曾經如此輕微的修改,以挖掘「X」圖標,在這裏我們每修復@code猴建議挖掘。 Fix在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() 
         } 
        } 
    } 
    
    相關問題