Join the webinar on 'Optimizing ISV Applications: Unleashing the Power of HeadSpin' on Dec 7
Hello everyone and welcome back to the Appium Pro Intro Workshop. This is the final module of the course where we will explore waiting for elements. Waiting For elements is the most important best practice for all of Appium testing. So, we're going to explore a couple of different ways to wait for elements and talk about the best ways to wait for elements as well. Okay, first of all, I should say that waiting for elements is really just one specific category in the more general topic of waiting for application state. The key point here is that Appium is just a sort of not very intelligent automation robot. It does what you ask it to when you ask it to do it. It doesn't perform any intelligent thinking. It doesn't try to emulate the thought process of a user. It can only emulate the actions of a user.
What this means is that Appium doesn't know what state your application is in when you ask it to do something. So, if your application is currently showing a spinner and downloading something over the network and you ask Appium to tap on a button, well that's probably not going to work because the button isn't displayed or it's not accessible at that point in time. Even if the spinner only lives on the screen for 100 milliseconds in the blink of an eye where a human user would obviously wait for that brief flash of time and then interact with the button. If you happen to tell Appium to click on a button right while that spinner is up on screen blocking the button or waiting for the button to be displayed, then you're going to have test failures.
And in general, this is an okay sort of scenario because we want our applications to be dynamic. We want nice smooth transitions, all of these things that users care about. Spinners are really good for having feedback, for not being able to tap buttons until they're really ready to be interacted with and so on. So, we just need to be sure as the test authors that when we're telling Appium to do things, we have a good idea of the state of our application. So, we don't want to tell Appium to do something until we know our app is in the right state. Or conversely, we could tell Appium to keep retrying something as long as it's not harmful until it succeeds, hoping that means that we got to the right state.
So, there are a lot of different kinds of state we could talk about. But what we really care about in this module is just talking about elements being present. So, this is the most common thing that we need to wait for and probably the most common source of Appium test flakiness when it comes to not waiting correctly for things. So, there are three basic categories of waiting strategy and let's go through each of them.
The first type of wait, we can call a static wait. This is basically waiting for a hard-coded and definite amount of time. So, in some of the first examples we saw in this course, I used Thread.sleep to cause the test execution to pause for a certain number of seconds. So, this is one strategy for waiting for an element. If you find that an element isn't available, you can use thread dot sleep and wait for 5 seconds or 10 seconds or whatever amount of time the element usually takes to become available. And then you could try running driver.findElement again.
Another option is to use something called an implicit wait. Implicit wait is a feature supported on the Appium server. It comes from the days of Selenium. So, this is something that also works in Selenium. An implicit wait is basically a server side retry where we first tell the Appium server what we want our element finding timeout to be. This is called the implicit wait timeout. We control this number using a command that looks like driver.manage().timeouts().implicitlyWait(). So, we can pass a number into this method and from that point on in the test anytime we try and find an element, if the element isn't available immediately when we ask Appium will not return an error to us, but instead it will retry. It will try to find the element again and again and again and again up until the timeout that we set has elapsed.
So, if we set a timeout of 10 seconds, then Appium will continually retry finding the element for up till 10 seconds. That means it might try 10 times or 20 times, however long it takes to find the element. Whenever it kind of wakes up after finding an element, it checks the time and if the time has gone past the timeout you requested, then it will fail at that point. And of course, if Appium finds an element on the first try or the second try long before the timeout has been reached, then it will return to element straight away. So, you're not kind of tied to waiting a certain amount of time, which is the problem with static waits or at least one of the problems.
The third kind of wait that we can use is called an explicit wait. And the explicit wait can wait for any kind of application state, not just for finding elements, but we can also use it to wait for finding elements. It's a similar concept to implicit waits. In an implicit wait, the server manages retrying for you up to the timeout that you set on the server, whereas with explicit waits, it's the client side that manages the retry. And this is typically not something that you write yourself. You don't write a for-loop that retries and has all of this exception handling and stuff in it. Instead, you use something called an "expected condition", which we'll look at and basically say, "We want to wait until this expected condition has occurred up until a certain amount of time and we'll keep trying looking for that condition up until that timeout. And if we don't find the condition is true, by the time we reached the timeout, then we'll throw an error."
So, let's talk about some of the differences between these strategies. First of all, static waits are, it must be said, quite bad. So, I list them here as a strategy, but it's not something I think one should ever use. The reason that we shouldn't use static waits, firstly, whenever you use a static wait, you're locked into waiting the amount of time that you've defined, regardless of whether you need to wait for that amount of time or not. So, imagine waiting for an element which might show up immediately or it might show up in five seconds depending on how long it takes to download something over the network. To find that element successfully using a static wait, you would have to use a very long Thread.sleep value. You'd have to use five or six or seven seconds or whatever it is. But that means that even if the element was available for the whole duration of that time, you would be waiting.
So, you would be wasting a ton of time in your test and wasted time in functional tests is not a good thing because functional tests like the ones we run with Appium already take a ton of time. Another problem with static waits is that you keep on needing to increase them. Inevitably, you run into situations where the wait wasn't long enough so you keep increasing it and increasing it and now all of a sudden all of your waits everywhere are super long making the first problem I described even worse. So implicit waits solve some of these problems. They are elastic in a way where if you try and find an element and it's available immediately, you'll get a response immediately. You won't wait extra time, but it still has a certain problem to it, which is that now imagine you are in a situation where you're looking for an element and you know that element will show up within one or two seconds.
You know that if the element doesn't show up within one or two seconds, the test should fail anyway because something is horribly wrong and there's no reason waiting any longer just to find out that, yes, the element never showed up. Examples of this could be an element showing up after an application view transition. It only takes one or two seconds to transition a view. It's not like you're waiting on any network content to download. You know that within a few seconds that element should be there. Now, because the implicit wait timeout is set globally, you end up setting it to the highest type of timeout you would ever need for finding an element. So, there are elements that might not show up until after some very complex processing has been done by the application or until some very large files have been downloaded. So, it might take legitimately 30 to 40, 50, 60 seconds for the element you're looking for it to appear. So, in that case, it would make sense for you to set your implicit wait timeout to something high like 60 seconds.
Now, if you go back to the first case, imagine we have a very high implicit wait timeout. But we have an element that hasn't been found in two or three seconds, and now at this point we as human users of the application know that something has gone wrong. But because we have a very high implicit wait timeout, we're locked into waiting for the whole rest of the 60 seconds just for the test to fail when we knew that it would fail long before. So, again, using implicit waits is not a great strategy because it means that in failure scenarios, you're also wasting a lot more time than you need. Explicit waits solve this by giving you the flexibility to use waits of custom lengths whenever you want. So basically, what you do with an explicit wait is you say, "I would like to wait for this condition for this amount of time." And then later on in your test you'd say, "Well, and I'd like to wait for this other condition this other amount of time."
So, you can very easily specify the timeouts in a much more granular way using explicit waits. So I recommend using explicit waits everywhere. So that's what we're going to look at an example of shortly in this module.
But first a word about expected conditions and how they work together with explicit waits. So the Appium Client Library that you use and that we're using in this course has a set of expected conditions built into it. These actually come from the Selenium project, so Appium just reuses them. The most common expected condition is one called presenceOfElementLocated, and basically it takes a locator. So, in the example here we have a locator consisting of an ID strategy and a myID selector. We pass that into the expected condition and then we're able to use that expected condition in the context of something called a WebDriverWait.
A WebDriverWait is a kind of flexible and reusable waiting interface where we create a wait object by constructing a WebDriverWait with the driver object and the number of seconds that we want to wait for whenever we use this wait object. And now we can call wait.until, and wait.until will take an expected condition and return the appropriate kind of response. So, for example, the presenceOfElementLocated expected condition will return a web element. So we can actually use this very handy little interface here to perform the explicit wait. And this will actually do all the retries for us under the hood so we don't have to do them manually on our own. So essentially this code says, "Build me a wait interface that waits for 10 seconds and use it to wait for a particular element by its ID and only give me back the web element once we found it, or if it hasn't been found within the time allotted, within 10 seconds, then throw an error."
There are other expected conditions, like there's one called titleContains and so on. So you can use them actually to wait for other kinds of application state, not just for elements to be present. You can wait for elements to be absent, for example, if that's part of what you need to do in your test. You could also create custom expected conditions. So, this is a whole set of interfaces that you can override and use for yourself. So, in this example here, I've created an expected condition called elementFoundAndClicked. So basically, what this does is it bundles up the action of finding an element and clicking it into one set of actions, basically, and only when both of them pass will the expected condition return true. So, this expected condition would be valuable if you found yourself in a position where you could rely if we find the element, but for some reason tapping on it was a flaky or not successful. So, you wanted to both find and tap in one go and only proceed on your test when both of those things work.
So, we can create custom expected conditions. We typically don't need to do this, but it's something that we can do and can be really useful as a way to keep your test code clean.
All right, let's take a look at our last and final example of this course called AndroidWaitTest.java. I will open it up in my terminal here. I've got AndroidWaitTest.java open and is extending our test based class like normal. And everything important is here in the login test method. It's actually the very same logic as the login test we saw in a previous module. What's different is we now have this WebDriverWait object that we've created. We've decided that 10 seconds is good for this particular wait object. And now instead of finding the login screen element using driver.findElement, we're finding it using wait.until, and then using the presenceOfElementLocated expected condition.
The rest is the same. You can see that a couple places we still use driver.findElement, which is a bit odd, but we'll talk about that in a moment. And we finish up by again waiting for the correct login text to prove that our test actually passed. So why did we not use wait.until on these two lines here where we still have driver.findElement? The reason is that because I know how this app works. I know that once we've got to the login screen, if I proved that the username is visible on the screen, then I know that the password and login button will already be present. I don't need to do any waiting for them. And in fact, if I do wait for them and they're not there, I'm just wasting time. So, the fact that I'm using driver.findElement is encoding into this test my assumption that these will definitely be here right there we don't need to wait for them any longer. So, it's a little bit of an optimization.
You certainly can always wait. You just might end up waiting a bit more time in the failure case if you know that an element should be there by that time already. So, let's give this a whirl here. I've got my Appium server running so now all I need to do is run the AndroidWaitTest, I believe is what it's called. So, we'll do that. There it goes, the Appium logs, and I will open up my emulator. There we go. And we're walking through our login test this time using only the best kind of wait, which is the explicit wait. So again, the whole point of this module is to say that whenever there is any chance that an element we need to interact with might not be fully present or fully loaded, we should make sure to use an explicit wait and that will save us a lot of headaches from failures due to elements not being found, even though really they are going to show up it's just that Appium didn't find them right at the precise moment that you asked.
Okay. Well, we have now reached the end of our Appium Pro Intro Workshop. Thank you for joining me and joining me until the very end. I hope that you've learned something interesting or fun about Appium and that you're excited to dig into it a bit more on your own and start automating your own apps. Don't forget that this is just one small portion of a much larger training, and later in 2020 I will be releasing a certification course for Appium and Selenium. They will be quite a bit longer and go into quite a bit more detail. So, if you liked this course, make sure you register for the waitlist for the upcoming course as well. All right. On behalf of HeadSpin University and Appium Pro, this is Jonathan Lipps signing off. See you next time!