Troubled Times with Selenium Webdriver

20 July 2014 in programming

Data Channel

Testing is one of those subjects I underestimated or taken rather less seriously in the university. Later in my first job, after about 2 years working there and observing which things usually went wrong in a normal development cycle of our team, I realized that some problems probably were solvable with automated testing. Or at least some time could be saved, when we wouldn’t do it manually all the time. Now, in my current job at Oximity, finally the time has come that I have to start actually doing it.

My testing system at work is comrpised of Selenium WebDriver (JavaScript version), Mocha with Chai and Coffeescript. What I’m doing with it is frontend end to end testing of our website. So basically I’m simulating a robot sitting in front of a browser, clicking as fast as possible on UI elements. When something breaks while he is doing it he either explodes in a burst of error messages or when I’m lucky, he tells me what went wrong.

This will not be a complete tutorial of Selenium Webdriver or Mocha or Chai, just a collection of things (resources, tutorials, bugs, techniques) that I want to remember and that will help me if I need to install and run it all over again. Maybe it will help also someone else out there.

#System and setup

My system is a Mac OS X Mavericks (10.9) so most of the setup will be Mac specific, some of it can be applied for Linux (the only difference actually, I guess, will be with the file paths and installation folders).

So the things I already assume to be installed:

What you need to install:

Go to your favourite terminal and install them:

npm install selenium-webdriver
npm install -g coffee-script
npm install -g mocha
npm install chai

Ok, before I go into details with some of the technologies lets look how the setup our first tests and make sure everything runs smoothly. Let’s make a file test.coffee.

webdriver = require("selenium-webdriver")
should = require("chai").should()

# the website we want to test
host = "http://watch-next.herokuapp.com"

# chrome (Mac specific for Linux it's something like /bin/google-chrome)
chromePath="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"

describe "Test Suite", ->
  # executed once before the first test
  before (done) ->
    this.timeout = 150000

    # initialize browser
    capabilities = webdriver.Capabilities.chrome()
    capabilities.set "chromeOptions", {
      "binary": chromePath
    }

    # start browser
    global.driver = new webdriver.Builder()
    .withCapabilities(capabilities)
    .build()
    done()

  # executed after last test
  after (done)->
  	# closes the browser
    driver.quit().then done

  describe 'Title test', ->
    before (done) ->
      done()
    after (done) ->
      done()
    @timeout 10000
    it 'should display the desired page title', (done)->

      driver.get host
      driver.wait () ->
        driver.getTitle().then (title)->
          title.should.have.string "WatchNext"
          done()
      , 5000

Now we need to run it with Mocha.

mocha --compilers coffee:coffee-script/register test.coffee

If you’ve written it with JavaScript a simple mocha test.coffee will suffice. After you’ve run it you should see something like:

  ․

  1 passing (3s)

It shows every test as a dot, and when they fail you see which one failed. I prefer another output, so I see which test is currently running and to not type it every time I simply make a shell script for it, called test.sh

#!/bin/bash

mocha --compilers coffee:coffee-script/register -R spec test.coffee

This gives me the more descriptive output:

  Test Suite
    Title test
      ✓ should display the desired page title (2769ms)


  1 passing (3s)

#Selenium

So Selenium is this thing that gives you the possibility to automate a browser. At first I had difficulties to find a good documentation or introduction, especially for the JavaScript version. So here at first a link collection of good introductions and documentations.

Tutorials and documentations

A good point to start from is this introduction on Google code.

Another challenge was to find a good documentation for it. There was this site, from Google Code, but I ran into several problems with it, there where numerous 503, 404 errors with it etc, so I found also a mirror of the doc here, but it seems to be incomplete. The most helpful documentations are that of webdriver.Webdriver, webdriver.WebEelement and webdriver.By, which is a jquery-like element selector.

Mocha/Chai

Mocha is the test framework I’m using with Chai as the assertion module. Here is the documentation for the BDD style chainable assertion construct, that is expect or should. With it you can chain assertions so they sound like real sentences. For example:

people.should.be.ok()

Frequent use cases

Here are some use cases which you might need frequently, but which maybe not that trivial to accomplish with Selenium. Also the problems you can run into.

Execution of arbitrary JavaScript code inside a selenium test:

driver.executeScript('return document.location.pathname')

Executing JavaScript and getting a value back:

yourAsyncScript = """
  var callback = arguments[arguments.length - 1];
  setTimeout(function(){
    callback("yourReturnValue")
  }, 5000);
"""
# timeout needs to be set or else it will fail
driver.manage().timeouts().setScriptTimeout(5000, 1) 
promise = driver.executeAsyncScript(yourAsyncScript);
promise.then (returnValue)->
  returnValue.should.equal("yourReturnValue")

Waiting for a page to load:

# wait for page to be loaded
driver.wait((()-> driver.executeScript('return document.readyState')), 10000)
.then ()->
	# continue doing something

If you already know for which element you are waiting you can use:

# wait until element appears on page
driver.wait (-> driver.isElementPresent webdriver.By("#myElement")), timeout
# wait for element to become visible (omit if not needed)
driver.findElement(webdriver.By("#myElement"))
.then (element) ->
  driver.wait -> element.isDisplayed()
  driver.findElement(webdriver.By("#myElement"))
.then (element) ->
	# do your thang

Checking if element has a class:

driver.findElements(By.css('#myElement'))
.then (elements) ->
  elements[field].getAttribute('class')
  .then (classes_string)->
    classes = classes_string.split ' '
    classes.should.include 'bestClass'

