It's worth noting that your current code is performing a case-insensitive search. If you have the capability to conduct a case-sensitive search, consider using the following method to locate your element:
browser.findElement(By.linkText(item_to_search)).then(...);
In my experience developing numerous application tests with Selenium, I've consistently been able to search by link text without requiring case sensitivity. I highly recommend organizing your code in a way that enables this functionality.
If conducting a case-sensitive search is not an option, you may need to scan through each element to identify the desired one. While it is possible to create an XPath expression that matches text regardless of case, I tend to avoid using XPath for matching CSS classes like in your scenario. Consequently, scanning through elements as you were previously doing might be preferable. Note that when comparing text values against HTML content, it is advisable to use getText()
rather than getInnerHtml()
, as testing against HTML can be fragile: there could be additional elements within the HTML that do not affect the actual text of the link. For example,
<a><b>This</b> is a test</a>
would display "This is a test" but checking the inner HTML of the <a> tag would yield
<b>This</b> is a test
, which is not ideal for matching purposes.
Below is an implementation showcasing the By.linkText
method mentioned earlier and including an illustrative example:
var webdriver = require('selenium-webdriver');
var By = webdriver.By;
var chrome = require('selenium-webdriver/chrome');
var browser = new chrome.Driver();
browser.get("http://www.example.com");
// Modifying the example.com page to add test data.
browser.executeScript(function () {
document.body.innerHTML = '\
<ul class="list_of_items">\
<li><a>One</a></li>\
<li><a> Two </a></li>\
<li><a>Three</a></li>\
</ul>';
});
// Example of utilizing By.linkText for a case-sensitive search.
browser.findElement(By.linkText("Two")).getOuterHtml().then(function (html) {
console.log("case-sensitive: " + html);
});
var item_to_search = "TwO"; // Intentionally mixed case for illustration.
browser.findElements(By.css(".list_of_items a")).then(function (els){
if (els.length === 0)
throw new Error("abort!");
var item_to_search_normalized = item_to_search.toLowerCase();
function check(i) {
if (i >= els.length)
throw new Error("element not found!");
var el = els[i];
return el.getText().then(function (text) {
if (text.trim().toLowerCase() === item_to_search_normalized)
return el;
return check(i + 1);
});
}
return check(0);
}).then(function (el) {
el.getOuterHtml().then(function (html) {
console.log("case-insensitive: " + html);
});
});
browser.quit();
Additional tips:
Your initial code checked if .list_of_items
existed before proceeding. Instead of this, my approach assumes the page is correctly structured, reducing unnecessary operations. If troubleshooting why no elements are being retrieved, modify the throw new Error("abort!")
statement for diagnosis purposes.
Your original code halted the search upon finding a match, which I preserved. Using webdriver.promise.filter
scans every element even after locating a target, unlike my recursive method which terminates early upon success. This reduces round-trip interactions between the script and the browser, especially significant for remote server setups where each test adds latency. The recursion mirrors the behavior of webdriver.promise.filter
while optimizing efficiency.
In my code snippet, I utilize executeScript
to minimize exchanges between the Selenium script and the browser:
browser.executeScript(function () {
var item_to_search = arguments[0].toLowerCase();
var els = document.querySelectorAll(".list_of_items a");
for (var i = 0; i < els.length; ++i) {
var el = els[i];
if (el.textContent.trim().toLowerCase() == item_to_search)
return el;
}
return null;
}, item_to_search).then(function (el) {
if (!el)
throw new Error("can't find the element!");
el.getOuterHtml().then(function (html) {
console.log("case-insensitive, using a script: " + html);
});
});
The script passed to executeScript
runs within the browser context, necessitating the use of arguments
for parameters and .then
for result retrieval. Any console.log
calls within executeScript
output to the browser console.