[iOS 14] XCUITestでアプリを削除する

アプリの初回起動時のみに表示される画面をテストする必要があったので、 `XCUITest` でアプリを削除する方法について調べました。

WEB上にいくつか参考になる記事がありましたが、iOS 14のアプリ削除フローに対応した実装方法が見当たらなかったので内容をまとめてみました。



## iOS 14でのアプリ削除フロー

iOS 14では以下のようにアプリを削除します

| アプリアイコン長押し | コンテキストメニュー | 削除確認アラート１ | 削除確認アラート２ |
|:-:|:-:|:-:|:-:|
| ![Simulator Screen Shot - iPhone 11 Pro - 2021-03-03 at 15.05.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/d96bbf4c-92db-cfdc-941f-9fff48e22fff.png) | ![Simulator Screen Shot - iPhone 11 Pro - 2021-03-03 at 15.05.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/887efe23-41a9-2388-5774-8955b07e60b4.png) | ![Simulator Screen Shot - iPhone 11 Pro - 2021-03-03 at 15.05.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/59d01678-4cef-7a6e-2a62-d62e7af59ef3.png) | ![Simulator Screen Shot - iPhone 11 Pro - 2021-03-03 at 15.05.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/048dceca-a6a8-88a3-0861-415eb9c8cc15.png) |
| ホーム画面上で<br>削除するアプリのアイコンを長押し | コンテキストメニューで<br>アプリの削除を選択 | ホーム画面からの削除確認アラートで<br>アプリの削除を選択 | アプリ削除確認アラートで<br>アプリの削除を選択 |

ポイントは、アプリを削除（アンインストール）するための確認アラート（上記の `削除確認アラート２` ）が表示される前に、アプリの情報を保持しつつホーム画面から削除するための確認アラート（上記の `削除確認アラート１` ）が表示される点です。

なお、コンテキストメニューが表示されてもアプリアイコンの長押しをしたままにすると、各アプリアイコンの左上に削除を示すマイナス:no_entry:ボタンが表示されますが、このフローだと[上手く削除できない場合がある](https://qiita.com/bitpoetics/items/8e39dfa839b0fb404a40#%E5%88%A5%E3%83%95%E3%83%AD%E3%83%BC)ことが分かったので、今回はコンテキストメニューでアプリの削除を選択するフローを採用しました。



## 実装

上記を踏まえて、アプリを削除するプログラムを書いていきましょう。
今回は [SpringBoard](https://ja.wikipedia.org/wiki/SpringBoard) [^1] を使ってアプリの削除フローをSwiftで記述します。

```swift
struct Springboard {
    private static let myAppName = "削除するアプリ名"
    private static let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

    static func deleteApp(name: String = myAppName) {
        XCUIApplication().terminate()

        springboard.activate()

        XCUIDevice.shared.orientation = UIDeviceOrientation.portrait
        Thread.sleep(forTimeInterval: 2.0)

        let appIcon = springboard.icons[name]
        guard appIcon.exists else {
            return
        }
        appIcon.press(forDuration: 1.5)

        let preferredLanguageCode = NSLocale.preferredLanguages[0].prefix(2)

        // コンテキストメニュー
        let firstDeleteButtonText = preferredLanguageCode == "ja" ? "Appを削除" : "Remove App"
        springboard.buttons[firstDeleteButtonText].firstMatch.tap()
        Thread.sleep(forTimeInterval: 1.0)

        // 削除確認アラート１
        let secondDeleteButtonText = preferredLanguageCode == "ja" ? "Appを削除" : "Delete App"
        springboard.buttons[secondDeleteButtonText].firstMatch.tap()
        Thread.sleep(forTimeInterval: 1.0)

        // 削除確認アラート２
        let thirdDeleteButtonText = preferredLanguageCode == "ja" ? "削除" : "Delete"
        springboard.buttons[thirdDeleteButtonText].firstMatch.tap()

        Thread.sleep(forTimeInterval: 0.5)

        XCUIDevice.shared.press(.home)
    }
}
```

確認アラートの文言などはシミュレーターの言語設定によって変わるので、必要に応じて切り分けます。
英語の `Remove` と `Delete` で表記ゆれしているのは地味にハマりどころかもしれません :sweat_smile: 

これをUIテストのアプリ起動前に呼び出せば、毎回クリーンな状態でテストを実行できます :tada:

```swift
import XCTest

class MyAppUITests: XCTestCase {
    override func setUp() {
        super.setUp()

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // インスタンスメソッド `setUp()` で呼び出せば、各テストメソッドの実行前にアプリが削除される
        Springboard.deleteApp()

        // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
        XCUIApplication().launch()

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

...

}
```



## 別フロー

コンテキストメニューでアプリの削除を選択せずにアプリアイコンを長押ししつづけていると、アプリアイコンたちがプルプルしはじめ、各アプリアイコンの左上には削除を示すマイナス:no_entry:ボタンが表示されます。

このフローでもアプリを削除できますが、**環境やタイミングによっては想定外のアラートなどに邪魔されてしまう**ことがありました。

| アプリアイコン長押し継続 | 確認アラート | 想定外のアラート |
|:-:|:-:|:-:|
| <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/abc6c30e-5375-a31b-921a-6f3968ab7539.png" title="abc6c30e-5375-a31b-921a-6f3968ab7539.png" width="400"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/d49e2e9a-29ff-e71a-2a99-b2bdc1aa75b8.png" title="d49e2e9a-29ff-e71a-2a99-b2bdc1aa75b8.png" width="400"> | <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27943/763ca06f-191c-3f75-24f9-f457ad92e97b.png" title="d49e2e9a-29ff-e71a-2a99-b2bdc1aa75b8.png" width="400"> |
| コンテキストメニューが表示されても<br>無視して押し続ける | 削除確認アラートが表示されることもあるが | ホーム画面編集についてのお知らせアラートなどに<br>邪魔されてしまうこともある |

もちろんこれらを適切にハンドリングできればテストは可能だと思いますが、今回は採用を見送りました。


## 参考

[XCUITestでアプリを削除する](http://harumi.sakura.ne.jp/wordpress/2019/04/03/xcuitest%E3%81%A7%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B/)

[^1]: iOSのホーム画面やそれに付随した機能を管理するためのソフトウェア