I’ve been meaning to try out Xcode’s UI Testing support ever since they introduced the feature way back in Xcode 7 / 2015!
In the past I’ve tended to worry that UI testing was too brittle - it was too easy to change the appearance or layout of the UI and accidentally break tests, even though the functionality was still ok. It’s also a really major committment to retro-fit UI testing to an existing application of any significance, which has made it hard for me to adopt for existing projects.
However, I’m currently working on a new application with desktop and mobile aspects to it, so this seemed like the ideal time to give it a go.
So far the jury is out on how useful it’s going to be, but one thing was immediately irritating me.
By default, when the user starts up an application in macOS, it will restore any documents that they had open - even ones that haven’t yet been explicitly given a name or saved.
This is great for the user, but not so helpful for UI testing, where we really want to be starting from a known state - generally a blank slate.
It’s easy to end up crashing or halting the application whilst debugging it (and the tests themselves), often leaving it with half-finished or inconsistent documents which it will merrily attempt to restore.
For normal debugging, Xcode has a solution - a way to suppress this restoration when it runs the application, in the “Run” part of the scheme:
Annoyingly though, when the application is launched via UI testing, this setting does not get applied1.
Luckily, there’s a way to achieve the same thing, in code, by passing the right arguments to the application when we launch it from our test.
I knew that there was a flag that would do this, but couldn’t remember what it was - and it took me a ridiculously long time to find the correct incantation:
let application = XCUIApplication() application.launchArguments = [ "-NSTreatUnknownArgumentsAsOpen", "NO", "-ApplePersistenceIgnoreState", "YES" ] application.launch()
This technique can be usefully extended, by passing your own custom arguments as well as system ones.
You can then pick these up in your application delegate (see here for an example), and use them to trigger extra setup code - for example to put the application into a known state or load a test document for your tests to play with. Setting up state like this can theoretically be achieved purely with simulated UI actions, but doing so can get complicated, is slow to execute, and if the main point of it is just to get the application into a known state before testing something, it arguably makes more sense to take that work out of the tests.
That said, you’ve obviously got to be careful not to take this idea too far. You want your UI tests to be actually testing the same thing that your users will be using, so if the first thing you do is tell your application to radically change its behaviour because you’re running tests, the effect could be counter-productive.
This is understandable, since Xcode actually just launches the tests, and it’s the tests themselves which launch the application by calling
XCUIApplication().launch(). However, it would be nice if the implementation of
XCUIApplication was smart enough to pick up the setting from Xcode. ↩