DEV Community

Cover image for 9 things I wish I knew when learning to use Selenium Webdriver with Node.JS
ADS-BNE
ADS-BNE

Posted on • Edited on

9 things I wish I knew when learning to use Selenium Webdriver with Node.JS

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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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|
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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);
    }
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

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!'));
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
priteshusadadiya profile image
Pritesh Usadadiya

[[..Pingback..]]
This article was curated as a part of #115th Issue of Software Testing Notes Newsletter.
softwaretestingnotes.substack.com/...
Web: softwaretestingnotes.com