This workshop will be retired on May 31, 2020.
Bummer! This is just a preview. You need to be signed in with a Pro account to view the entire video.
Implementing UI Testing25:48 with Amit Bijlani
Utilizing the XCUIApplication, XCUIElement and XCUIElementQuery classes to programmatically test a user interface.
So for the sample code for this project, we're going to 0:00 reuse the project files from the build an interactive story app course. 0:03 This is a great example because the views that are presented 0:08 are totally dependent on the decisions we make. 0:11 We can test whether the decision tree we're creating is valid and 0:14 if the user experience is in line with our expectation. 0:18 So if you don't remember this app or this project, here's a brief overview. 0:22 You're presented with this main home screen where there's a text field. 0:26 So I'll enter my name, click Done, and click on Start Adventure. 0:32 And then we're presented with options on subsequent screens. 0:37 So I can stop and investigate, can explore the rover, or 0:42 I can just return back to earth, and play the game all over again. 0:47 So this is a very simple app that is an adventure game and 0:51 it presents you with options each step of the way. 0:55 So let's see how we can test out this decision tree. 0:59 Now I will only show you how to test one of the screens. 1:02 I will leave it up to you to go ahead and test out the entire workflow 1:07 on your own as a bonus exercise. 1:12 So going back to the project. 1:16 We need to click on the project file, which is InteractiveStory. 1:17 And we need to add a new testing target, so we'll click on the Add button. 1:23 We'll scroll down and we'll choose UI Testing Bundle. 1:28 And everything should be fine here if not adjusted as needed, and 1:33 then click on Finish. 1:36 So it added a new testing target for us and it created a folder called UI Tests. 1:39 And within that folder is a class. 1:44 This is a subclass off XCTest, 1:48 which contains the set up, tear down, and test methods. 1:52 Unlike unit testing, 1:56 UI testing has a convenient recording feature where the system 1:59 watches all our interactions with the app and writes out corresponding code. 2:03 Once the code is written, we can play it back or tweak it as necessary. 2:09 So down below in the Debug bar, 2:13 there should be a red button that says Record UI Test. 2:15 So we'll go ahead and hit that button. 2:19 And I'm gonna remove my name. 2:23 So we should start from scratch. 2:27 So I'll hit the pause button here and let's delete this. 2:29 All right, and I'm gonna remove this comment. 2:36 So we'll reuse this test method called TestExample. 2:38 And I will rename this to testInteractiveStory. 2:44 This test method should be aptly named. 2:50 so whatever feature or 2:53 function you're testing, that's what that test method should be named as. 2:54 In this case, we're actually testing the interactivity off the story. 3:00 So that's what I'm saying testInteractiveStory. 3:04 Now make sure that you're inside the test method because that's where 3:07 the record UI is gonna write out its code. 3:12 So I'll hit this record UI test button in the debug bar, 3:15 which is going to run the application in this simulator. 3:18 So I'll just move this here and you will notice that as I am interacting 3:23 with the application, there's code being written in Xcode. 3:28 So I'll put my name, hit the Done button, and I'll click on Start Adventure. 3:33 So basically, it's mimicking in code whatever I am doing with the application. 3:39 So before we dive into all this code, let's replay this. 3:45 We can actually replay this entire test that has been written down though. 3:49 So each test method has a play button right next to it. 3:54 So if I hit on Play, 3:58 it will replay all the interactions that I had with the application. 3:59 So it opens up the app it clicks on the text feel 4:08 it enters my name, and then the test failed. 4:12 Now UR recording is great but. 4:17 but it is also very important for us to have a knowledge of how this stuff works. 4:20 Because it does not always work perfectly. 4:26 So it's saying Testing Failure- Neither element nor descendant has keyboard focus. 4:31 So I kind of understand what happened here. 4:36 So what we'll do is we'll comment this out for now. 4:38 And let's replay this once again to make sure that it works. 4:45 So it's launching the app. 4:49 It's entering my name and it hit the Start Adventure button. 4:52 And now it says, test succeeded. 5:00 Beautiful, wonderful. 5:02 So it seems like we have successfully automated UI testing with code. 5:05 It was that easy and we're done. 5:09 Well, maybe not so fast. 5:12 Let's first figure out how all of this is happening. 5:15 Let's dissect this code that it generated. 5:19 So in line 33, it says let app= XCUIApplication. 5:23 What does this class do? 5:30 Well, it launches an application process, and 5:31 then we have reference to that application where we can query it for UI elements. 5:35 And then we can interact with them. 5:42 For example, like we were typing text in the text field or 5:44 we are tapping the Start Adventure button. 5:48 Now this next line is very messy. 5:51 The main goal of this line is to get a reference to the text field. 5:54 It achieves this by navigating the view hierarchy. 5:59 Of course we can see the view hierarchy when we're running the application and 6:02 debugging the view. 6:06 So if you're ever confused about the view hierarchy, you can check it out yourself. 6:08 Let's run our application in the simulator. 6:13 And then we can go over to X code, click on Debug, and 6:17 click on View Debugging, and then say Capture View Hierarchy. 6:22 Essentially, it takes the current state off our application and 6:28 it breaks it out into this 3D viewer. 6:34 You can see this 3D view. 6:38 And on the left side, you can see this tree structure. 6:41 Where you have the parent and you have the children and 6:45 you have all these leaf nodes. 6:49 For example, inside the UI button, you have a UI label. 6:52 So there are two concepts that you have to understand when we talk about UI testing. 6:56 One is how do you access all the children nodes or 7:02 how do you access all the descendants? 7:06 For example, this main parent over here is UIWindow.. 7:09 Now if I say descendants of UIWindow.. 7:14 It will be everything in here. 7:17 Basically everything that you, all of these elements. 7:20 All of these UI elements are descendants of UIWindow.. 7:22 But if I say give me all the children UI window, there's only one child. 7:28 This is the only direct descendant of UIWindow., 7:33 which is this UILayoutContainer View. 7:37 So that's the key distinction we have to understand when we're querying for 7:41 UI elements. 7:46 So let's go back to our code. 7:47 Over here, you'll see that it has all of these methods, you know, 7:50 one says containing, one says children, then it says element. 7:55 Then it says, children (matching: .textField), 8:00 all of this crazy stuff going on. 8:04 Now looks like the recorder is not exactly optimized to write efficient code. 8:07 Fortunately, by understanding some of the basics, 8:12 we ourselves can do a better job at writing the code. 8:15 So we talked about descendants and children. 8:19 There are two main classes we need to learn about. 8:22 One is XEUI element query and the other one is XEUI element. 8:26 So what I'll do is I'll create a break point here and 8:31 we will run our test once again. 8:36 So now we have an instance of the app or an instance of XCUI Application. 8:42 Which means that we ourselves can write queries in our console in LLDB. 8:48 We'll write some code. 8:54 So I'll say p, which is for print output, app. 8:57 But here I have access to a whole bunch of methods. 9:03 So let's see, I talked about descendants. 9:07 So I will choose the descendants method and we can choose what type. 9:13 So if you look at this code here, there is a type matching, we'll say textField. 9:19 So let's see if we can access all of the objects that match textField. 9:29 So you will notice that we get back and XCUIEElementQuery. 9:35 That's because these methods are designed to be chained. 9:41 You can chain one query with another or 9:45 you can actually get back an actual element. 9:48 So if I wanted to get back an element, I would do .element.debugDescription. 9:52 And now this is a little more verbose where it gives me exactly what it found. 10:01 Says element sub tree which traits and paths. 10:07 And finally if it actually finds an element, 10:12 it will tell me that there is an output. 10:15 It did find a TextField but there is no identifying marker for the TextField. 10:19 It says, Find Descendants matching type TextField and 10:25 the Output says that it found a TextField. 10:29 So how can we do this, how can we do this better? 10:32 So let's clear this over here. 10:36 And let's try to find a textField using the children method. 10:39 So I'll do app.children and the rest are gonna remain the same. 10:46 Let's expand this here. 10:53 And as you can see, there was no output here. 10:56 So it could not find a TextField using the children method and 10:58 it shouldn't be able to because there are no direct descendants off the app node or 11:05 the tree, the root of the tree. 11:10 However, there are descendants that's why we were able to find the TextField. 11:14 Now one thing to do for 11:19 us to make the TextField identifiable is we can head on over to the storyboard. 11:21 And actually add some accessibility identifiers. 11:28 So, heading on over to the storyboard. 11:32 I'll click on the Text Field, open up the Utility pane and 11:34 then click on the Identity Inspector and 11:39 under Accessibility, right here, I can enter an identifier. 11:42 So I would say username and we can give it a Label as well, Enter a name. 11:47 Remember the other benefit of UI Testing is that you're 11:54 actually adding accessibility through applications so 11:59 that disabled people can also now have access to your app. 12:03 All right heading back over to our UITests now instead of 12:07 having all of this code, we can easily get rid of this entire block of code. 12:13 So now after getting rid of all that code what we can do as we can say it, 12:20 app.textFields and then use the identifier, the accessibility identifier. 12:26 And then get access to the textField. 12:34 So this is basically using the descendants query. 12:37 But it's a convenient method of using the descendants query 12:42 where we just say app.textFields, give me the username. 12:45 Right, so let's run this test method once again. 12:50 So you can see it successfully found the textField using this convenient method. 12:56 And just like text fields, if you Command+Click on it, you will notice that 13:01 we have convenient methods for several different UI elements, 13:07 images, icons, searchFields, scrollViews, so on and so forth. 13:12 All you have to do is Command+Click and you will see all the convenient methods. 13:18 So here you can see that this convenient method buttons is being used. 13:22 So why did our test initially failed when it was captured by the UI recorder? 13:28 Well, for some reason, after clicking on the Done button, 13:34 it was typing a return key inside the textField and this is where it failed. 13:39 Now why did it fail? 13:46 And how can we debug the reason why it failed? 13:48 So, let's go ahead uncomment this line, I'll uncomment it here. 13:53 And we will run this test once again, with this line uncommented out. 13:58 And I'll show you how we can debug this failure. 14:04 So still running and there we go it says, test failed. 14:09 Now in the navigator, you will see that the last icon 14:14 here is the Report navigator, we can click on that and 14:18 here you have all the builds and the tests. 14:23 So if we click on the Test, 14:28 you will see that it has all the steps that it took to pass the test. 14:30 And here you have the testInteractiveStory method, 14:35 there is the setup that it tap on the username TextField. 14:39 It typed the text Amit in the username TextField then it tap the Done Button and 14:43 then it failed. 14:48 Where you see the text in red is where it failed. 14:50 Now if we expand that you will see that there is like a little preview icon and 14:54 each step of the way you will see that there is a preview icon. 14:59 If I click on the preview, 15:03 it will show you a snapshot of the simulator what it was doing at the time. 15:05 So free expand here and after it typed name, 15:11 you will see that the TextField has come up. 15:15 So this is a great way to see what was going on while it was 15:20 conducting the UI testing. 15:25 So down here where it failed, we can expand that as well, 15:28 we can look at the Preview icon. 15:32 So, we can see that there is no keyboard. 15:35 And it says right here, neither element nor any descendant has keyboard focus. 15:39 That's because there was no keyboard and it gives you an element subtree 15:46 over here of all the of various elements that were present at the time. 15:51 And this is the Element tree that we talked about, about parents and 15:56 children and descendants. 16:00 So it gives you a complete dump of all the UI elements and 16:02 the tree structure at the moment. 16:05 So when I tap the Done Button. 16:08 It says, Find Done Button, Synthesize event. 16:11 Let's see what it looked like. 16:14 So here is where it's about to press on the Done button or tap the Done button. 16:16 And then when we come here as you can see there is no keyboard. 16:21 So in that last test if I go back to our code here. 16:26 It's trying to type the return key. 16:31 So a lot of times in your keyboard you either have the return key or 16:35 you have one of the action buttons. 16:39 And in this case we have the Done button so there is no return key. 16:41 And you also notice that it's trying to typeText. 16:45 Where is it trying to type this text? 16:48 Obviously, the UI recorder made some kind of error. 16:50 Now we don't really know what was thinking at the time but 16:55 at least now we can debug the various steps and the failure where it occurs. 16:59 Going back to the Report navigator, you can also see that there is a Log tab. 17:05 If you click on the Logs, it will tell you exactly what happened, it's a like, 17:12 I was running this class, this method within that class and 17:17 where exactly that failure occurred. 17:21 So this is a great way to debug your UI testing issues. 17:25 And the best part is that it gives you a snapshot of what the screen looked 17:30 like at the moment. 17:35 So what we'll do is we'll delete this line of code cuz as we can see that line of 17:36 code is where the problem was occurring and really it shouldn't be there. 17:44 And we can actually condense those two lines of code. 17:49 So instead of having the Done button, 17:52 we can even remove the Done button and we can simply have a return. 17:56 Because the Done button is really the return button. 18:01 And if we just put Amit \n which means that right after you type 18:04 the name of the person, you hit the return key to dismiss the keyboard. 18:09 So let's run our test to see if it works. 18:14 So there you go, we were successfully able to debug our UI testing code. 18:22 And fix it but this knowledge only came to us once we got an understanding of 18:27 all the classes and methods that builds this UI testing API. 18:34 Now the goal of unit testing is not just tapping around the application, 18:40 it is also a validation. 18:44 If you tap on a certain button, does it result in the expected functionality? 18:47 In this case, tapping on the Start Adventure button, it should take us to 18:52 another view which is the other view that contains the Stop and Investigate button. 18:57 So we need to validate whether in fact this other view contains this Stop and 19:02 Investigate button. 19:07 So now let's write an assertion. 19:09 So we'll say XCAssertTrue. 19:12 So what are we asserting? 19:18 We're asserting to see whether the Stop and Investigate button actually exists. 19:19 So we'll say Stop and Investigate. 19:25 And we will check to see whether that button exists. 19:31 Right, let's run our test method once again. 19:35 And there we go, it was successful. 19:40 If the button didn't exist or 19:42 we mislabeled the button it would have failed. 19:44 So we can try that as well. 19:50 Now there are several different ways where you can find elements. 19:52 And that's one of the key things about finding and interacting with elements. 19:57 And I highly encourage you to look at the documentation to explore 20:02 The API methods for XCUI element and XCUI element query. 20:06 So I mislabeled the stop investigate button. 20:12 And this test failed. 20:15 So I'll undo this change. 20:19 Now, one other thing is that, what if. 20:22 We didn't know exactly the name of the button. 20:27 What if it was not stop and investigate? 20:30 What if it was stop and something else? 20:33 If was just labeled stop, you didn't know exactly the name. 20:37 We have to make our UI tests flexible enough because we're 20:41 dealing with hard coded values here. 20:45 It does not make our test very flexible. 20:48 So what we can do is we can use predicate matching and, 20:51 for example, we can just say like let stopButton = 20:57 app.buttons.matching. 21:02 And we can use this variation of the matching method 21:11 where we can pass it a predicate. 21:16 So I'll say predicate format. 21:18 And the format is basically, 21:24 I'll say label BEGINSWITH. 21:28 Oops, BEGINSWITH Stop. 21:39 And now, over here in the assertion, 21:44 we'll simply put the name of the button that says stopButton. 21:47 Missing a closing parentheses. 21:53 And let's run our test once again. 21:58 Something happened. 22:03 It is failed. 22:06 What we need is the element. 22:07 That's what we missed because remember that a query, 22:11 an XCUI element query, always results in a query and not the element. 22:14 If you want the element, then you have to ask for it or you have to retrieve it. 22:19 All right, let's run our method. 22:26 And it succeeded. 22:29 So let's just look at the documentation. 22:31 I will look at XCUI Application. 22:34 And using the documentation, 22:39 you can see all of the methods that are available to you. 22:41 And we have the XCUI element. 22:46 Goes to the some of the main classes XCUIElement and query but 22:50 there are other classes like XCUI device or 22:56 remote which you can explore on your own. 23:01 So, here are the two main methods that I was talking about, children and 23:05 descendants. 23:09 You have the typeText method to enter text into text fields. 23:10 You can move the cursor because you can use this on OS X as well. 23:16 There's clicking, there's scrolling, 23:20 there are all kinds of gestures like double tap, two finger tap, press, 23:23 swipes, interactions with sliders and pickers and calculating coordinates. 23:28 And finally you have the XCUI elements query which basically contains 23:35 all the querying methods like children, descendants, containing, matching. 23:42 Then you can access them by index, check out the count, retrieve the element. 23:47 All of the methods that are related to actually getting back 23:55 a result in query or accessing the element that was the result of a query. 24:00 Now last but not least, I want to show you the accessibility inspector. 24:07 If you hover over Xcode on the dock and 24:11 right-click on it and click on Open Developer Tools. 24:16 You can click on Aaccessibility Inspector and 24:22 what this does is it checks out the application. 24:26 Here, let's run our interactive story app. 24:31 You can select the simulator. 24:38 What you click, whatever you click here. 24:41 So that's say I clicked on the button or the text field. 24:44 So we can inspect all of the elements that are there in the UI 24:49 to check if they actually have some kind of accessibility identifier. 24:55 For example, text field has the label that says enter a name. 25:01 The button has a label Start Adventure. 25:07 So if you find any UI element that's missing some kind of accessibility 25:11 identifier or you don't know exactly where this UI element is, 25:15 the Accessibility Inspector is a great tool for you to sit and 25:19 inspect all of the elements. 25:23 So as I've mentioned previously, I'm just going to test one of the actions here. 25:27 Feel free to download this project and go ahead and 25:34 implement the rest of the logic to test out the entire user decision tree, 25:37 so that you can practice your UI testing skills 25:44
You need to sign up for Treehouse in order to download course files.Sign up