Developer Tools • iOS • 40:15
iPhone SDK 4 provides a powerful new instrument for automated user interface testing. Learn how to write scripts to control your app, verify run-time behavior, and discover user-facing regressions quickly and efficiently. See how automated testing can help you write better apps that behave the way you expect.
Speakers: Michael Creasy, Matt Dreisbach
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Michael Creasy]
Hi, everyone and welcome. I'm Michael, here to talk today about Automated User Interface Testing with Instruments, or as I like to call it, "How to find bugs while you sleep." Now, I know a lot of you are developers, and you're building these great applications, and maybe you don't have a dedicated QA person.
You're trying to split your time between testing your application, actually developing your application, and testing the new features that you're implementing. So what I'm going to show you today is how you can turn a little bit of your time automating some of your tests and then get right back to developing those great new features.
Now, maybe you do have a dedicated QA person, or maybe you are that QA person. Well, I'm going to show you how you can automate your tests, kind of automate those tests that you do maybe every day, on every build, that kind of mundane stuff that you have to go through all the time, and then you can spend your time hunting down those edge cases and those more interesting scenarios. So let's get started. What am I going to talk about today? Well, first of all, why you should create automated tests.
And we'll talk about what is UI Automation? What is it that we have for you today? We're going to talk about how to automate an application; we are going through step by step by step how to build up a test case, how to actually create your first automated script; and, lastly, I'm going to cover some of the more advanced topics. These are the more interesting things that you can do with UI Automation. So why should you create automated tests? Well, you can find bugs while you sleep. One of the great things about automation is the system can be testing your application while you're doing something else.
You can be getting back to doing developing, writing those great new features for your application. You can be, maybe you're doing some other manual testing; digging in deeper into your application while the automation is running for you. You can create repeatable regression tests. So let's say you find a bug in your application, you write a script that covers that scenario, and then from that point on, you never need to worry about that bug coming back. You never need to be concerned that you've got to keep testing that because you've got a script now. That script is going to find that bug should it ever come back; something you never need to worry about again. You can quickly turn around updates to your application.
Now, users love getting updates to their application. Personally, I check the App Store every day just to see what new updates I have available. The trouble with pushing out an update for your application is that you have to test everything again because you don't want to introduce a new regression or a new bug that your user's going to run into.
So you want to make sure that you've maintained or exceeded that level of quality that you had previously. So the great thing with automation is that if you have a suite of automated tests, you can run through those with your new update, and you instantly know if you've maintained that level of quality.
So what is UI Automation? Well, User Interface Automation for the iPhone. Internally at Apple, we've been using UI Automation for a long time. This is how we do a lot of our testing. We're working through the User Interface in our applications, we're making sure that what the user sees is what we want them to see.
So we automate UIKit based applications. Now many of you are familiar with UIKit, you're using it to build your applications, and so really you don't need to do any more work for automation. It's just going to better work through your application. Fully touch based. Anything a user can do with a finger on a device, maybe Tap, Scroll, that kind of thing, you can automate through UI Automation. Support the iPhone, iPod touch, and iPhone Simulator -- one script runs in all three exactly the same. So this is any device that runs iOS 4.0 and supports multitasking, will run with UI Automation; one script exactly the same each time.
It's integrated into Instruments. So Instruments is an amazing tool. Maybe you've already been to some of the sessions on Instruments this week. So with Instruments you can monitor kind of your performance of your application, memory usage. And so we've added in the ability to automate your application. And what this means is that you can use the Automation Instrument alongside other instruments and see exactly what is happening in your application.
So maybe you've been trying to track down a memory leak, maybe you've had other performance issues with your application. You can now write an automated script that runs through your application with those other instruments, and you can pinpoint exactly what was going on when you saw, maybe, a memory spike or some other issue.
Fully accessibility based. Now, there's one key thing with accessibility that's going to make automating your application easier, and that's up here. The Accessibility Label. You set this in Interface Builder, you put a name in for your controls that you're building up, that is going to make your life when it comes to automation so much easier. It's not required, but it's going to make life easier because it will enable you to know exactly which control you're using when you're automating your application. And we'll show an example of this a little later.
So we use JavaScript to automate. And you're probably thinking, "Michael, why are you using JavaScript for automation?" Well, there's a couple of reasons. Automation has always traditionally been used with a scripting language. And so the reason behind this is that scripting language is very easy for anyone to pick up.
There's a lot of very talented QA engineers out there that maybe don't have any programming experience that you developers have, and so something like JavaScript is very easy for them to pick up and learn, yet it also has the flexibility and complexity for you developers to do more complex things, and we'll cover some of those a little later.
And lastly, with the JavaScript, it runs outside of your application. So the application that you're testing through your automation is exactly the same as what you'll submit to the store. You're not adding anything to your application in order to do the testing, so it's exactly the same code each time. So with that, I'm going to hand it over to Matt who's going to show us a quick demo of what UI Automation looks like.
[Matt Dreisbach]
So let's see how this looks like on a device. What we've got here is a piece of sample code it's available on the Developer's Site UI Catalog. We've gone ahead and written the script. You can see that we can identify buttons, navigate the navigation bar, click on things.
[ Running script ]
We can interact with text fields. So we're going to go ahead and set -- cap the field to make it First Responder and set the text. We can simulate various gestures such as Inch Open and Drags. Notice I'm not interacting with the device. Easy to do double taps. You can set the orientation, in case your app happens to run in landscape, you can do some testing there.
Show you a few more examples of it interacting with some controls, and then I'll show you exactly how you set one of these up in Instruments to run your script. Interacting with Switches here, Sliders. You can also simulate a Lock, and you can also do Backgrounding and Foregrounding of your app, so you can test the Multitask interface.
Okay. So how did I run that script? Let's go look at the desktop machine here, and I'm going to bring up a new Instrument session. You notice when you first launch Instruments, you'll be prevented -- presented with this template chooser. Under the iPhone templates here, you'll want to select the Automation template. Then you need to make sure you're targeting your iPhone.
So up here in the target chooser, select iPhone, and select your -- the application that you want to target. And then down here on the left there's a Choose Script. You just need to navigate to your script, and then start the Tracing session which will automatically start up the script. So you'll see as you run the automation, you can log the beginning of tests, and they show up in this Outline view. You can expand that and see more detailed logging inside.
We also have the ability to capture screenshots, so if there's something you're interested in onscreen while you're running your test, you see it over here on the right in the Extended Detail, it will display the screenshot that you took. And up at the top here in the timeline, we highlight green when you passed this test. If you were to fail a test, we'll highlight that red. So I'm going to hand this back to Michael, and he's going to tell you a little bit about how you write this script.
[Michael Creasy]
So now you've seen kind of an example script and how you can use that. So let's look at how we can actually automate an application. First thing I need to talk about is Elements. So every control in your application gets represented to UI Automation as an element, Now the base element is UIAElement. This is from what all other elements are inherited from. And there's a couple of properties on that that are really important to understand. There's a lot more than this, but these are the four that I really want to focus on today. First up, Name.
Now the name is what was set -- if you remember, we talked about accessibility, the Accessibility Label gets translated into the name of your element. And this is what makes it very easy for you to find individual controls in your application. The Value. As you'd expect, this is the value of a control. Say you had a text field, for example, any text that appeared in that field gets represented as the value. So you can very easily read maybe some text out of your application for verification purposes.
Elements. Well, you think of your application as having a hierarchy of controls. This gets represented as individual elements, so every element can contain zero or more other elements. Think, for example, a table here may have multiple cells as its child elements, and equally parent within one level back up. So let's look at the control hierarchy a little bit more. Here we have the rest of his sample from Developer website, and we're going to dig through this and see how we build up a line of code to access a particular element in your application.
So let's start off with the target application. This is the application that -- your application that you're testing. And we access that through UIATarget, localTarget, and the frontMost application. From there we move down to the mainWindow, which looks identical, it is, after all, the main window of the application. Move down from the mainWindow, we get to an individual view. In this case we have a tableView. Moving down from the tableView, we get down to an individual cell.
And you'll see how I've accessed this, we have an array of table views, we just chose the first one, so the Zero Table view. And again I've chosen the first cell which is highlighted here. Beyond the individual element, you get a child element, in this case, we're directly accessing "chocolate cake." So how do you go from that to actually building up a test case? Well, let's start with something really simple.
I'm going to start by tapping a button. Nearly every application uses a button at some point, you're going to want to tap one at some point during your testing. So we got a little code snippet up here that shows you how it's done. But what I want you to pay attention to is when I've worked through the hierarchy of the navigation bar, individual buttons, into the add button.
And so that's actually the plus button that you see at the top of the navigation bar, and the reason we can access it using the word "add" is because that's the Accessibility Label that we've set on that individual button. So we've made it easy for ourselves to go and be able to find that rather than having to see, oh, the zero button or the first button, you know.
Accessing it by name makes not only a more readable script, but it makes it much easier for you to write your script. So let's go ahead and tap that and see what happens. Well, we've moved to another screen. It looks like that worked. So what are we going to do next? Well, we've got a text input there, you probably want to type something into it.
We start off by declaring a local variable, in this case a bit of text with "Turtle Pie." It's a favorite recipe of Matt's, and he's promised to bring some into the office next week. So how do I get that text into that text field? Well, again, I work my way through the control hierarchy to the first text field.
There's only one there, so I don't really necessary access it by name. I can just go for the first one in our array and course setValue on it, and that's going to insert the text directly into -- into the display. So let's take a look at that. Sure enough we've managed to insert Turtle Pie.
Tabs are great in your application. It lets you display a lot more information users can navigate back and forth between. But how do you access individual tabs? How do you move between them in your application? Well, it turns out it's a lot like tapping a button. Let's have a look at this little code snippet here and see how it's done.
First of all, I'm declaring a local variable to access the tabBar directly so I don't have to write that huge long line of text every single time, I can just narrow it down to one element. Once I've got my tabBar, I want to know which tab is selected. So I'm accessing the tabBar, asking for the selected button, and getting the name of it.
Then I'm checking to see what tab is selected. Now, is it the Unit Conversion tab, and if it's not, we just tap that button, and let's see what happens. Well, we moved to the next tab as we expected. So let's move on in building up our test. Scrolling. You got a table in your application, you're already used to scrolling up and down to find individual elements, so let's see how we to that.
First of all, again, accessing localTarget, frontMost application, my mainWindow, and the individual table, and now I want to scroll to that Turtle Pie recipe we've added. So I'm using the function called scrollToElementWithPredicate, and what this enables you to do is scroll to an element that maybe you don't know the complete name of. This will come in handy if maybe you have some unexpected text, maybe some text that's come from the Internet, maybe a date or a time.
Something that you can't predict at the time you're writing your script. In this case, we could predict it, but let's have a look at how it works. So I create a predicate, namebeginswith "Turtle Pie," and that enables me to access that cell directly and scroll down to it.
So once you've worked through your application, it's great that you can tap individual elements, you can find stuff, you can scroll, but that doesn't make a great test case. For a test to actually work, you need some sort of verification. You need to actually verify that you've done something and that a test has passed or failed. So let's look at how we'd verify that we've added Turtle Pie to our recipe. This big chunk of code here.
Again, what I want to do is access the cell that contains Turtle Pie and determine if it's -- that -- if it's there or not. So first of all, I create a local reference to that cell, and I'm using this case, firstWithPredicate, and namebeginswith "Turtle Pie" as well. That will give me a reference to that cell. I then check if the cell is valid or not valid. And if it is valid, I go ahead and log a pass for my test; and if it isn't, then I go and log a fail.
And what cell.isValid is actually doing is checking to see if that cell exists or not. How do you go about logging information? Now, when you're writing your tests, you need to log as much information as you can because this is what's going to make it easy for you to diagnose any failures that occur. One of the most important things you need to log is when you start your test and when you end your test. And we've made this really simple.
All you need to do is call UIALogger, logStart and the name of your test, write your test, and then ultimately at the end call logPass, assuming your test is passing; otherwise logFail for a test that's failing. So it's very easy just to mark off in your code exactly when a test is starting and when it ends.
Of course, that's not everything. You really need to log as much as you can about what's going in your application. What we recommend is every time you interact with a control, log what you've done. That way when you go back to look at the log of your test, maybe when it fails, you can read through your log and see exactly the steps that have occurred. And that'll make it much easier to diagnose any failures that have happened.
But logging is as simple as just calling logMessage and a text string. But they say a picture is worth a thousand words, and sometimes text is just not going to be enough. So you can go ahead and log a screenshot. And, in fact, that's how we've captured all of the screenshots in this presentation.
All of the screenshots we've used were captured through UI Automation using code just like this. So you go ahead and call captureScreenshot, give it a name of the screenshot, and this gets saved back in Instruments as the file name. So, you know, a little more useful name than Screenshot 1 is probably a good idea; otherwise, you're never really going to know what your screenshots are until you look at them. This will make it easier for you to find them, do any post processing that you need to do, after the fact. So with that, we're going to hand it back over to Matt, who's going to show you how the script we've just written works in practice.
[Matt Dreisbach]
So we've gone ahead and written this recipe script. You can see, just as Michael showed you, clicks the add button in the navigation bar and sets the text. We've added some other tests that enter the description information and the prep time of this recipe, adding some ingredients, just give you more examples of the type of testing you can do.
[ Running test ]
So this test is coming along well, but you'll notice that we're -- left with the recipe of the night and it might be nice to implement a Delete test. So I'm going to go ahead and walk through implementing this Delete test. In order to delete this item, the first thing you're going to need to do is select the Edit button from the navigation bar. So we're going to have to isolate that, click that button.
Then we're going to choose the switch to enter the Delete mode and then click the Confirmation Delete button. I want to exit Edit Mode again by tapping on that done button in the navigation bar, and then, once again, this time we'll want to verify that the recipe's been removed. So here's the last run we had on this test.
You'll notice there's this Edit button next to the script. If you click that, that will go ahead and bring up that JavaScript in the editor that you have selected for JavaScript. In this case, we've -- we have Xcode set. And I'll just run you through this script quickly here.
We've gone ahead and saved a reference to the local target here in this variable target, and the frontMost app and this Variable app, and then we've implemented a few convenience functions here that help us get back to the main list view because we know we're going to be doing that a lot. Let's see, just finds different tab bar and navigation bars and it goes ahead and selects those to make sure we're on the right mode.
And you'll see at the bottom here, we're calling the various tests we've implemented -- this is fairly simple script, you may want to add more complexity to yours, but I wanted to give you a simple example. And here's the Add Recipe test that Michael's been working on. So let's go ahead and add that Delete Recipe test. We're going to start out by just sort of framing this function here, function Delete Recipe. It's going to take one argument, the name of the recipe, and we're going to log the start of the test.
And we're going to call this Return to Recipes continue function to get back to the list. And then, if you recall, the first thing we want to do is isolate that navigation bar and the -- the Edit button and the navigation bar. We're going to log what we're doing, and on the app Object, find the navigation bar, get the list of button elements, and select the Edit button, and click Tap.
Now one of the things you'll notice with the automation is that we will patiently wait up to a certain timeout for elements to come on screen. One of the things that happens often in automation is that the thing that you're trying to target won't be -- there'll be animations that won't be on screen right away, so in this case, we're actually getting the right button, and we're making sure its name has changed to Done before moving on. So now that we know we're in Edit mode, we're going to go ahead and scroll to the element using that scrollToElementWithPredicate. In this case, when we're in Edit mode, the name of that cell will be prefixWithDelete, so we're constructing this string Delete and the recipe name.
We've saved a reference to that cell, and now we're going to look inside that cell for all of its switches. There'll only be one switch. We're going to get the first switch and set its value to one. Added some delays in here to make this run slower so you can see what's going on. Next, we look in that same cell, and we look for the Confirm Deletion button. So we're using Predicate again, nameBeginsWith "Confirm Deletion," and we're tapping that element.
Then we want to exit the Edit mode by selecting the done button in the navigation bar. And we'll search the table to make sure that that element has been removed. And we're going to take a screenshot there to make sure that it's gone so we can confirm later if we need to. And then, finally, all we need to do is call our new delete function here at the end.
Okay. So let's return to Instruments and let's see that run. Notice I don't need to refresh anything in Instruments, just hit Record again, it will pick up the changes. This time I'll let you watch it from the desktop side.
[ Running test ]
See how easy it is, once you have these tests to just iterate them over and over again. If you write them flexible enough, you can add several recipes to your, you know, app, and stress test your app. You can run this in conjunction with other instruments to maybe do some leak checking, as Michael mentioned. Here's our new test.
[ Test running ]
Okay. As you can see, we've passed the test, and we have the screenshot, we can see that it's been deleted. One of the things, though, it's all fine and dandy to know we have a test that passes. But what's really valuable is to know if we can catch a failure.
So it's important when you're writing your tests to test that failure case. So let's go ahead and switch over to Xcode, and we're going to select the Delegate function here that's responsible for deleting this recipe from the list. I'm just going to go ahead and add a Return, simulate a bug.
[ Writing code ]
There we go.
[ Background voices ]
I'm missing...
[ Writing code ]
[ Writing code ]
[ Applause ]
We got a test failure. So I'll go ahead and hand this back to Michael, and he can talk to you about some additional things you can do with the automation.
[Michael Creasy]
So now you've seen how we can not only, you know, show that your test's a pass, but we can also catch a failure as well, so you know when something has failed in your application. So now I'm going to cover some of the more advanced topics, some of the more interesting things that you can do with UI Automation. So we'll start off with handling unexpected alerts.
One of the traditional problems that User Interface Automation has had is what to do when something unexpected happens. When something like this pops up on screen, I mean, you are testing on a phone after all, what happens if you get a text message in the middle of your automation run? Well, traditionally, everything would now fail at this point.
Your world, as far as the application is concerned, has changed; something has appeared on screen; your script wasn't expecting this to happen; it can't find the controls it's looking for; and everything just falls apart. And you end up looking at a whole load of failures, and you find out that Johnny sent you a text message and destroyed your automation run. So let's fix that. We can automatically dismiss these alerts. Let's have a look at how we do that. Well, create a function called onAlert. This is our Alert Handler function.
And all I've done in this function is just log a message, just logging the fact that this alert happened. And then I return false. And what false is telling the system is that I, as a script writer, have not handled this alert, and I want you to get rid of it for me.
And so what UI Automation is going to do is click that Close button, get rid of the alert, and your script continues running exactly where you left off. So whatever action you were doing previously, you go right back to where you were, and your script carries on as if nothing had ever happened.
Now occasionally, of course, you're going to get expected alerts. You're going to get stuff pop up that is that is part of your application. Now, maybe, as in this case, you've got a confirmation dialog. Now obviously you don't want to dismiss this alert automatically because this is part of your application, and you want to be able to deal with this appropriately when the time comes.
So let's have a look at our Alert Handler again. If we just run it as it is, we'd go ahead and probably click that Rename button, that is the default button, and that's not what you want to happen. So how do I deal with that? Well, I have this little code snippet here, that looks for this precise dialog box that's going to pop up, and when we find it, we're going to tap the Continue button instead of the Rename button and return true.
And that way we're by-passing the Default Handler, which means that UI Automation is not going to deal with this alert, you've told the system, you know, "I've dealt with this alert. Don't handle it. It's all dealt with. It's all fine." And at this point, your script goes back exactly where it was and continues running. So you don't have to sort of try and write extra code all over the place to deal with alerts as they happen, you just have one function that deals with them no matter when they occur.
And Multitasking. Huge new feature in iOS 4.0. Something we're very excited about, and everyone else is excited about it. You've probably already started implementing Multitasking in your application, and you want to be able to test that. So we try to make it as easy as possible to test Multitasking, and there's only one line of code you need to do to test Multitasking in your application.
As you know, when a user exits your application by tapping the Home button or causing some other application to come to the foreground, your application gets suspended. So we've simulated this entire work flow just in this one line of code. We code deactivateAppForDuration, give it a time, in this case ten seconds, and what that's going to do is simulate a user tapping the Home button on your device, your application will get suspended, ten seconds later your application will resume, it will come back to the foreground exactly the same way that a user would do it, as if they'd brought up the app switch, or tapped your app's icon, brought it back to the foreground.
And your script just continues running. You don't have to do anything else. That one line will simulate suspending your application and resuming it. It's that easy to implement and test Multitasking. So how do you deal with Orientation? All right, you've got your devices hooked up to your desk, it's running with your Mac, and you can tap on various things, but how do you simulate Orientation? We've not gone and built a robot that will rotate a device for you. That would have been awesome, but we kind of had to do it in software instead.
So how do we go about doing that? Well, again, really, really simple. Just simulating that Device Orientation. And to do that we setDeviceOrientation with one of the constants that are defined, in this case Device_Orientation_Landscapeleft. This is what's going to rotate the device. That just simulates the rotation that's occurred.
Now if you deal with raw accelerometer data in your application, this is not actually going to do what you want. This is just purely simulating that a rotation has occurred. So if you're using a UI device just to get notified when a rotation has occurred, this will do that work for you. Of course, once you've rotated, you do need to rotate back again.
And what I recommend is if you're doing a test that involves changing the orientation of your device, that you set your rotation at the beginning of your test, and then set it back at the end of your test to Portrait. That way, no matter what happens, your next test that runs, it's always back in a known, good state, and you don't risk getting confused about not being in the right orientation for whatever tests are carrying on. So let's talk about some of the advanced touch interactions that you can do.
What you've seen so far is how we can tap into individual controls and elements in a very simple fashion. But that might not be enough for your application. So there's a few things that I want to cover. We'll cover how you can do more advanced Taps, some Pinches, and Drags and Flicks. So let's have a look at, first of all, Taps.
So you see from this first line of code, I'm just tapping at an arbitrary location on the screen. So I've set x and y coordinates, and that simulates a tap happening at that location. Doesn't matter what's at that location, it's just going to tap right there. Double Tap is exactly the same. And also a two finger tap. So think how, like in Maps, do a two finger tap to zoom out, and this is how you do that in a simulated environment.
Pinches. So you can Pinch Open and Pinch Close, maybe to zoom in, zoom out, that kind of thing. And this is little bit more complex. Code pinchOpenFromToForDuration. And so again this is coordinates that's starting and ending the pinch and the duration, this is the time in seconds for that action to occur. So in this case, we're doing two seconds. Well, what that means is, for your application you may want to simulate a very slow pinch or a very fast. You have some flexibility in the timing of that action happening. Equally, Pinch Close does the exact opposite, zooming in.
So Drag and Flick. Maybe you just need to scroll through a table really quickly, maybe you've got other elements that you just need to move around on screen. Call dragFromToForDuration, and this is our starting location, our ending location, and a time. So in this case, this is going to drag from 160 by 200, to 160 by 400, over a period of one second. Again, you have the flexibility of specifying a time.
Whether you want it to happen very quickly or very slowly, completely up to you and what your application needs to do. flickFromTo is a little bit different. A Flick is, by definition, a fast action. So there is no time on this because it has to happen quickly. But otherwise, code in much the same fashion.
Timeout. As you see when Matt built up his script, occasionally you need to wait for an element to appear on screen. Maybe you've got some great animation or transition that is occurring in your application after you click the button, and you want to wait for that to happen before you start interacting with another control.
So you want to wait for an element to appear on screen before you interact with it. How do we do that? Well, just try and access the element directly. And what this will do is keep trying to access that for a given amount of time. Once it finds that element, that line of code returns, and you can carry on doing whatever you need to do with it. By default will wait up to five seconds. If that element doesn't appear within that five second period, it will get an exception because clearly something has happened in your application that wasn't expected, and you need to deal with that in some other way.
But maybe five seconds is too long or too short. Maybe you've got this fantastic animation that takes ten seconds to occur, but it's awesome, but you need to wait that amount of time. So much like a stack, just go ahead and push a timeout, in this case I'm pushing a two second timeout to the top of my stack. I write whatever code that I need that's going to access these elements whenever I want to wait two seconds to occur, and ultimately I pop the timeout.
And the whole reason we have this timeout is so you don't have to pepper your script with delays. You might have seen that during Matt's run, that we had a few delays in there -- that was purely to slow down the script to make it easier for you to see what was going on; otherwise it tends to run through stuff really quickly and makes it quite difficult to see. But using this, you end up with a robust script, waiting exactly the right amount of time for something to happen.
So in summary, what you've seen today is how you can take UI Automation, how you can write scripts for your application very easily. It's going to cover as much testing as you can so that you don't need to spend a lot of time manually testing your application. You can have something do it for you while you focus on other things.
There's another session a little later this afternoon on Accessibility, so if you want to know a little bit more about how you can name your elements and interact with them, a little later this afternoon go to this session, they'll tell you more about it. I've got a couple of resources up here. We've got some documentation that's great, it will cover all the elements we have and how to write scripts.