XCUITest: Add waitForNonExistence() to your iOS Automation Toolkit
Available since the release of XCode 9.0, XCTest’s waitForExistence(timeout: TimeInterval)
method has been a helpful companion in the effort against UI Test flakiness. As a UI test executes, elements available to a test vary depending on the state of the application, leaving tests prone to failure. In my experience with writing automated UI tests for iOS apps, this very problem has been a leading contributor to test flakiness. I’ve seen first hand how leveraging waitForExistence(timeout: Int)
has increased the stability of test runs I’m responsible for. With the addition of waitForNonExistence(timeout: Int)
, UI test stability is improved to a greater extent.
What is waitForExistence()?
When writing assertions to verify the existence of expected on page elements, often we will use the .exists
property extended from type XCUIElement. At test runtime, an assertion will check the declared element against the application’s accessibility hierarchy. The check against the hierarchy will determine whether or not the element was found to exist on the page. Issues start because of how .exists
works in comparison to page navigation. Navigating between pages simulates a user’s behavior & experience with the application. There will be a brief delay between an interaction & an expected result. .exists
, on the other hand, isn’t delayed to as great of an extent, and will fire off after the .tap()
activity executes regardless of the state of the application. If in between btnPink.tap()
and the assertion that follows, a complex page is to load, there is no guarantee the page will be ready and the test is now considered ‘flaky’.
With the use of waitForExistence(timeout: 3)
, we are giving our test a set timeout of 3
seconds to wait for the existence of the txtPinkScreen
label.
Note: if using the Page Object Model (POM) pattern for UI testing, adding waitForExistence()
to your page initializers could ensure pages are loaded prior to testing.
So then what about waitForNonExistence()?
We expect there to be situations where elements appear on screen after app interaction, but we also expect the opposite to be true. There are just as likely to be behaviors on an application’s page that remove UI elements from view, and a similar problem of flakiness would emerge. Currently, XCUITest has no built in manner of checking that an XCUIElement is no longer in view with a timeout, so we will build one ourselves!
We will start by creating a file in our UITest directory where extensions off of XCUIElement will live, and call it XCUIElement+Extensions.swift
. Inside this Swift file, we will import XCTest and create the skeleton for our extension.
Now we can create waitForNonExistence(timeout: TimeInterval)
- We will start by setting our predicate (which is like a cross between SQLs WHERE clause and a regular expression) for which we want
exists
== FALSE. - Next, we will create our expectation using
XCTNSPredicateExpectation
. It takes in 2 parameters: a predicate (which we just created), and an object against which the predicate will be evaluated. The object we will be using here isself
because we are creating an extension off of XCUIElement, and are waiting for its non existence. The expectation will be fulfilled once/if the predicate is satisfied. - Then we will use XCTWaiter to wait for the previously created expectation, with the timeout given being the parameter passed into our waitForNonExistence function.
- After XCTWaiter executes, fulfilled or not, our function will return the negation to the existence of itself. If the element no longer exists,
.waitForNonExistence()
will return true.
Using it in a UI Test
We can now use .waitForNonExistence(timeout: _)
on an element that should no longer be in view after the app’s visual state has changed. Hope this helps you in your combat against flaky UI tests! Happy testing!