Case Study: Locator Chaining in Selenium WebDriver
Cleaner and more readable locators in Selenium tests with Locator Chaining
Locator Chaining, in test automation, is a way to find an element relative to another. I will illustrate how to use locator chaining with a real example in this article.
Table of Contents:
· One Selenium Test Failed
· Analyse
· The Problem — Multiple Elements
· The Solution — Locator Chaining
∘ 1. Find all matching elements
∘ 2. Filter for displayed elements
∘ 3. Get the first displayable notification badge
∘ 4. Use Locator Chaining to go back to the parent elements
· Zhimin’s notes
One Selenium Test Failed
This test (for my fathers’ WhenWise app) is to verify if a group training is available on Saturday, marked with the number in a red circle.
The test passed yesterday. After a code change, the test started to fail with the below comparison error.
Failure/Error: try_for(2) { expect(select_training_class_page.first_available_date_label_regardless_displayed).to include("SAT") }
RuntimeError:
Timeout after 2 seconds with error: expected "FRI\n30/12" to include "SAT"
Diff:
@@ -1,2 +1,3 @@
-SAT
+FRI
+30/12
It seemed to select the “FRI” tab instead of “SAT”. What went wrong here?
Analyse
It could be a bad code change. However, there was actually no visible change on the week selection page.
Visually, the notification badge (red circle) was still on Saturday, and there was nothing on Friday. Visually, exactly how it was before. But something has changed since yesterday, as we got a good run in the BuildWise CT server yesterday.
Zhimin: This shows the importance of daily automated end-to-end (via UI) regression testing, help narrow down regressin issues quickly. By the way, this issue won’t be found by unit testing or integration testing.
This means that we need to update the test script.
The Problem — Multiple Elements
Here is the method in the test script to select the label’s text, first_available_date_label
:
def first_available_date_label
driver.find_element(:xpath, "//div[@class='carousel-item carousel-fixed-item active']//li/div/div[@class='noti-count' and text()='1']/../..").text
end
It retrieves the text for the tab which contains a div
with a class noti-count
where the notification count is 1
.
I used TestWise’s attach-to-browser functionality to check how many div
s’ have a class noti-count
where the notification count is 1
.
elems = driver.find_elements(:xpath, "//div[@class='carousel-item carousel-fixed-item active']//li/div/div[@class='noti-count' and text()='1']")
puts elems.count
The result of this is 2!
Zhimin: Checking the number of elements that matching the lcoator, is a good web testing technique.
I extracted the HTML source and pasted it into a text editor to verify this.
Sure enough, Friday did have a notification badge too, but not displayed. It had a style of display:none
.
The Cause: a recent change removed one step of filtering, not-matching-criteria group lessons would still be kept on the page, but their notification counts were hidden.
Knowing why the test was failing, I moved on to fixing the first_available_date_label
method.
The Solution — Locator Chaining
Starting where the debugging left off — we now have two notification elements on Friday and Saturday. We just want to retrieve one that is visible.
Previously, I used driver.find_element(:xpath, “//div[@class=’carousel-item carousel-fixed-item active’]//li/div/div[@class=’noti-count’ and text()=’1']/../..”).text
to find the tab with the notification element. As you can see, it is quite a long Xpath expression, and adding more constraints to find the correct one might be complex.
I used a different approach:
Finding all elements with a notification badge (regardless of whether they are displayed or not)
Filter out non-displayable ones
Get the first displayed notification badge
Somehow get the tab text based on the notification badge
1. Find all matching elements
elems = driver.find_elements(:xpath, "//div[@class='carousel-item carousel-fixed-item active']//li/div/div[@class='noti-count' and text()='1']")
The returned is a list of Elements.
2. Filter for displayed elements
Then keep the elements that are displayed (without the style of display:none
).
To filter by a list in Ruby, we can use the select
function. Selenium has an elem.displayed?
method, which checks if the element is visible and returns a boolean.
# only keep elements that are visible
display_elems = elems.select{|x| x.displayed? }
At this point, we have the correct tabs in display_elems
.
3. Get the first displayable notification badge
the_notification_badge_element = display_elems.first
However, this contains the notification element, it is relative to the tab, but not the tab (i.e. the text here is 1
not FRI 30/12
).
4. Use Locator Chaining to go back to the parent elements
Keep reading with a 7-day free trial
Subscribe to AgileWay’s Test Automation & Continuous Testing Blog to keep reading this post and get 7 days of free access to the full post archives.