We are excited to kick off a series of posts on Selenium WebDriver-based test automation best practices and tips for test engineers who are developing or maintaining test automation for Salesforce applications. Lightning Experience continues to evolve with modern web standards, and that evolution can have unintended impact on Selenium-based tests. Our posts will provide measures to deal with the changes introduced by the new web technologies.
Not familiar with Selenium WebDriver? It is the de-facto standard open-source-tool for testing web applications like the Salesforce app. Learn more about Selenium WebDriver on its home page or this Wikipedia article. This 5 Minute Getting Started Guide leads you through the first steps to run tests automatically. In June 2013 the tool was also the topic of this Salesforce Developer blog post.
In this first post, we cover automation of the global search field in the Lightning UI. The global search field is one of the most popular features used by end users, and many automated UI test cases will need to implement steps that work with this UI element. We’ll start with a simple query and then move to more complicated settings — the use of categories for restricting the search and how to make the use of categories dynamic. By following the steps here, you will be able to automate these tasks in a fast yet robust way.
Task 1: Enter a search term in the global search field and start the search
Here is a very simple, straight-forward implementation, assuming you have to test the UI in one language only and that would be English:
01 WebElement inputBox = wd.findElement(By.xpath("//input[@title='Search Salesforce']")); 02 inputBox.click(); 03 inputBox.clear(); 04 inputBox.sendKeys("CS88 Corp"); 05 inputBox.click(); 06 wd.findElement(By.xpath("//span[@title='CS88 Corp']")); 07 inputBox.sendKeys(Keys.RETURN); 08 wd.findElement(By.xpath("//a[@title='CS88 Corp']"));
- Line 1: If you are covering only a single UI user language, then searching for an element by using a UI text is the most simple differentiator. If you need to cover multiple languages, you could perhaps get the correct text from a dictionary. See the line-by-line description for Task 2 below for what the approach could look like.
- Line 2: This click sets the cursor in the search box.
- Line 3: Remove any previously entered text.
- Line 4: Insert the search term of your choice.
- Line 5: This click kicks off the search.
- Line 6: Using findElement() is an easy way to assert that an element is present. If not, Selenium will throw a proper exception and your test will fail in a way you can easily triage the problem. Here we assert that the list of most recently used records is listing the record we are looking for.
- Line 7: Sending a Return keystroke will finally kick off the search.
- Line 8: Verify that the search returned the expected record.
You may have noticed that this code works without a single Thread.sleep() call! Why? Because WebDriver does a lot of things under-the-hood to ensure stable test execution. We will have more on this in a future post.
Is this the only way to implement testing the global search field? No, it’s not. However, it is an approach which is both simple and robust.
Task 2: Select a category for filtering before entering a search term in the global search field
For clarification purposes, this picture shows where you can set a category for filtering the search result:
This code example builds on the above example:
01 WebElement inputBox = wd.findElement(By.xpath("//input[@title='" + getSearchHint() + "']")); 02 WebElement categoryHome = wd.findElement(By.xpath("//input[@id='input-5']")); 03 categoryHome.click(); 04 wd.findElement(By.xpath("//lightning-base-combobox-item[@id='input-5-1-5']")).click(); 05 inputBox = wd.findElement(By.xpath("//input[@title='Search " + getSearchHintForAccounts() + "']")); 06 inputBox.click(); 07 inputBox.clear(); 08 inputBox.sendKeys("CS88 Corp"); 09 inputBox.click(); 10 wd.findElement(By.xpath("//span[@title='CS88 Corp']")); 11 inputBox.sendKeys(Keys.RETURN); 12 wd.findElement(By.xpath("//a[@title='CS88 Corp']"));
Line-by-line unless covered in Task 1:
- Line 1: This assumes getSearchHint() gets you the right text from a dictionary which is based on UI user language setting. I leave the implementation of getSearchHint() up to you.
- Line 2: This is the input field directly on the left side of the global search box. The use of By.xpath instead of By.id is intentional and is because of Lighting Web Components (LWC). We will have more on this in a later post.
- Line 3: A click in that input field expands the list of categories the user can choose from.
- Line 4: Finding any entry in the list is actually quite tricky, because a right-mouse click on it to inspect in your browser’s developer console makes the list vanish without updating the console. However, once you have expanded the list and are inspecting the input field in the console, you will find an attribute of type “aria-activedescendant”. This short video shows how.
- Line 5: Now that a filter is established, the selector for the input box needs an update. If your user language setting is English, the title attribute needs to have the value “Search Accounts”. Similar to line 1, we call a dictionary to get right text based on the UI user language setting.
- Lines 6-12: See comments for lines 2-8 for Task 1.
As mentioned above, this too is technically, still pretty simple, don’t you agree? The exception is of course figuring out how to get to the right element in the list of categories. In the next example we will explore how to use an arbitrary category name.
Task 3: Select a category by name for filtering
This code example is a variation of the above example:
01 WebElement inputBox = wd.findElement(By.xpath("//input[@title='" + getSearchHint() + "']")); 02 WebElement categoryHome = wd.findElement(By.xpath("//input[@id='input-5']")); 03 categoryHome.click(); 04 wd.findElement(By.xpath("//lightning-base-combobox-item[@data-value='FILTER:Account:Accounts']")).click(); 05 inputBox = wd.findElement(By.xpath("//input[@title='Search " + getSearchHintForAccounts() + "']")); 06 inputBox.click(); 07 inputBox.clear(); 08 inputBox.sendKeys("CS88 Corp"); 09 inputBox.click(); 10 wd.findElement(By.xpath("//span[@title='CS88 Corp']")); 11 inputBox.sendKeys(Keys.RETURN); 12 sleep(1000L); 13 wd.findElement(By.xpath("//a[@title='CS88 Corp']"));
Line-by-line unless covered in Task 2:
- Lines 1-3. See comments for same lines for Task 2
- Line 4. The interesting part here is the value of the attribute data-value. It is a concatenation of the object’s API name in its singular form (“Account”) and its plural form (“Accounts”) as defined in Setup. Hence this value will be the same even if using a different UI user language.
- Line 5-11. See comments for same lines for Task 2.
- Line 12. This is one of the rare examples where a Thread.sleep() call is actually required! We need to wait for the page to display the list of records found. If you leave the call out, the UI sometimes opens the record right away and the command on the next line will fail. We will have more on judicious uses of Thread.sleep() in a future post.
- Line 13. See comments for line 12 for Task 2.
The examples in this post show how to:
- Write a test in a simple, yet robust way.
- Get information on an UI element from the browser’s dev console, even if direct inspection is not possible.
You also had a glimpse into what it means to test engineers if UI elements are now based on Lightning Web Components (LWC). At time index 0:05 of the video above, the mouse hovers over a node called #shadow-root (user-agent). This indicates that the UI element has been ported over to LWC. We will have more on the changes that are introduced to the DOM by the LWC rollout that negatively impact automated UI tests in future posts.
What challenges are you facing as a test engineer? Do you have any topics you would like to suggest for a future post? Please let us know by leaving a comment below. We are interested in hearing from you.
About the author
Georg Neumann is intimately familiar with Selenium since version 1. He is part of the Salesforce Central QE team, which is using Selenium tests written by Salesforce customers to test their own Salesforce implementation to find regressions introduced by Salesforce in a new release before they affect the customers. This service is called Business Scenario Testing (BST) and open to any Salesforce customers meeting the requirements for participation. Follow him on LinkedIn.