In this chapter, let's discuss wait strategies.
Sometimes you have a website and it may take a while for elements to load. Or they may dynamically load based on what it is that you're doing.
For example, you may click a button which spawns some action in the background, and the UI may show a spinner or some type of indicator letting you know that it's processing.
Your script won't know to handle this unless you tell it to do so.
There are multiple ways that you can do this.
Let's first talk about implicitly waiting.
You can make a call one time in your script to say, "I want you to wait up until this amount of time."
For example, let's say after we create our ChromeDriver
, we could do a driver.manage().timeouts()
, and this will give us a Timeouts object.
If we do .timeouts
we see that there are multiple methods here. One being the implicitlyWait
, so let's look at this one.
implicitlyWait
takes two arguments — tt takes the amount of time to wait, and then it also takes a time unit.
Let's say that we entered 30 and then we say for time unit, we would like seconds.
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
What this is saying is that any time WebDriver needs to interact with an element, that it should poll the website for up to 30 seconds until it finds that element.
If it finds the element before 30 seconds, then it will interact with it then.
If not, then it will wait and continue to poll until it finds the element, or until the 30 seconds are up.
If the 30 seconds are up and it has not found the element, then it will throw a NoSuchElementException
.
The 30 seconds that we have here is simply an example. You could make this whatever you wanted it to be.
Caution
Be careful when using implicit waits because you're setting this at the project level, meaning it will wait up until this amount of time for all interactions, and this could slow down your project if you're not careful.
There is another approach that allows us to do explicit waits.
When we use explicit waits, we use them only when we need to. So, if we know of examples where our application needs to wait, we can plug them in right there.
So, I'm going to comment out the implicit wait so that we're not adding that to the project. I'll leave this commented just so that you have this as an example, but let's move on to the explicit waits.
//driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
Using our example application, we're going to write a new test.
This one is going to click on this Dynamic Loading link and we have two examples here.
Our test is going to click on the first example, “Element on a page that is hidden”.
Once we get here, we’ll click this “Start” button.
Notice we have a this loading icon.
Once it's finished loading, then it presents this “Hello World!”
So, we need to create a test that can account for the loading and then assert that the “Hello World!” label is correct.
I've added a new clickDynamicLoading
method to our HomePage
class, which will take us to the new DynamicLoadingPage
.
public DynamicLoadingPage clickDynamicLoading(){
clickLink("Dynamic Loading");
return new DynamicLoadingPage(driver);
}
Now for the DynamicLoadingPage
:
package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class DynamicLoadingPage {
private WebDriver driver;
private String linkXpath_Format = ".//a[contains(text(), '%s')]";
private By link_Example1 = By.xpath(String.format(linkXpath_Format, "Example 1"));
public DynamicLoadingPage(WebDriver driver){
this.driver = driver;
}
public DynamicLoadingExample1Page clickExample1(){
driver.findElement(link_Example1).click();
return new DynamicLoadingExample1Page(driver);
}
}
Remember there were two example links on this web page.
I created a By
for the first example.
Both of those examples had the same formula. There was no id on it. So, I just got it from the text, and I didn't want to put the entire text there since it was long and it could change.
So, I've created our Xpath to say, “let's see if it contains a specific amount of text”.
And then for link link_Example1
, I made that "Example 1".
If you wanted to add a link to “Example 2” we could easily do that by following this same formula.
Also, in this class we have our constructor as well as a clickExample1
method.
This will click on the first example link and then will take us to the DynamicLoadingExample1Page
.
In DynamicLoadingExample1Page
we have our driver and our constructor.
package pages;
import org.openqa.selenium.WebDriver;
public class DynamicLoadingExample1Page {
private WebDriver driver;
public DynamicLoadingExample1Page(WebDriver driver){
this.driver = driver;
}
}
Let's go ahead and add the elements that we need for our test.
The first thing we want to do is click on the “Start” button.
Let's go ahead and get a locator for this.
There's no identifiers on the button. However, the parent <div>
has an id. We should be able to do id of “start” and find us the button.
Let's add this to our framework.
private By startButton = By.cssSelector("#start button");
And we'll create a method to click that button.
public void clickStart(){
driver.findElement(startButton).click();
}
Let's see what happens when we click start.
It doesn't take us to a new page, so we don't have to worry about returning a new page.
However, it did show this loading icon. And when we hover over that we see a preview of it. So, we know that's the right one.
And fortunately, it does have an id.
Let's add this to our framework as well.
private By loadingIndicator = By.id("loading");
And then finally we want to be able to get the text “Hello World!”.
Let's get the locator for this.
And this has an id of “finish”.
private By loadedText = By.id("finish");
So, we know after we click the “Start” button that the loading indicator will be there, and we'll have to wait for it to finish.
How do we present this in this class?
A good way to do this is in the clickStart
method itself.
So, after it clicks, it needs to wait until the application is ready before giving control back to the test. So, we don't want our tests to have to think about calling into a method to wait for it.
When it clicks Start, it wants to know control won't be given back to me until it's done.
So inside of here, after we do the click, let's go ahead and write functionality for the explicit wait.
WebDriver has a class that's called WebDriverWait, and this is in the support UI package.
The WebDriverWait
constructor takes two arguments — the driver and also timeout in seconds.
Let's say maybe 5 seconds.
WebDriverWait wait = new WebDriverWait(driver, 5);
The timeout in seconds, this will also do a polling just like the implicit wait.
However, the implicit wait was for the entire app, whereas this is only for this specific method. So, we're saying poll the application up until 5 seconds.
Now this statement in and of itself won't do anything — it's just a setup statement to create this wait
object.
We then have to say wait.until
and then we give it some expected condition.
There's this ExpectedConditions class that's also in support.ui package.
The ExpectedConditions
class contains all sorts of methods that allow you to wait for some condition to be met before proceeding.
This class is really wonderful and provides lots and lots of different conditions to choose from.
So, we have to ask ourselves, what is it we're waiting for?
Let's look at the app again.
When we click the “Start” button, we see that the loading icon is here and before we move on, we need to wait until it's gone.
Let's see if we can find a condition to say, “wait until that loading element is no longer there”.
I see there is this method called invisibilityOf
, so we can use this one.
invisibilityOf
takes a WebElement
, so we give it our By
object that we created, the “loadingIndicator”.
public void clickStart(){
driver.findElement(startButton).click();
WebDriverWait wait = new WebDriverWait(driver, 5);
wait.until(ExpectedConditions.invisibilityOf(
driver.findElement(loadingIndicator)));
}
This method will click the start button. Then it will wait up until 5 seconds for the loading indicator to be invisible.
If after 5 seconds, it is not invisible, then this will throw an exception. Again, the timeout is the maximum amount of time that it will wait.
However, if it finds that the loading indicator is invisible after 1 second, then it will continue on after 1 second.
So, this will only wait as long as you need it to up until the timeout provided.
Okay.
The last thing we need to do for this page object class is to provide a method that returns the loaded text.
package pages;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class DynamicLoadingExample1Page {
private WebDriver driver;
private By startButton = By.cssSelector("#start button");
private By loadingIndicator = By.id("loading");
private By loadedText = By.id("finish");
public DynamicLoadingExample1Page(WebDriver driver){
this.driver = driver;
}
public void clickStart(){
driver.findElement(startButton).click();
WebDriverWait wait = new WebDriverWait(driver, 5);
wait.until(ExpectedConditions.invisibilityOf(
driver.findElement(loadingIndicator)));
}
public String getLoadedText(){
return driver.findElement(loadedText).getText();
}
}
Alrighty, let's create our test.
We create an object for the loading page.
public class WaitTests extends BaseTests {
@Test
public void testWaitUntilHidden(){
var loadingPage = homePage.clickDynamicLoading().clickExample1();
}
We will now click the start button, and again that will start and wait of the loading to be complete.
loadingPage.clickStart();
And then we can assert, because we have the text now.
assertEquals(loadingPage.getLoadedText(), "Hello World!", "Loaded text incorrect");
package wait;
import base.BaseTests;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
public class WaitTests extends BaseTests {
@Test
public void testWaitUntilHidden(){
var loadingPage = homePage.clickDynamicLoading().clickExample1();
loadingPage.clickStart();
assertEquals(loadingPage.getLoadedText(), "Hello World!", "Loaded text incorrect");
}
}
Let's run it.
Now we see that it's waiting.
Wonderful. The test passed.
Now let's just make sure that it's actually waiting as it should.
We're going to go to the clickStart
method and we're going to comment out the wait part and let's make sure our test will fail.
Great. It didn't wait and the error says that it expected “Hello World!”, but the actual was nothing.
So, we know that this is waiting appropriately.
That's the explicit wait.
There's also the concept of Fluent Waits.
Fluent Waits give you a little bit more flexibility in how you create this WebDriver “wait” object.
In addition to indicating the timeout, you can also tell it how often it should poll. And then also you can specify any exceptions that you want it to ignore.
Let's look at this same example with a FluentWait
.
We'll create a new instance of FluentWait
and this takes a driver.
FluentWait wait = new FluentWait(driver)
As opposed to putting a semicolon, we're going to put a .
and now we can specify any of these methods.
You see we have:
ignoring
pollingEvery
withTimeout
ignoreAll
withMessage
Let's set the timeout first.
We can say .withTimeout
, we still want to do the 5 seconds.
We would say Duration.
because this takes a Duration
object and then we'll say .ofSeconds
.
And then specify how many seconds. In addition to seconds, there's also nano, milli, minutes, et cetera. So, you can choose the unit that you'd like.
We're going to stick with the same example, so let's say 5 seconds and then we can do another .
to keep going.
FluentWait wait = new FluentWait(driver)
.withTimeout(Duration.ofSeconds(5))
Then we can say, “okay, I want you to poll every maybe 1 second”. So, we can say duration of 1 second.
FluentWait wait = new FluentWait(driver)
.withTimeout(Duration.ofSeconds(5))
.pollingEvery(Duration.ofSeconds(1))
And then another .
to say “ignore the NoSuchElementException
”.
So, let's say that the element was not present in the DOM. If we tried to find it before it was there, then it would throw this NoSuchElementException
.
So, to ignore that exception, we say:
FluentWait wait = new FluentWait(driver)
.withTimeout(Duration.ofSeconds(5))
.pollingEvery(Duration.ofSeconds(1))
.ignoring(NoSuchElementException.class);
You can specify any other exceptions that you wanted in there.
Okay, so again, this is equivalent to the creation of the object for the explicit wait.
In order for it to actually do something, we need to do a wait.until
and then we'd go ahead and give it that same expected condition.
And now we have a fluent wait.
FluentWait wait = new FluentWait(driver)
.withTimeout(Duration.ofSeconds(5))
.pollingEvery(Duration.ofSeconds(1))
.ignoring(NoSuchElementException.class);
wait.until(ExpectedConditions.invisibilityOf(
driver.findElement(loadingIndicator)));
So, this gives you a little bit more control. In addition to just setting the timeout, you can also say how often it should check for this and any exceptions to ignore.
Okay, let's run this again just to make sure the fluent one works for this scenario.
Great.
If we do the driver.Manage.timeouts
again, let's see what else is here.
There's also the pageLoadTimeout
.
The pageLoadTimeout
allows you to set the amount of time to wait for a page load to complete before it throws an error.
This is also something that you can add to your script at the project level to say, "I would like to wait a certain amount of time for my pages to load."
Then there's also this setScriptTimeout
.
This will allow you to set the amount of time to wait for asynchronous scripts to finish executing.
Lots of applications are written in JavaScript and there may be some asynchronous actions happening in the background. So, you can set some timeouts using this.
For your optional exercise, click on the Dynamic Loading page from the application home page
And this time click “Example 2” where the element is rendered after the fact
Click on the “Start” button
This looks like a similar scenario to the one we've done. However, this text is not in the DOM until the loading indicator has disappeared.
So, figure out what's the best strategy to use for this scenario.
But the catch is I want you to wait until this “Hello World!” is available. So, I don't want you to use the waiting strategy on the loading indicator, but on this text itself.
Good luck!
Solution
Programming can be done many different ways, but here’s my solution: DynamicLoadingPage, DynamicLoadingExample2Page, WaitTests.