7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

XCUIElementの使い方をざっくりまとめてみた

Last updated at Posted at 2021-04-26
1 / 18

XCUITest周りで時間を浪費しないために

XCUIElementTypeQueryProviderの定義済みクエリを使うことで、テストで利用するUIを特定することができます。
しかし、まとまった記事が少なく扱い方が見つからず時間を浪費したものもいくつかあったので自分で発見したやり方も含めて簡単にまとめてみます。


※下記を省略してます

SampleViewUITest.swift
let app = XCUIApplication()

AccessibilityIdentifierを使えるパターン

UIView

SampleViewController.swift
view.accessibilityIdentifier = "sample_view"
SampleViewUITest.swift
let view = app.otherElements["sample_view"]

UIButton

SampleViewController.swift
let button = UIButton()
button.accessibilityIdentifier = "sample_button"

// サンプルというタイトルをセット
button.setTitle("サンプル", for: .normal)
SampleViewUITest.swift
let button = app.buttons["sample_button"]

// ボタンのタイトルを確認
XCTAssertEqual(button.label, "サンプル") 

UILabel

SampleViewController.swift
let label = UILabel()
label.accessibilityIdentifier = "sample_label"

// サンプルというテキストをセット
label.text = "サンプル"
SampleViewUITest.swift
let label = app.staticTexts["sample_label"]

// ラベルのテキストを確認
XCTAssertEqual(label.label, "サンプル") 

UITextField (SecureTextEntryではない)

テキストの中身は.value as! Stringでキャストして使います。

SampleViewController.swift
let textField = UITextField()
textField.accessibilityIdentifier = "sample_textField"

// サンプルというテキストをセット
textField.text = "サンプル"
SampleViewUITest.swift
let textField = app.textFields["sample_textField"] 

// ラベルのテキストを確認
XCTAssertEqual(textField.value as! String, "サンプル") 

UITextField (SecureTextEntry)

テキストの中身は.value as! Stringでキャストして使うと
中身が"・・・・"になるのでバリデーションチェックなどには使えません。
※いい方法あれば教えて欲しいです。

SampleViewController.swift
let textField = UITextField()
textField.isSecureTextEntry = true
textField.accessibilityIdentifier = "sample_textField"

// サンプルというテキストをセット
textField.text = "サンプル"
SampleViewUITest.swift
let textField = app.secureTextFields["sample_textField"] 

// ラベルのテキストを確認
XCTAssertEqual(textField.value as! String, "サンプル") // failure

UITabBarとUITabBarItem

SampleViewController.swift
tabBarController?.tabBar.accessibilityIdentifier = "sample_tabBar"
tabBarItem.accessibilityIdentifier = "sample_barItem"
SampleViewUITest.swift
let tabBar = app.tabBars["sample_tabBar"]
let barItem = tabBar.buttons["sample_barItem"]

UIImageView

.isAccessibilityElement = true
これが無いと使えません。

SampleViewController.swift
let imageView = UIImageView()
imageView.isAccessibilityElement = true
imageView.accessibilityIdentifier = "sample_image"
SampleViewUITest.swift
let tabBar = app.tabBars["sample_tabBar"]
let barItem = tabBar.buttons["sample_barItem"]

AccessibilityIdentifierを使えないパターン

SwipeMenu

.accessibilityIdentifier
が設定できないので

.accessibilityLabel
を設定しておくとうまく拾える。

SampleViewController.swift
let action = UIContextualAction()
action.accessibilityLabel = "leadingSwipe_action"
SampleViewUITest.swift
let action = app.tables.cells.firstMatch.buttons["leadingSwipe_action"]

AlertとAlertAction

ラベル名でしか拾えない。※いい方法あれば誰か教えて欲しいです。

SampleViewController.swift
let alert = UIAlertController()
alert.view.accessibilityIdentifier = "alert_view"
let cancelAction = UIAlertAction(
                    title: "キャンセル",
                    style: UIAlertAction.Style.cancel,
                    handler: nil))
// accessibilityIdentifierを指定しても使えない。
cancelAction.accessibilityIdentifier = "alert_cancel_button"
...
SampleViewUITest.swift
let alert = app.alerts["alert_view"]
// accessibilityIdentifierを指定しても使えない。
// let cancelAction = alert.buttons["alert_cancel_button"]

// ラベル名で拾える
let cancelAction = alert.buttons["キャンセル"]

それでも拾い方がわからない時

例えばUISegmentedControl内のSFシンボルを使ったボタン

順番で拾うパターン

SampleViewUITest.swift
let segmentedControl = app.segmentedControls["sample_segmentedControl"]
let button01 = segmentedControl.buttons.element(boundBy: 0) // 拾いたいボタンのindex

順番が使えないパターン(動的に変化するなど)

SFシンボルの特有のラベル名を事前に調べておきます。
※SFシンボル名とは若干異なるようです。

SampleViewUITest.swift
let segmentedControl = app.segmentedControls["sample_segmentedControl"]
for button in segmentedControl.buttons.allElementsBoundByIndex {
    print(button.label)
}

その他Tips

便利だったので覚書程度に。

色やイメージのテストについて

色やイメージ自体は拾えないが、変更の挙動があったかどうかを
accessibilityIdentifierで拾うことでカバーしました。

SampleViewController.swift
// 色の設定のたびにaccessibilityIdentifierを付け直しています。
button.tintColor = isStar ? .systemYellow : systemGray
button.accessibilityIdentifier = isStar ? "star_button" : "notStar_button"
SampleViewUITest.swift
let isStar = app.buttons["star_button"].exists

キーボードのReturnKey(DoneKey)

extensionでまとめておくと便利でした。

extension_XCUIApplication.swift
extension XCUIApplication {
    // 日本語と英語のキーボードのみ対応
    var doneKey: XCUIElement {
        // キーボードの表示アニメーションのラグを考慮して1秒探します。
        if keyboards.buttons["完了"].waitForExistence(timeout: 1) {
            return keyboards.buttons["完了"]
        } else {
            return keyboards.buttons["Done"]
        }
    }

    var returnKey: XCUIElement {
        // キーボードの表示アニメーションのラグを考慮して1秒探します。
        if keyboards.buttons["改行"].waitForExistence(timeout: 1) {
            return keyboards.buttons["改行"]
        } else {
            return keyboards.buttons["Return"]
        }
    }
}

キーボードの入力と強制タップ

こちらもextensionでまとめておくと便利でした。
強制タップはタップできないオブジェクトもタップしたい時に使います。
UIImagePickerController で画像を選択するのに使いました。

extension_XCUIElement.swift
extension XCUIElement {
    // テキストに新しい文字列を入力する
    func clearAndEnterText(text: String) {
        guard let stringValue = self.value as? String else {
            XCTFail("Tried to clear and enter text into a non string value")
            return
        }
        self.tap()
        let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
        self.typeText(deleteString)
        self.typeText(text)
    }
    
    // 強制タップ
    func forceTap() {
        if self.isHittable {
            self.tap()
        } else {
            let coordinate: XCUICoordinate = self.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
            coordinate.tap()
        }
    }
}

最後に

初めてUITestを実装してみましたが、意外と面白かったです。
慣れてくるといろんな挙動が表現できるようになるものの時間も結構かかりました。
つまり変更に強いテストを書く必要があります。
プロジェクトにマッチしたPageObjectパターンの設計や
accessibilityIdentifierをうまくまとめる方法など工夫がまだまだ必要だと感じました。
そもそも無理にUITestでテストする必要がないこともあるかもしれません。
ライブラリを使ったテストなども試して、ベストプラクティスを見つけようと思います。

参考にした記事や書籍


7
4
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?