Snapshot XCUI Testing

Joe Susnick
5 min readJan 24, 2018

--

This tutorial show you how to use the popular testing library iOSSnapshotTestCase with XCUITests. Feel free to skip ahead if you already know how to create a project and install some pods.

Set up the Project

Make a new single view app
Make sure to include both unit and UITests

Then in the root of your project, run pod init. This will create a Podfile in the root of your project. Open it and add pod 'iOSSnapshotTestCase' to the Test and UITest targets like so:

My lazy screenshot taking makes it look like I wrote this on paper and photocopied it… ha!

Then run pod install. This will add the required files to those targets.

Next, you’ll want to edit your scheme to add the necessary environment variables.

Hard to find but only the first time.
In actually you’ll probably wind up putting these in a different directory to be archived as part of CI

The first two you can copy paste from the iOSSnapshotTestCase repo, the third will be identical except for the last part of the path.

FB_REFERENCE_IMAGE_DIR is where the tool will store the reference images.

IMAGE_DIFF_DIR is where the tool will store the diffs to examine failures.

FAILED_UI_TEST_DIR is our custom directory where we’ll store the snapshots from our XCUITest failures

Add an XCUITest

Now we need something to test. In Main.storyboard add a UILabel with some text. Also add a UIButton that segues to a new UIViewController with a different feel. For instance:

They need to be different. Aside from that, go bananas.

In the pre-generated SnapshotUITestingUITests, replace the generated code with this:

The actual assertion might be different on yours.

Enable your XCUITest to Take Snapshots

Create a new file, call it SnapshotTestCase and add the following code:

It looks empty but we’ll need it later.

Next, update your UITest to inherit from SnapshotTestCase and set it to record your first screenshot.

Note that recordMode is on

Almost everything in here is standard usage of the library and you should check out their README for more on how it works. Long story short, the library requires that we pass it a view. The trick here is that we’re using the image property on XCUIScreenshot to create a UIImageView. Which is what the library requires to create and store an image for comparison.

Run the test. It should fail with a message reminding you to toggle recordMode to false.

This is good. If you toggle recordMode to false (or delete that line), your next run will compare the screenshot against the one it just took. You should have no trouble finding your stored images in your project directory.

The screenshot was stored in the right place

Fix the Status Bar Issue

I’ll save you the journey of discovery and just tell you there’s a problem. Doing things this way takes a screenshot of the entire simulator.

Houston…

Don’t worry. We can fix this. Add a new file to your UITest target and call it UIImageExtensions. Add the following code.

This will remove a status bar-sized chunk from a UIImage. We can now add a method to our SnapshotTestCase class.

Notice that we capture the file and line since this is a shared helper method

This will handle taking the screenshot, cropping it, and calling the requisite method on the library.

Now in your test you can just call verifyView()

Tada!!!!! Now you can replace all those annoying assertions with a simple image comparison!

One More Thing

So while I was using this, I had another idea. What if we could use this to access screenshots of failed XCUITests that are run in CI?

Here’s the caveat. If you’re just running tests for yourself, you probably don’t need to set up screenshots on failure. You can already access a test report in Xcode that has screenshots of the failure. However, there are a few scenarios I can think of where you would want this extra functionality.

  • you’re running your tests headless in parallel
  • you’re running your tests in continuous integration and you don’t have ready access to the build and test logs
  • you’re running your tests in continuous integration and you DO have ready access to the build and test logs but you work with a lot of people so finding the logs you care about is really annoying

So if any of that applies to you, stay with me. In SnapshotTestCase we’ll override the recordFailure method.

And before we go any further, let’s break that test we wrote earlier.

It won’t find “Hello World”. This will dump us into the recordFailure method on SnapshotTestCase. Let’s add a one more method to be able to take and save a screenshot.

  1. Takes a screenshot using the normal mechanism and converts it to Data
  2. Creates a path for saving screenshot that includes the name of the test that failed and the line it failed on
  3. Uses the environment variable we stored earlier to determine where to store the image
  4. Creates the necessary directory if it doesn’t already exist

Now when you run the test and it fails, you should see the failure stored in the directory you specified.

Conclusion:

Haven’t used this much yet but it seems really promising. Let me know what you think and if you wind up using it! You can find the completed project here: https://github.com/joesus/SnapshotUITesting

--

--