Framework for improving the structuring of XCUI tests in a BDD style using the POM, steps and gherkin styled feature files. Also a collection of functions to make XCUI easier to use and read.
TABTestKit has no dependencies and supports iOS 10 and newer.
TABTestKit is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'TABTestKit'
There's 1 subspec available: Biometrics
. This means you can get a subset of TABTestKit
's functionality.
pod 'TABTestKit/Biometrics'
There are three core concepts in TABTestKit:
- Screens: these represent the screens in your app
- Features: these represent the features the screens have that you want to test
- Steps: these represent the steps that you need to carry out to test the features
To create a screen, create a class that conforms to UITestScreen
:
final class ExampleScreen: UITestScreen {
let trait: XCUIElement // Required property to conform to UITestScreen, used for awaiting screens
init() {
trait = App.shared.otherElements["Screen title here"]
}
}
Once you've created a screen, you can use it in features by creating a series of steps:
class ExampleFeature: TestBase {
let exampleScreen = ExampleScreen()
func test_exampleTest() {
Given(I: backgroundTheApp) // backgroundTheApp is a real function! See below.
When(I: foregroundTheApp)
Then(I: seeTheExampleScreen)
}
}
private extension ExampleFeature {
func backgroundTheApp() {
XCUIDevice.shared.press(.home)
}
func foregroundTheApp() {
App.shared.activate()
}
func seeTheExampleScreen() {
exampleScreen.await()
}
}
When the test_exampleTest
function runs as part of a test run, each step gets called automatically, and since they're real functions you get code completion, syntax highlighting, and help from the compiler.
See below for more in-depth discussion around steps and scenarios.
trait
: This is a screen element that must be defined once a page has conformed toUITestScreen
which will allow you to call.await()
on that screen.await
: Uses the giventrait
element for a screen and callswaitForElementToAppear()
on it. Using a unique element to that screen is the recommended selection for a trait.waitForElementToAppear
: Takes an element and waits for a default (but overridable) amount of time and checks over that time period whether that element exists and is hittable.tapWhenElementAppears
: RunswaitForElementToAppear
and adds a.tap()
call onto the element.waitForElementToDisappear
: Takes an element and waits a for a default (overridable) amount of time and checks over that time period whether that element doesn't exist and isn't hittable.tapCoordinate
: Takes an x and y value and taps it.
setUp
: Starts the XCTestCase instance with, defaultingcontinueAfterFailure
tofalse
and launches the App usinglaunchApp()
.launchApp
: CallslaunchWithOptions()
from the App singleton to launch the app at the beginning of each XCTestCase.tearDown
: Callsterminate()
from the App singleton to terminate the app after each XCTestCase.
Within test functions you define different steps. Steps can be one of the following:
Given
When
Then
And
Steps are all typealiases of a struct called Step
, which is initialised with a handler or function to call when the test runs.
Thanks to Swift treating functions as reference types, you can pass functions directly into steps like this:
func doSomething() {}
Given(I: doSomething)
Notice how it's possible to omit the ()
from the function. That's because you're not actually calling the function here, you're just passing it in to be called by the step when it's created during the test run.
Steps have three different initialisers, allowing your test code to be human readable and expressive:
Given(I: doSomething)
When(the: backEndIsErroring)
Then(nothingHappens)
To make your code even more readable, you can go one step further and group steps in scenarios:
Scenario("Doing something when the back end is erroring") {
Given(I: doSomething)
When(the: backEndIsErroring)
Then(nothingHappens)
}
Grouping steps in scenarios not only make your tests more readable, it also improves the Xcode test report logs as well as allowing you to run multiple scenarios in one test function, reducing test run time, without losing clarity:
func test_happyPath() {
Scenario("Launching the app") {
Given(I: launchTheApp)
When(the: backEndIsWorking)
Then(I: seeTheLoginScreen)
}
Scenario("Logging in") {
Given(I: seeTheLoginScreen)
When(I: logIn)
Then(I: seeTheSettingsScreen)
}
}
Finally, you can pass functions into steps that take arguments, meaning you can do stuff like this:
enum Screen {
case login
case settings
}
func see(theScreen screen: Screen) {
switch screen {
case .login: loginScreen.await()
case .settings: settingsScreen.await()
}
}
Given(I: see(theScreen: .login))
When(I: logIn)
Then(I: see(theScreen: .settings))
scollToLastCell
: Finds the last cell on the page and usesscroll
to get to it.scroll
: Scrolls to a given element, the default is to scroll down but this is overridable using abool
argument flag.visible
: Whether the element is visible.
TABTestKit
allows you to simulate biometrics on the simulator. This means you can enroll or unenroll biometrics and simulate a successful or unsuccessful biometric authentication. This can be really helpful when trying to automate biometric features of your app.
Note: You will also need to handle the Face ID authentication dialog, which shows up when your app uses Face ID for the first time. In order to do so, you need to define the NSFaceIDUsageDescription
key in the Info.plist file and handle the dialog in your UI test code.
// Enrollment
Biometrics.enrolled()
// Unenrollment
Biometrics.unenrolled()
// Successful authentication
Biometrics.successfulAuthentication()
// Unsuccessful authentication
Biometrics.unsuccessfulAuthentication()
To run the UI automation tests, switch to the UI test scheme in your project and press CMD + U
.
Guidelines for contributing can be found here.
Neil Horton, neil@theappbusiness.com, https://github.com/neil3079
Zachary Borrelli, zac@theappbusiness.com, https://github.com/zacoid55
Kane Cheshire, kane.cheshire@theappbusiness.com, https://github.com/kanecheshire
TABTestKit is available under the MIT license. See the LICENSE file for more info.