Table of Contents
Enroll in Selenium Training

Page object model is a wonderful design pattern to abstract out Web Page elements and its actions from actual tests. This way tests do not have to worry about the creation/finding of WebElements. We use @FindBy and @FindAll annotations to mark a WebElement in the Page Object. @CacheLookup is a very important and overlooked annotation that can help us make our tests run faster.

@CacheLookup, as the name suggests helps us control when to cache a WebElement and when not to. This annotation, when applied over a WebElement, instructs Selenium to keep a cache of the WebElement instead of searching for the WebElement every time from the WebPage. This helps us save a lot of time.

In this tutorial, we will discuss about the benefits of @CacheLookup and will try to quantify the performance gains by using this annotation. However,this tutorial will not discuss the basics of @CacheLookup annotation.

Please go through the tutorial covering the basics of @CacheLookup here

Quick review of when Element lookup happens in Page Objects

Let us first understand when Selenium makes a FindElement call in PageObject model. Whenever you use a WebElement from your Page Object to perform some action a FindElement is triggered to lookup for the latest version of WebElement from the WebPage. This lookup is basically a FindElement REST Request to Browser's Web driver. This lookup is one of the most time-consuming section of your code.

Let us write a small test using the Page Object to see this interaction. We will log all the browser web driver interaction to validate this.

Below is the Test page URL

http://toolsqa.com/automation-practice-form/

In this test page, we will only create First and Last name text boxes. Here is the code

public class PracticeFormPageObject {

	@FindBy(how = How.NAME, using = "firstname")
	public WebElement firsName;

	@FindBy(how = How.NAME, using = "lastname")
	public WebElement lastName;

}

In the test we will use this Page object. The test does only two things

  1. Adds some text to First and Last name text boxes
  2. Reads the text from First and Last name text boxes

Here is the test code

@Test
public void TestFirstAndLastNameFields()
{
	// In order to understand how action on WebElements using PageObjects work,
	// we will save all the logs of chrome driver. Below statement helps us
	// save all the logs in a file called TestLog.txt
	System.setProperty("webdriver.chrome.logfile", "TestLog.txt");

	ChromeDriver driver = new ChromeDriver();
	driver.get("https://toolsqa.com/automation-practice-form/");

	// Initialize the Page object
	PracticeFormPageObject pageObject = PageFactory.initElements(driver, PracticeFormPageObject.class);

	// Write some values to First and Last Name
	pageObject.firsName.sendKeys("Virender"); // A FindBy call is triggered to fetch First Name
	pageObject.lastName.sendKeys("Singh"); // A FindBy call is triggered to fetch Last Name

	// Read values from the Text box.
	pageObject.firsName.getText(); // A FindBy call is triggered to fetch First Name
	pageObject.lastName.getText(); // A FindBy call is triggered to fetch Last Name

	driver.close();
	driver.quit();
}

In this test, there will four times when FindBy call will be triggered. This is marked in the code comments. Every statement like this "pageObject.firstName.getText()" are actually two calls

  1. FindBy to find the element
  2. getText to get the text

Both these calls are REST calls to the browser's WebDriver. If you run the above test you should get a log file named: TestLog.txt in the project's root directory. Let us open the text file and see the contents of it, for reference I am adding a trimmed version of the logs here

[12.654][INFO]: COMMAND FindElement {
   "using": "name",
   "value": "firstname"
}
[12.654][INFO]: Waiting for pending navigations...
[12.717][INFO]: Done waiting for pending navigations. Status: ok
[12.851][INFO]: Waiting for pending navigations...
[12.854][INFO]: Done waiting for pending navigations. Status: ok
[12.854][INFO]: RESPONSE FindElement {
   "ELEMENT": "0.8984444413515806-1"
}

[12.860][INFO]: COMMAND TypeElement {
   "id": "0.8984444413515806-1",
   "value": [ "Virender" ]
}
[12.860][INFO]: Waiting for pending navigations...
[12.861][INFO]: Done waiting for pending navigations. Status: ok
[13.045][INFO]: Waiting for pending navigations...
[13.050][INFO]: Done waiting for pending navigations. Status: ok
[13.050][INFO]: RESPONSE TypeElement

[13.053][INFO]: COMMAND FindElement {
   "using": "name",
   "value": "lastname"
}
[13.053][INFO]: Waiting for pending navigations...
[13.054][INFO]: Done waiting for pending navigations. Status: ok
[13.074][INFO]: Waiting for pending navigations...
[13.082][INFO]: Done waiting for pending navigations. Status: ok
[13.082][INFO]: RESPONSE FindElement {
   "ELEMENT": "0.8984444413515806-2"
}
[13.086][INFO]: COMMAND TypeElement {
   "id": "0.8984444413515806-2",
   "value": [ "Singh" ]
}

