XCTest内に、テストの間にアプリをリセットするためにsetUP()またはtearDown()に入れることができるAPI呼び出しがありますか? XCUIApplicationのドット構文を見て、見たのは.launch()だけでした
または、Swiftでシェルスクリプトを呼び出す方法はありますか?次に、xcrunをテストメソッドの中間で呼び出して、シミュレータをリセットできます。
「スクリプトの実行」フェーズを追加して、テストターゲットにビルドフェーズを作成し、単体テストを実行する前にアプリをアンインストールして、 残念ながら、これはテストケースの間ではありませんが、。
/usr/bin/xcrun simctl uninstall booted com.mycompany.bundleId
更新
テスト間で、tearDownフェーズでSpringboardを介してappを削除できます。ただし、これにはXCTestのプライベートヘッダーを使用する必要があります。 (ヘッダーダンプは FacebookのWebDriverAgentはこちら から入手できます。)
以下は、タップアンドホールドでSpringboardからアプリを削除するSpringboardクラスのサンプルコードです。
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()
}
}
}
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:)
はAppleによって公開され、プライベートヘッダーはでなくなりました必要です。
現時点では、Xcode 7および8のpublic APIおよびシミュレーターには、setUp()
およびtearDown()
XCText
サブクラスから呼び出し可能なメソッドがありません。シミュレーターの「内容と設定をリセットする」。
パブリックAPIを使用する他の可能なアプローチがあります。
アプリケーションコード。 myResetApplication()
アプリケーションコードを追加して、アプリケーションを既知の状態にします。ただし、デバイス(シミュレーター)の状態制御は、アプリケーションサンドボックスによって制限されます...これは、アプリケーションの外部ではあまり役に立ちません。このアプローチは、アプリケーションで制御可能な永続性をクリアするのに適しています。
シェルスクリプト。シェルスクリプトからテストを実行します。各テスト実行の間に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>
xcrun simctl erase all
(またはxcrun simctl erase <DEVICE_UUID>
)または同様のスキームテストセクションを追加します。 [製品]> [スキーム]> [スキームの編集…]メニューを選択します。 [スキームテスト]セクションを展開します。 [テスト]セクションで[事前アクション]を選択します。 [+]をクリックして、[新しいスクリプトの実行アクション]を追加します。コマンドxcrun simctl erase all
は、外部スクリプトを必要とせずに直接入力できます。呼び出すためのオプション1。アプリケーションをリセットするアプリケーションコード:
A.アプリケーションUI。 [UI Test]アプリケーションをリセットするリセットボタンまたはその他のUIアクションを提供します。 UI要素は、XCUIApplication
ルーチンsetUp()
、tearDown()
、またはtestSomething()
のXCTest
を介して実行できます。
B.パラメータの起動。 [UI Test]Victor Roninが述べたように、テストから引数を渡すことができます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テスト、単体テスト][製品]> [スキーム]> [スキームの編集…]メニューを選択します。スキーム実行セクションを展開します。 (+)MY_UI_TEST_MODE
などのパラメーターを追加します。パラメーターはNSProcessInfo.processInfo()
で使用可能になります。
// ... in application
let args = NSProcessInfo.processInfo().arguments
if args.contains("MY_UI_TEST_MODE") {
myResetApplication()
}
Z.ダイレクトコール。 [単体テスト]単体テストバンドルは実行中のアプリケーションに挿入され、アプリケーションのmyResetApplication()
ルーチンを直接呼び出すことができます。警告:デフォルトの単体テストは、メイン画面が読み込まれた後に実行されます。 ロードシーケンスのテストを参照 ただし、UIテストバンドルは、テスト対象のアプリケーションの外部プロセスとして実行されます。そのため、ユニットテストで機能するものは、UIテストでリンクエラーを返します。
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
更新された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()
}
}
}
}
@ 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()
}
}
}
}
アプリに「クリーンアップ」を依頼することができます
XCUIApplication.launchArguments
を使用してフラグを設定しますAppDelegateで確認します
if NSProcessInfo.processInfo()。arguments.contains( "YOUR_FLAG_NAME_HERE"){//ここでクリーンアップを行います}
@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()
}
}
}
}
IOS 11のシムアップの場合、「x」アイコンをタップするために、そして@Code Monkeyが提案した修正ごとにタップするように、わずかな変更を加えました。修正は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)
}
}
Chase Hollandとodmの回答に基づいて、disなどの設定でアプリを削除することで、ロングタップと+3オフセットbsを回避できました。
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()
SwiftのCraig Fishers回答を更新します。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)
}
}
}
アプリを削除して警告をリセットする上記の回答の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];
}