Looking for more information about UI Automation? Check out my new book, published through the Pragmatic Programmers. Thanks for your support!
This is part of a larger study about UI Automation. For a good collection of the resources I’ve worked on, check out the features page.
When we last left off in part 1, we learned how to search for elements
in an iOS app’s view and interact with them. We learned a bit about how to use
logElementTree()
on any UIAElement
to figure out a path to what’s inside.
We set up wip.js
, our “work in progress” file we can use as a playground to
explore the Core Data Books sample app. If you haven’t gone through the first
part, you should.
Now, let’s start asserting that our interface works the way we expect.
We need a way for our tests to talk back to us. The singleton UIALogger
gives
us the basic logMessage()
method that does pretty much what you’d expect.
// Dig down to the nav bar
var target = UIATarget.localTarget();
var app = target.frontMostApp();
var window = app.mainWindow();
var navBar = window.navigationBar();
var navBarLabel = navBar.staticTexts()[0];
// Tell us what title you see in the navigation bar
UIALogger.logMessage("The navigation bar title is " + navBarLabel.name());
Put this in your wip.js
file, run it against the Core Data Books application,
and you’ll see the message in the ugly log pane.1 At the very least,
you could use this to output error messages if elements on the screen aren’t
what you expect, like so:
pipe this output unix-style into other tools we build ourselves.
// ....
var realTitle = navBarLabel.name();
if (realTitle != "Other Title") {
UIALogger.logMessage("Expected 'Other Title' but was '" + realTitle + "'!");
}
// ....
Apple provides a nice mechanism to help group tests together by calling
UIALogger.logStart(message)
. Now, all log output will be nested inside this
parent group until you close it. If you decide that this chunk of tests pass,
you close this group with UIALogger.logPass(message)
. If you determine that
there was an error, you can use UIALogger.logFail(message)
.
The these log groups can be collapsed in the log pane. The final state (pass or fail) of the group will show up in that collapsed row and you can expand the row to see the details if you need to.
// Start the log "group" with this message
UIALogger.logStart("Checking the navigation bar title");
// A flag we'll use to know if log group passed
var hasError = false;
var realTitle = navBarLabel.name();
if (realTitle != "Other Title") {
// When the assertion above fails, the following
// error is output in red within the log group.
UIALogger.logError("Expected 'Other Title' but was '" + realTitle + "'!");
// Set the error flag so we know how to close this log group
hasError = true;
}
// Now, we check to see if any of the tests above had failed.
// Calling logFail() displays the test group in red.
if (hasError) {
UIALogger.logFail("Some tests failed");
} else {
UIALogger.logPass("Tests passed");
}
This is a very low level way to work. It would be better to have an abstraction on top that simplifies our test cases and assertions. You could use something like Jasmine and add your own matchers and such that relate to UI Automation. But in this case, I think that’s a bit overkill. I like to use frameworks as lightweight as I can get away with. I found Tuneup JS by Alex Vollmer to do just fine.
Tuneup JS was built with UI Automation in mind. Its codebase is easy to
understand and extend, even for a beginner. Clone the Tuneup JS repo into your
ui_testing
folder and put it under lib
so your structure looks like this:
ui_testing
\--lib
\--tuneup_js
|--assertions.js
|--lang-ext.js
|--screen.js
|--test.js
|--tuneup.js
\--uiautomation-ext.js
The UI Automation scripting environment lets you import other javascript files
relative to the currently executing file. Put this at the top of wip.js
:
#import "lib/tuneup_js/tuneup.js"
You now have access to all the goodies in your script file. Let’s rewrite our test above with this framework.
test("Checking the navigation bar label", function(target, app) {
assertEquals(navBarLabel.name(), "Books");
});
Put as many assertions as necessary in there. If any of them fail, the whole test group will fail and Tuneup even gives you a dumped element tree of the entire window at the point of failure. Try altering the test above so it fails on purpose and you can see what the messages look like.
For the Javascript newbies, you’re calling Tuneup’s global test()
function
with two parameters: a string naming the test, and an anonymous function to be
called inside test()
that actually does the work. Notice that this anonymous
function takes two optional parameters, target
and app
. Tuneup passes
UIATarget.localTarget()
and it’s application in so you don’t have to keep
fetching them yourself. You can leave those two parameters out if you don’t
happen to need them. All arguments are optional in Javascript. For
completeness, here’s how we could structure the whole wip.js
file if we used
the parameters handed in.
#import "lib/tuneup_js/tuneup.js"
test("Checking the navigation bar label", function(target, app) {
// An example using the `app` parameter provided for us by Tuneup
var window = app.mainWindow();
var navBar = window.navigationBar();
var navBarLabel = navBar.staticTexts()[0];
assertEquals(navBarLabel.name(), "Books");
});
Inside the anonymous function, we make our assertions. assertEquals()
is one
of the many global functions Tuneup provides in assertions.js
. It raises an
exception if the first parameter doesn’t equal the second parameter. That
exception is caught by the enclosing test()
function which balances the
UIALogger.logStart(...)
and UIALogger.logFail/Pass(...)
calls for you.
Magic!
At this point, you have the basics for manipulating and querying a live iOS app’s interface. And we have a nice test framework to help us organize and output our assertions. When I’m able to continue this series we’ll start building a library of objects that know how to interact with our screens.
This is part of a larger study about UI Automation. For a good collection of the resources I’ve worked on, check out the features page.
Yes, it is ugly. Please, Apple, please give us a means to↩
My books...