この記事はWanoグループ Advent Calendar 2016の14日目です。
2016/12/16追記
アプリ削除の処理で、たまに削除Alertの表示が遅いときにテストが死ぬことがあったので、Springboardクラスを修正しました。
UITest中にアプリを削除したい
アプリのインストール直後だけアプリの説明ページとか表示してた場合、すでにインストール済のシミュレータでテストすると説明ページが表示されなくて説明ページのテストができない。
シミュレータをリセットするスクリプト書いてテスト前に自動実行してもいいんだけど、それやっちゃうとほかの作業に影響ありそうだし、時間かかるし微妙。
なんとかテスト中の任意のタイミングで、アプリを削除して状態をリセットする方法ないかなー、と思って調べてみたらあったのでメモ。
http://stackoverflow.com/questions/33107731/is-there-a-way-to-reset-the-app-between-tests-in-swift-xctest-ui-in-xcode-7
に書いてある回答の2つめ。
どうやらXCUIApplicationでホーム画面を操作することができるらしい。
やってみた
1. XCTestのPrivate Headerを追加
FacebookのWebDriverAgentで同じことをやってるので、そこからPrivate Headerファイルを持ってくる。
具体的には、
https://github.com/facebook/WebDriverAgent
の、PrivateHeaders/XCTestフォルダーにある、
XCUIApplication.h
XCUIElement.h
の2つのファイルをUITestのディレクトリに入れる。
入れたファイルをswiftでも呼べるようにBridging Headerファイルを作って、UITestディレクトリにでも入れておく
// Private headers from XCTest
#import "XCUIApplication.h"
#import "XCUIElement.h"
UITestのtargets -> Build Settings -> Objective-C Bridging Header
の項目に、作ったBridging Headerのファイルパスを記入してパスを通す。
2. アプリ削除のメソッドの実装
stackoverflowにあったswiftのコードをコピペして 新しくクラスを作ってUITestディレクトリにでも入れておく。
削除Alertが表示されるタイミングが遅いと処理が死ぬので、Alertが表示されるまで待つ処理を追加しました。
import XCTest
class Springboard: XCTestCase {
static let shared = Springboard()
let springboard = XCUIApplication(privateWithPath: nil, bundleID: "com.apple.springboard")!
/**
Terminate and delete the app via springboard
*/
func deleteApp() {
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.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()
// Wait for showing alert
let alert = springboard.alerts["Delete “MyAppName”?"]
let alertExists = NSPredicate(format: "exists == true")
expectation(for: alertExists, evaluatedWith: alert, handler: nil)
waitForExpectations(timeout: 5, handler: nil)
springboard.alerts.buttons["Delete"].tap()
}
}
}
この時、springboard.icons["MyAppName"]
とlet alert = springboard.alerts["Delete “MyAppName”?"]
のMyAppNameの箇所に、自分のアプリのProduct Nameに書き換えておく。
(Alertのtitleをキーにして処理を行ってるけど、端末の言語環境が英語じゃないと動かない気がしている。もっとうまいやり方ないものか。)
3. 適当なタイミングで削除のメソッドを呼ぶ
func testExample() {
// いろんなテスト
Springboard.deleteMyApp() // アプリ削除
// いろんなテスト
}
消えた!!!!