How to Set Up Xcode UI Testing for Today Extensions
Today Extensions are a great way to add quick and glanceable UI to your app. Xcode UI Testing, along with iOSSnapshotTestCase (previously named FBSnapshotTestCase), is a great way to test UI in your app. So, why not use them together? Well, there are a few problems with that.
First, Xcode doesn’t allow a Today Extension target to be set as the target application for a UI Test. Second, iOSSnapshotTestCase doesn’t work out of the box with Xcode UI Testing.
I will cover workarounds for both of these issues below.
Manipulating the World Outside of Your App
Since Xcode doesn’t allow a Today Extension target to be set as the target application for a UI Test, we need a way to get to the Today Extension after the app has launched. While using Xcode UI Test recorder is usually a great start to creating a UI Test, it won’t work without some tweaking. For one thing, it doesn’t record hitting the home button to get to the home screen. Getting to the homescreen and controlling it used to be fairly difficult and involved imported private headers, but now it’s easy—just call
XCUIDevice.shared.press(XCUIDevice.Button.home). Then, once on the home screen, it records:
let scrollView = XCUIApplication().otherElements["Home screen icons"].otherElements["SBFolderScalingView"].children(matching: .scrollView).element scrollView.swipeRight()
The problem here is that
XCUIApplication is SpringBoard and not the current target application, so the test runner isn’t able to find “Home screen icons.” To fix this, we need to find SpringBoard’s scroll view like this:
XCUIDevice.shared.press(XCUIDevice.Button.home) XCUIDevice.shared.press(XCUIDevice.Button.home) //Do this twice to go to the first home screen let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") let scrollView = springboard.otherElements["Home screen icons"].otherElements["SBFolderScalingView"].children(matching: .scrollView).element scrollView.swipeRight() //Swipe over to the Today view
Now we have the Today View on screen. Note that this method is also useful for manipulating other apps, like Settings, in order to test permission changes, such as notifications settings for an app.
Getting iOSSnapshotTestCase to Play Nice with Xcode UI Testing
Typically, iOSSnapshotTestCase is used in a white box testing scenario in which a programmatically-created view is compared to a screenshot previously captured using iOSSnapshotTestCase in record mode. However, Xcode UI Testing is used in a black box testing scenario in which the running app is inspected.
To get set up, follow the instructions in iOSSnapshotTestCase’s ReadMe—except you will want to add iOSSnapshotTestCase to your UI Test target, instead of just your Unit Test target. To bridge the gap between the iOSSnapshotTestCase and Xcode UI Testing, simply create a UIImageView with an image captured during UI Testing:
sleep(1) //wait for swipe animation to finish let image = springboard.screenshot().image FBSnapshotVerifyView(UIImageView(image: croppedImage))
Next, set iOSSnapshotTestCase record mode to
Yes, run the test, set record mode to
No, run the test and … the test failed. Well, the test probably failed because taking a screenshot of the SpringBoard includes the status bar with the time, which more than likely changed since the reference screenshot was recorded.
To avoid this external state change, we need to crop the status bar out of the image. Fortunately, cropping the status bar is easy with Joseph Susnick’s UIImage extension. Add the extension to your UI Test target, and then call
removingStatusBar on the screenshot image:
sleep(1) //wait for swipe animation to finish let croppedImage = springboard.screenshot().image.removingStatusBar FBSnapshotVerifyView(UIImageView(image: croppedImage))
It took a few hoops to jump through, but now you can verify the look and feel of your Today Extension.