A collection of useful helper functions to make writing UI tests at least a little bit less painful.
You can install UITestHelpers by adding them to your UI tests section in your Podfile:
platform :ios, '10.0'
use_frameworks!
target '<name-of-your-UI-tests-target>' do
pod 'UITestHelpers'
end
Then run pod install
.
Of course you can just drag the source files into your project, but using CocoaPods is really the recommended way of using UITestHelpers.
class MyUITests: XCUITestCase {
override func setUp() {
super.setUp()
self.continueAfterFailure = false
self.addAlertsHandler(for: ["Allow", "OK"])
self.app.launchEnvironment = ["AutoCorrection": "Disabled"]
self.app.launch()
}
func testSomething() {
// tap an element (it's important to wait for an element to exist, before tapping it)
element.waitAndTap()
// tap a button
self.tapButton("buttonAccessibilityIdentifier")
// tap a cell in a collectionView (scroll to the right, if needed)
self.tapCollectionViewCell("collectionViewCellAccessibilityIdentifier", in: "collectionViewAccessibilityIdentifier", scrollDirection: .right(100))
// handle (dismiss) permission alerts
self.addAlertsHandler(for: ["Allow", "OK"])
// tap the "Continue" button on the next alert dialog
self.tapAlertButton(name: "Continue")
// show keyboard
self.app.showKeyboard(for: self)
// really type on the keyboard (sometimes this is needed in contrast to `XCUIElement.typeText`)
self.app.typeOnKeyboard(text: "1337")
// hide keyboard
self.app.hideKeyboard()
}
}
To get reliable results from your tests, it's recommended, to use accessibilityIdentifier
s, wherever possible.
For static stuff you can just set them right in Interface Builder:
Or if the element does not have an Accessibility
section, using User Defined Runtime Attributes
:
For dynamic stuff however, you'll have to set it in code. Some may consider this a kind of clutter to have test-only stuff in the code base, but as it's really just a one-liner in e.g. a UITableViewCell
. And I would really take "this clutter" anytime over less reliable UI tests (e.g. when using indices instead).
E.g.:
let dynamic = "somethingSpecificToThisCell"
self.accessibilityIdentifier = "your\(dynamic)IdentifierHere"
As Apple doesn't provide a way to really clean the app state for each test, we'll have to do it manually by providing our own "main function".
For that we'll use a launch argument like --Reset
that we'll pass to our XCUIApplication
in our tests setup (see XCUITestCase
base class).
- Remove the
@UIApplicationMain
annotation from your AppDelegate. - Create a
main.swift
file in your project with following content:
_ = autoreleasepool {
if ProcessInfo().arguments.contains("--Reset") {
// Delete files, whatever...
}
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to:
UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc)),
nil,
NSStringFromClass(AppDelegate.self)
)
}
See also Resetting iOS Simulator for UI tests.
func disableHardwareKeyboard() {
let setHardwareLayout = NSSelectorFromString("setHardwareLayout:")
UITextInputMode.activeInputModes
.filter({ $0.responds(to: setHardwareLayout) })
.forEach { $0.perform(setHardwareLayout, with: nil) }
}
Make sure the hardware keyboard is disconnected.
- Pressing
cmd
+shift
+k
in the simulator will toggle the hardware keyboard - Even better: Disable it in a
Build Phase
script, in your UI tests target, like follows, to ensure this for each test run:
defaults write com.apple.iphonesimulator ConnectHardwareKeyboard -bool NO
Here you can see all the accessibility information for an element.
Use this nice app (inclued in Xcode's Developer Tools) to inspect the app in a simulator, and get accessibility information as well as the hierarchy of the element.