UITestで任意のタイミングでアプリを削除する


この記事は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ディレクトリにでも入れておく

App-Bridging-Header.h
// 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が表示されるまで待つ処理を追加しました。

Springboard.swift
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. 適当なタイミングで削除のメソッドを呼ぶ

HogeUITest.swift
    func testExample() {
        // いろんなテスト

        Springboard.deleteMyApp() // アプリ削除

        // いろんなテスト
    }

消えた!!!!