A short while ago I was tasked with setting up automated testing on a website. I inherited some existing tests written in Java, but, being a web developer, I felt that the tests would be easier for me to write in JavaScript on Node.js.
This was for the most part true, but there were certain areas lacking in the official Selenium Webdriver documentation for JavaScript.
So, to save others the trouble of trying to convert Python or Java examples to JavaScript, here's some things that I learnt along the way. Note: I am using Cucumber.js as well.
I'm using the npm packages selenium-webdriver and Cucumber.js.
Turning off Geo Location
This is set an an option in your browser settings before you run build
against. For example, for FireFox:
let options = new firefox.Options();
options.setPreference('geo.enabled', false);
const driver = new Builder()
.forBrowser('firefox')
.setFirefoxOptions(options)
.build();
Dismissing alerts
The Selenium documentation gives the way to dismiss alerts in JavaScript as:
await alert.dismiss();
For the life of me, I could not get this to work. An alternative is to automatically dismiss alerts as a browser setting, like above:
let options = new firefox.Options();
options.setAlertBehavior('dismiss');
const driver = new Builder()
.forBrowser('firefox')
.setFirefoxOptions(options)
.build();
Getting clean text from an element
Retrieving text content is relatively easy with Selenium's .getText()
method. However some browsers like Safari also grab a bunch of junk like line breaks and empty spaces. So it's better to clean this up with regular JavaScript, as such:
var resultsCountText = await driver.findElement(By.className('search-results_count')).getText();
resultsCountText = resultsCountText.replace(/\n|\r|/g, "");
resultsCountText = resultsCountText.trim();
Getting text from a hidden element
Sometimes you might just want to get text content from an element like a modal or mega-menu that is just not visible on the page. That's where .getAttribute("innerText")
comes in handy.
let modalContent = await driver.findElement(By.className('modal-body')).getAttribute("innerText");
Remember this may need cleaning up, as above.
Repeating the same test for multiple pages
This can be achieved using Cucumber's Scenario Outline
. Say, if I wanted to ensure the footer is present on a give list of pages, my .feature
file might look like:
Scenario Outline: Run a check to see if the footer is present
Given I am on the '<page>' page
Then the footer section should be present
Examples:
| page |
| about-us |
| products |
| contact-us |
| store-locations|
The values in the above table are paths to the aforementioned pages. In my steps .js file I would have something like:
Given('I am on the {string} page', async function (string) {
this.driver = new Builder()
.forBrowser('firefox')
.build();
this.driver.wait(until.elementLocated(By.className('logo')));
await this.driver.get('https://www.your-website.com/' + string);
});
Then('the footer section should be present', async function() {
var footerContent = await webdriver.driver.findElement(By.css('footer')).getText();
assert.ok(footerContent.length>1);
});
Iterating through a series of elements
Say you have a group of .card
elements and simply want to test if each has a title. driver.findElements()
(note the s) returns a collection of elements which you can simply loop through. This works particularly well with Cucumber/Gherkin datatables.
Then('the prod card titles should be', async function (dataTable) {
var productCard = await this.driver.findElements(By.className('product-card'));
var expectations = dataTable.hashes();
for (let i = 0; i < expectations.length; i++) {
const productName = await productElements[i].findElement(By.className('product-card_title')).getText();
assert.equal(productName, expectations[i].productName);
}
});
Locating multiple elements that share the same class with Xpath
Xpath is a way of accessing specific pieces of content in an XML file. Selenium can make use of this way of locating data by converting the HTML page to an XML file. This is specifically handy when pulling data from HTML elements that all share the same class name.
Consider trying to obtain the price data from the following HTML:
<div class="prod-card">
<div class="prices">
<div class="price-wrap">
From:
<div class="price">$9.99</div>
</div>
<div class="price-wrap">
To:
<div class="price">$19.99</div>
</div>
</div>
</div>
Obviously the issue here is there are no unique identifiers between the 2 .price
elements.
However, we can use Selenium's Xpath to locate the first and second instance of .price
, as such:
let lowerPrice = await driver.findElement(By.xpath("(//div[@class='price'])[1]")).getText();
let higherPrice = await driver.findElement(By.xpath("(//div[@class='price'])[2]")).getText();
Using different kinds of Explicit Waits when Implicit Waits just won't work
Happy to be proved wrong, but the Implicit Waits as per the Selenium Documentation just never worked for me. At all.
Instead, I had to get creative using Explicit Waits (until.
).
For example, on a page whose content is altered with JavaScript (eg a Store Locator page that uses the Google Maps API) I had to wait until the text of an element changed, eg:
await driver.wait(until.stalenessOf(driver.findElement(By.id('location'))));
await driver.wait(until.elementTextContains(driver.findElement(By.id('location')), 'New content!'));
Note I had to pair this with until.stalenessOf()
. Instead of waiting a couple of sections for the JavaScript on the page to do its thing and alter the content on the DOM, I instead waited for a particular element's text content to change.
The comments in the files in the /lib
directory are very useful
For some reason, with the Selenium Webdriver npm package at least, there seem to be way more undocumented methods available. I found it actually incredibly useful to simply poke around the /lib
directory of the selenium-webdriver
package.
For example, the file node_modules/selenium-webdriver/lib/until.js
lists a whole host of very useful Explicit Wait methods:
module.exports = {
elementTextMatches,
elementTextContains,
elementTextIs,
elementIsNotSelected,
elementIsSelected,
elementIsDisabled,
ableToSwitchToFrame,
elementIsEnabled,
elementIsNotVisible,
elementIsVisible,
stalenessOf,
elementsLocated,
elementLocated,
urlMatches,
urlContains,
urlIs,
titleMatches,
titleContains,
alertIsPresent,
titleIs,
}
Extra: Checking for broken links
I've written another tutorial for how to check for broken links here.
Cover photo by Markus Spiske on Unsplash
Top comments (1)
[[..Pingback..]]
This article was curated as a part of #115th Issue of Software Testing Notes Newsletter.
softwaretestingnotes.substack.com/...
Web: softwaretestingnotes.com