Open4

XCUIElement.typeTextで右矢印が文字として入ってしまう件

kabeyakabeya

SwiftUIのUITest実行時に、TextFieldをクリアするような処理を入れています。通常は以下のような感じでうまく行きます。
タップしたときに、TextFieldの文字列先頭にカーソルが来るので、文字列の長さ分だけ、右矢印を押して、そのあとdeleteキーを文字数分押す、というような処理です。

func typeToClear(_ elem: XCUIElement) {
        let text = elem.value as! String
        
        if 0 < text.count {
            let rightArrowKeys = String(repeating: XCUIKeyboardKey.rightArrow.rawValue, count: text.count)
            elem.typeText(rightArrowKeys)
            let deleteKeys = String(repeating: XCUIKeyboardKey.delete.rawValue, count: text.count)
            elem.typeText(deleteKeys)
        }
    }

ですが、何かのタイミングなのか、右矢印が文字としてテキストフィールドに入ってしまうケースがあります。
なので以下のように処理を修正しました。

func typeToClear(_ elem: XCUIElement) {
        var text = elem.value as! String
        
        if 0 < text.count {
            let rightArrowKeys = String(repeating: XCUIKeyboardKey.rightArrow.rawValue, count: text.count)
            elem.typeText(rightArrowKeys)
        }
        text = elem.value as! String
        if 0 < text.count {
            let deleteKeys = String(repeating: XCUIKeyboardKey.delete.rawValue, count: text.count)
            elem.typeText(deleteKeys)
        }
    }

この文章を書いていて気づきましたが、これ右から左に書く言語のケースで正しく動作しないですね。
右矢印ではなく、endキーを押すようにしますか。

kabeyakabeya
func typeToClear(_ elem: XCUIElement) {
        elem.typeText(XCUIKeyboardKey.end.rawValue)
        let text = elem.value as! String
        if 0 < text.count {
            let deleteKeys = String(repeating: XCUIKeyboardKey.delete.rawValue, count: text.count)
            elem.typeText(deleteKeys)
        }
    }

これでうまく行きました。

kabeyakabeya

結局、endキーも文字として入ってしまうケースが生じてしまい、その場合、カーソルが先頭に残ったままなのでフィールドの文字列は消えず、テストが失敗してしまうことがありました。

func typeToClear(_ elem: XCUIElement) {
  elem!.doubleTap()
  elem!.typeText(XCUIKeyboardKey.delete.rawValue)
}

ダブルタップで全選択してdeleteで消す、というように直しました。

kabeyakabeya

テキストフィールドに-3とか入っていると、3だけが選択されてしまい、結局クリアできないケースが発生しました。
いったんは以下のように修正しました。

func typeToClear(_ elem: XCUIElement, _ prompt: String) {
    repeat {
        elem.doubleTap()
        elem.typeText(XCUIKeyboardKey.delete.rawValue)
        let text = elem.value as! String
        if text == prompt {
            break
        }
    } while true
}

全部消えるまで「ダブルタップ→deleteキータイプ」を繰り返すようにしました。
さてどうやって空かどうかを判定しようか、という問題が生じました。
プロンプトがあるとelem.valueはそのプロンプトを返してきてしまうので、空になったかどうかの判定を「プロンプト文言と同じかどうか」としました。

ただし厳密には、空かどうかとは違う条件になります。
実際、プロンプトと同じ文字列が打たれていても消えたことになってしまいます。プロンプト文字列が例えば「enter value」で、入ってる文字列が「please enter value」だと最初は処理が実行され、ダブルタップで「please」だけが選択されて、deleteキーで削除されます。いったん「please」が削除されてしまうと、残るのが「enter value」なので処理がそこで終わってしまいます。

プロンプトが返ってこないか(空文字列が返ってくるか)、endキーが確実に入るのであれば、この辺で悩む必要はないのですが。

とはいえ、テストケースでプロンプトと入力された値が一部かぶる、というようなことをやらないと思いますし、やる場合は気をつける、という程度で、いったんここはこのままにしたいと思います。

ちなみにXcodeは14.3です。
バージョンアップで直るといいですね(endキーが)。