Skip to main content

_ruby

Example 1

For this example we'll use a login example from the-internet.

First let's require our necessary libraries (e.g., selenium-webdriver to control the browser and rspec/expectations & RSpec::Matchers for our assertions) and wire up some simple setup, teardown, and run methods.

# filename: exception_handling.rb

require 'selenium-webdriver'
require 'rspec/expectations'
include RSpec::Matchers

def setup
@driver = Selenium::WebDriver.for :firefox
end

def wait_for(seconds)
Selenium::WebDriver::Wait.new(timeout: seconds).until { yield }
end

def teardown
@driver.quit
end

def run
setup
yield
teardown
end

To demonstrate the problem, let's write a basic test to exercise the login page. After logging in, we'll check to see that the login form is no longer displayed.

run do
@driver.get 'http://the-internet.herokuapp.com/login'
@driver.find_element(id: 'username').send_keys('tomsmith')
@driver.find_element(id: 'password').send_keys('SuperSecretPassword!')
@driver.find_element(id: 'login').submit
wait_for(10) { @driver.find_element(css: '.icon-2x').displayed? }
expect(@driver.find_element(id: 'login').displayed?).to eql false
end

When we run this (e.g., ruby exception_handling.rb from the command-line) our test will not pass. It will log in just fine, but it will error when performing the assertion -- returning the following exception:

Unable to locate element: {"method":"id","selector":"login"} (Selenium::WebDriver::Error::NoSuchElementError)

In the Python Selenium bindings they have a section of actions called expected conditions. One of them is designed specifically for this use case (called visibility_of_element_located). It checks to see if an element is displayed and returns false if it's not (rather than throwing an exception).

Unfortunately the Ruby Selenium bindings do not come with this functionality built-in, and there are no plans to add it. So let's go through how to do it ourselves. For that we'll need a basic understanding of exceptions and how to rescue them (here's a quick primer) and the name of the exception we want to rescue. You can find a full list of the Selenium exceptions for Ruby here. But we shouldn't need all of them since our previous test run told us the name of the exception.

Selenium::WebDriver::Error::NoSuchElementError is the one we want. Let's put it to use by wrapping the last display check in our test with a rescue block, making it return false if it's triggered.

run do
@driver.get 'http://the-internet.herokuapp.com/login'
@driver.find_element(id: 'username').send_keys('tomsmith')
@driver.find_element(id: 'password').send_keys('SuperSecretPassword!')
@driver.find_element(id: 'login').submit
begin
expect(@driver.find_element(id: 'login').displayed?).to eql false
rescue Selenium::WebDriver::Error::NoSuchElementError
false
rescue Selenium::WebDriver::Error::StaleElementReferenceError
false
end
end

Now if we run the test (e.g., ruby exception_handling.rb from the command-line) it will pass. But this is far from a clean implementation. Let's clean things up a bit.

def rescue_exceptions
begin
yield
rescue Selenium::WebDriver::Error::NoSuchElementError
false
rescue Selenium::WebDriver::Error::StaleElementReferenceError
false
end
end

def is_displayed?(locator = {})
rescue_exceptions { @driver.find_element(locator).displayed? }
end

run do
@driver.get 'http://the-internet.herokuapp.com/login'
@driver.find_element(id: 'username').send_keys('tomsmith')
@driver.find_element(id: 'password').send_keys('SuperSecretPassword!')
@driver.find_element(id: 'login').submit
expect(is_displayed?(id: 'login')).to eql false
end

By breaking apart our rescue block (into a rescue_exceptions method) and our displayed? check (into an is_displayed? method) we're left with a cleaner (and reusable) implementation in our test code. And if you wanted to add another Selenium exception to the mix, then you would simply add a new rescue statement along with the result you want in rescue_exceptions.

def rescue_exceptions
begin
yield
rescue Selenium::WebDriver::Error::NoSuchElementError
false
rescue Selenium::WebDriver::Error::StaleElementReferenceError
false
end
end

NOTE: NoSuchElementError and StaleElementReferenceError are the two most common errors you'll run into when using Selenium.

Example 2

If your test is still failing, it might not have enough time to find the login element. In this case, you will have to add a wait to your test.

# exception-with-wait.rb

def wait_for(seconds)
Selenium::WebDriver::Wait.new(timeout: seconds).until { yield }
end

Also add this line to your run code, just before the expect line:

# exception-with-wait.rb

wait_for(10) { @driver.find_element(css: '.icon-2x').displayed? }

The full script with wait added will be as follows:

# exception-with-wait.rb

# filename: exception_handling.rb

require 'selenium-webdriver'
require 'rspec/expectations'
include RSpec::Matchers

def setup
@driver = Selenium::WebDriver.for :firefox
end

def teardown
@driver.quit
end

def run
setup
yield
teardown
end

def rescue_exceptions
begin
yield
rescue Selenium::WebDriver::Error::NoSuchElementError
false
rescue Selenium::WebDriver::Error::StaleElementReferenceError
false
end
end

def is_displayed?(locator = {})
rescue_exceptions { @driver.find_element(locator).displayed? }
end

def wait_for(seconds)
Selenium::WebDriver::Wait.new(timeout: seconds).until { yield }
end


run do
@driver.get 'http://the-internet.herokuapp.com/login'
@driver.find_element(id: 'username').send_keys('tomsmith')
@driver.find_element(id: 'password').send_keys('SuperSecretPassword!')
@driver.find_element(id: 'login').submit
wait_for(10) { @driver.find_element(css: '.icon-2x').displayed? }
expect(is_displayed?(id: 'login')).to eql false
end

Expected Behavior

If you save the file and run it (e.g, ruby exception_handling.rb from the command-line) here is what will happen:

  • Open the browser
  • Visit the page
  • Log in
  • Check to see that the login form is NOT displayed
  • Catch the exception from Selenium and return false instead
  • Complete the assertion using the boolean response (e.g., false)
  • Close the browser

Summary

Hopefully this tip has helped you learn how to work effectively with exceptions in Selenium.

Happy Testing!