UI Testing in iOS - Generating Accessibility Identifiers using Reflection

Table of Contents

One of the most annoying things about UI Testing in iOS is the need to assign Accessibility Identifiers to views that are hard to access otherwise. Could be that a view is deeply nested, or that it is not easily distinguishable from other views, lots of scenarios might lead to manually assign Accessibility Identifiers… But don’t you worry, friend, I got something for you!

What if we could generate and assign Accessibility Identifiers automatically, using reflection?

🎬 Hi there, I’m Jean!

In this article, I am going to explore this crazy idea and, who knows, maybe find something useful after all! 😃

The usual way


Usually, assigning an Accessibility Identifier to a View is rather straightforward…

final class SomethingViewController: UIViewController {
    
    @IBOutlet private weak var titleLabel: UILabel!
    @IBOutlet private weak var descriptionLabel: UILabel!
    @IBOutlet private weak var doneButton: UIButton!
    
    override func viewDidLoad() {
        titleLabel.accessibilityIdentifier       = "SomethingViewController.titleLabel"
        descriptionLabel.accessibilityIdentifier = "SomethingViewController.descriptionLabel"
        doneButton.accessibilityIdentifier       = "SomethingViewController.doneButton"
    }
}

Simple! 👌
However, we can easily spot the problem here, it’s a hell of a boilerplate! 😫

Now here’s the thing, what if we could iterate through all the Views of a ViewController and assign to each one of them an Accessibility Identifier consisting of the name of the ViewController (prefix) and the name of the View (suffix)? 🤔

Mirror, mirror…


That’s right! Let’s use the awesome Swift’s reflection API: Mirror! 🔮
To make it generic and reusable across all ViewControllers/Views, let’s wrap it inside a protocol called Accessible! 👍

protocol Accessible {
    func generateAccessibilityIdentifiers()
}

extension Accessible {

    func generateAccessibilityIdentifiers() {
        #if DEBUG
        let mirror = Mirror(reflecting: self)

        for child in mirror.children {
            if
                let view = child.value as? UIView,
                let identifier = child.label?.replacingOccurrences(of: ".storage", with: "") {

                view.accessibilityIdentifier = "\(type(of: self)).\(identifier)"
            }
        }
        #endif
    }
}

Now, let’s take a look at the above implementation. 🧐

The first step is to instantiate a Mirror reflecting the type conforming to Accessible (self). Then, we iterate through all children (properties) of self and, whenever a child is of type View, assign an Accessibility Identifier with the format: "ParentType.ChildProperty"

Simple! 👍
Now, going back to our initial implementation of the ViewController, things will suddenly get much smoother! 😎

final class SomethingViewController: UIViewController, Accessible {
    
    @IBOutlet private weak var titleLabel: UILabel!
    @IBOutlet private weak var descriptionLabel: UILabel!
    @IBOutlet private weak var doneButton: UIButton!
    
    override func viewDidLoad() {
        generateAccessibilityIdentifiers()
    }
}

That’s all! 🤯
Now, executing the command print app during UI Testing will output the following:

StaticText, identifier: 'SomethingViewController.titleLabel'
StaticText, identifier: 'SomethingViewController.descriptionLabel'
Button, identifier: 'SomethingViewController.doneButton'

Boom! 💥
We just generated Accessibility Identifiers using Swift Reflection! 🎉
No more manual assigning, no more unnecessary boilerplate! 💪

Worth a try? Did you regain faith in UI Testing? If so, follow me on Twitter, I’ll be happy to answer any of your questions and you’ll be the first one to know when a new article comes out! 👌

Want to read more about UI Testing?
Check out my 3 parts series:

  1. Painless UI Testing in iOS (Part 1) - Mocking the Network
  2. Painless UI Testing in iOS (Part 2) - Stubbing the Navigation
  3. Painless UI Testing in iOS (Part 3) - Disabling Animations

If you’d like to see a real-life example of an app doing extensive UI Testing, don’t hesitate to check out my Github repo! 📦

Bye Bye! 👋