Error handling in Promises:

In Selenium WebDriver (JS) you can not deal with errors like you are used to with try/catch, because most of the actions you perform return promises and they are asynchronous. You wait for there completion with the then function and assign it a callback function there to get the result. The second argument of the then function is an error callback, which gets called when an exception happened performing the action.

element.click()
.then(resultCallback, errorCallback)

The errorCallback gets a reference to the error, and the resultCallback gets the result.

Dismiss alert when navigating away from a page:

So this is one that caused me some headache, if an alert pops up it can kill all your subsequent test-cases so when you have to check a page, where occasionally an alert pops up, you need to deal with it.

In this scenario, I want to change the page and wait for an element to appear, but an alert usually pops up and interferes.

handleAlert = (err) ->
  driver.switchTo().alert()
  .then (alert)->
    alert.accept()
  , (e)->
    console.log('No alert to handle')

getPageDismissAlert = (page) ->
  driver.get(page)
  .then(null, handleAlert)

  selector = By.css('.page-title')


  driver.wait((-> driver.isElementPresent selector), 5000)
  .then(null, handleAlert)
  driver.findElement(selector).then (element) ->
    driver.wait -> element.isDisplayed()
    .then null, handleAlert
    driver.findElements(selector).then null,handleAlert
  , handleAlert

  # we dealt with the alert... but haven't done what we wanted

Okay now we dealt with the alert. But we have not done we actually wanted. We could do it in the handleAlert method. Or we make us a function that creates the handleAlert functions for us:

alertHandleCreator = (retry) ->
  handle = (err)->
    driver.switchTo().alert()
    .then (alert)->
      alert.accept() # or .dismiss()
      retry()
    , (e)->
      console.log('No alert to handle')
  return handle

This creates us a function that cancels the alert and does the function we give it in the retry. Now we can do something like this:

getPageDismissAlert = (page) ->
  selector = By.css('.page-title')

  # function that we want retried when alert is raised
  try = (alertHandle)->
    driver.get(page)
    .then(null, alertHandle)
    driver.wait((-> driver.isElementPresent selector), 5000)
    .then(null, alertHandle)
    driver.findElement(selector).then (element) ->
      driver.wait -> element.isDisplayed()
      .then null, alertHandle
      driver.findElements(selector).then null, alertHandle
    , alertHandle

  handleAlert = alertHandleCreator try
  try(handleAlert)

Iterate through WebElements to get a value:

utils.waitVisible(driver,By.css('.info-tag'),10000)
.then (elements)->
  console.log 'Number of tags: '+elements.length
  tags = []
  tagPromiseList = elements.map (tagElement)->
    tagElement.getAttribute('textContent')
    .then (tag)->
      tags.push(tag)
  tags
.then (tags)->
  console.log 'tagList: ' + tags
  tags.should.include articleTag
  tags.should.include articleTagEdit
  done()

or if you want to check with just a substring… of a value for example

.then (elements)->
  tags = 
    list: ""
  tagPromiseList = elements.map (tagElement)->
    tagElement.getAttribute('textContent')
    .then (tag)->
      tags.list = tags.list+", "+tag
  tags
.then (tags)->
  tags.list.should.have.string articleTagEdit
  tags.list.should.have.string articleTag
  done()

Hover over element:

driver.findElement(By.css('.my-element-class'))
.then (tagElement)->
  new webdriver.ActionSequence(driver)
  .mouseMove(tagElement)
  .perform()

Handling StaleElementReferenceError and NoSuchElementError:

Reference: http://stackoverflow.com/questions/16166261

With the function waitVisible I want to find an element on the page and then wait for it to get visible. But sometimes the errors occur there, so I handle them like here:

waitVisible = (parent, selector, timeout) ->
  timeoutOver = false
  setTimeout ()->
    timeoutOver = true
  , timeout

  startTime = new Date()
  handleError = (error)->
    #handle NoSuchElementErrors and StaleElementReferenceError
    if error.name == "NoSuchElementError" || error.name=="StaleElementReferenceError"
      if timeoutOver 
        console.log "Still no such element :("
        throw error
      else #retry
        waitForIt(timeout-(new Date()-startTime))

  waitForIt = (timeout)->
    driver.wait (-> parent.isElementPresent selector), timeout
    parent.findElement(selector).then (element) ->
      driver.wait -> element.isDisplayed()
      parent.findElements(selector)
    , handleError

  waitForIt(timeout)

Actually that’s also not perfect. I have not found the best solution to handle StaleElementReferenceError and NoSuchElementError yet.

Waiting for Angular to finish first digest loop:

Reference: http://jiraaya.wordpress.com/2014/03/17/selenium-wait-for-digest-loop-to-complete/

Haven’t tested this out yet!

driver.executeAsyncScript "angular.element([DOM element]).injector().get('$timeout')(arguments[arguments.length - 1]);"
, ""

If you have something to add or correct, please write it in the comments. I’m still fairly new to this area and am eager to learn new things.


EDIT 27th of April, 2015

Here is a nice article I found on avoiding some confusing errors in your Selenium tests: Avoiding Random Failures in Selenium UI Tests

And two pages about page objects here and there, which can help you organize your page tests.

Also check out general useful tips on Elemental Selenium

I will probably also do a follow up article on some annoying things I dealt with.

This was posted on 20 July 2014 in programming tagged
Troubled Times with Selenium Webdriver
comments powered by Disqus