Reputation: 1408
I am trying to do UI Tests using XCUITest
. Currently I have a bit of a problem in inputting a text into a UITextField
. Here is what I've got in my Robot
class to type the text using the keyboard:
func typeTextToTextField(_ element: XCUIElement, text: String, timeout: TimeInterval = timeInterval,
file: StaticString, line: UInt) {
guard assertExists(element, timeout: timeout, file: file, line: line),
element.isHittable else {
return
}
element.tap()
sleep(2)
app.activate()
element.typeText("\(text)\n")
sleep(2)
}
I've tried these steps before it become like this:
element.tap()
and element.typeText()
.assertExists()
block)sleep()
to wait for a few secs for the keyboard to show up.app.activate()
because apparently sometimes it took too long to do query. And an answer about the error here said that I should call app.activate() first.If I run all the tests at once with parallel testing, some of the tests that use this will work, but some will fail. Some will correctly show the keyboard and typed the text correctly while others will fail to show the keyboard and displayed this error Failed to synthesize event: Neither element nor any descendant has keyboard focus.
. But when I run the test one at a time, they will all be green and work perfectly.
So, next, I tried changing the way to input by pasting the text instead of typing it. That also doesn't work. Here's the code:
func pasteToTextField(_ element: XCUIElement, text: String, timeout: TimeInterval = timeInterval,
file: StaticString, line: UInt) {
app.activate()
guard assertExists(element, timeout: timeout, file: file, line: line),
element.isHittable else {
return
}
UIPasteboard.general.string = text
app.activate()
element.tap()
element.doubleTap()
element.press(forDuration: 1.2)
tap(pasteMenuItem, timeout: timeout, file: file, line: line)
sleep(5)
}
So I did:
element.tap()
and tap(pasteMenuItem, timeout: timeout, file: file, line: line)
(which was the same with app.menuItems["Paste"].tap()
)element.doubleTap()
element.press()
Using the paste function sometimes the Paste menu item doesn't show up with this error: failed - Element: "Paste" MenuItem does not exist!
. But sometimes it worked just fine.
So, I'm at wits end now. How do I input a text into a textfield that will surely work at all conditions whether running all tests or running a singular test case?
Thanks in advance.
PS: It works just fine on my CI machine. That's weird.
Upvotes: 3
Views: 1011
Reputation: 26
You can try something like this
extension XCUIElement {
func clearAndEnterText(_ text: String, app: XCUIApplication) {
let currentClipboard = UIPasteboard.general.string ?? ""
waitForElementToBecomeHittable(timeout: .small)
guard let stringValue = value as? String else {
return XCTFail("Tried to clear and enter text into a non string value")
}
if stringValue == text {
return
}
if stringValue.isNotEmpty {
if app.isKeyboardKeysAvaliable(key: XCUIKeyboardKey.delete.rawValue) {
let deleteString = stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined()
typeText(deleteString)
} else {
let selectAllButton = app.menuItems.element(predicate: .label(Comparison.containsAny, "Select All")).firstMatch
if !selectAllButton.waitForExistence(timeout: .small) {
press(forDuration: 1.1)
}
if selectAllButton.waitForExistence(timeout: .small) {
selectAllButton.tapElement()
}
}
}
if value(forKey: "hasKeyboardFocus") as? Bool ?? false {
typeText(text)
} else {
UIPasteboard.general.string = text
let pasteButton = app.menuItems.element(predicate: .label(Comparison.containsAny, "Paste"))
if !pasteButton.waitForExistence(timeout: .small) {
tap()
press(forDuration: 2.1)
}
if pasteButton.waitForExistence(timeout: .small) {
pasteButton.tapElement()
}
}
// Dismiss tooltip if it is still displayed
if app.menuItems.element.waitForExistence(timeout: .small) {
app.tap()
}
waitForElementToBecomeHittable(timeout: .small)
UIPasteboard.general.string = currentClipboard
}
}
Note: you'll need to import this file https://github.com/ZhipingYang/Einstein/blob/600854f9b6f93bb3694deddb3fb6edbae0f67f74/Class/UITest/Model/EasyPredicate.swift, also I have changed the name from EasyPredicate
to Predicate
extension XCUIApplication {
func isKeyboardKeysAvaliable(key: String) -> Bool {
let keyboard = keyboards.element(boundBy: 0)
if key.contains(all: "next") {
return keyboard.buttons[key].exists
} else {
return keyboard.keys[key].exists
}
}
}
extension XCUIElement {
@discardableResult
func waitForElementToBecomeHittable(timeout: Timeout) -> Bool {
return waitForExistence(timeout: timeout) && isHittable
}
Upvotes: 1