In the logs, we can see that there are pairs of FindElement and TypeElement calls for every statement write statement. Further investigation into the log file will show you that for every getText call you will have a FindElement and GetElementText calls.

This proves our case for the Page Object model that, for every action that we perform on a WebElement Selenium makes an additional lookup call. This call can turn out to be costly, as we will see in the below section, and can be avoided in most of the cases.

@CacheLookup and Performance analysis

Let us now see how we can avoid the additional lookup call and make our tests faster. The smart people from the Selenium WebDriver team knew about this problem and have a wonderful solution to it. They have provided us with @CacheLookup annotation. If a WebElement is decorated with this annotation, Selenium will not try to look up for the Web element on the Webpage, it will just return the cached version of the element. The cached version is created in the very first look up to the Web page, after the first lookup, all other lookup requests will be full filled by the cached element.

Let us perform a small test to see the time performance difference that we get for 1000 successive calls to a Cached and a Non-cached web element. This test will be performed on a single Element, First Name text box. The modified page object will look like this

public class PracticeFormModifiedPageObject {

	@FindBy(how = How.NAME, using = "firstname")
	public WebElement firsName;

	@FindBy(how = How.NAME, using = "firstname")
	@CacheLookup
	public WebElement firsNameCached;

}

We will modify the Test code to measure the time taken by a cached and a non-cached web element to perform getText operation 1000 times. Here is the test code

public static void main(String[] args)
{

	// In order to understand how action on WebElements using PageObjects work,
	// we will save all the logs of chrome driver. Below statement helps us
	// save all the logs in a file called TestLog.txt
	System.setProperty("webdriver.chrome.logfile", "TestLog.txt");

	ChromeDriver driver = new ChromeDriver();
	driver.get("https://toolsqa.com/automation-practice-form/");

	PracticeFormModifiedPageObject pageObject = PageFactory.initElements(driver, PracticeFormModifiedPageObject.class);
	// set some text to fetch it later
	pageObject.firstName.sendKeys("Virender");

	// We will first try to get Text from the WebElement version which is not cached.
	// We will measure the time to perform 1000 getText operations
	long withoutCacheStartTime = System.currentTimeMillis();
	for(int i = 0; i < 1000; i ++)
	{
		pageObject.firstName.getText();
	}
	long withoutCacheEndTime = System.currentTimeMillis();
	System.out.println("Time take in seconds Without cache " + ((withoutCacheEndTime - withoutCacheStartTime)/ 1000));

	// Let us now repeat the same process on the cached element and see
	// the amount of time it takes to perform the same operation 1000 times
	long withCacheStartTime = System.currentTimeMillis();
	for(int i = 0; i < 1000; i ++)
	{
		pageObject.firsNameCached.getText();
	}
	long withCacheEndTime = System.currentTimeMillis();
	System.out.println("Time take in seconds With cache " + ((withCacheEndTime - withCacheStartTime)/ 1000));

	driver.close();
	driver.quit();

}

Let us now run the test and see the output.

@CacheLookup in PageObjectModel

In the output, we can clearly see that the Cached version of WebElement takes 50 percent less time to perform the same operation compared to the Non Cached version. You could see a small variation in time mentioned above, this variation should be in effect of plus/minus 2 %.

When to use and when not to use @Cachelookup annotation

From the test above we can clearly see that using the Cached version of WebElement is beneficial, but is not true for every element. Let us try to understand these two important points.

Stale Element and Stale Element Exception

Even though it is tempting to use @CacheLookup annotation for every element, it is not suitable for elements that are dynamic in nature. By Dynamic elements, we mean the elements which refresh themselves quite often. For example, a Timer text which continuously changes the value every second. Another example could be a Stock price ticker which changes every few seconds. These elements are not a good candidates for @CacheLookup annotation.

The reason is quite simple, due to the fact that these elements change frequently on the web page, they are not good candidates for caching. Because if we cache one version of an element and it changes a few seconds later then we will get a Stale element exception.

Static elements

@CacheLookup is very useful for the elements that do not change on the web page once loaded. These types of elements constitute a majority of elements on the web page. So for those elements, as they will not change during test execution, we should use the @Cachelookup annotation to improve the test speed.

I hope this tutorial was helpful to you. Do drop me a feed back for any comments or issues with the content of this page. Before we leave, there is a small exercise for you.

  • Analyze the content of the log file to verify if cached elements are making a call to browser's webdriver for FindElement
iFrames in Selenium WebDriver
iFrames in Selenium WebDriver
Previous Article
Actions Class in Selenium
Actions Class in Selenium
Next Article
Virender Singh
I am Virender Singh, I have around 14 years of experience in the Technology domain.
Reviewers
Lakshay Sharma's Photo
Lakshay Sharma

Similar Articles

Feedback