How To…take Screenshots in Selenium

Each month on the first Tuesday of the month, we will post a new blog post to take you through a step-by-step guide on how to address a particular aspect of Selenium Webdriver that you might need to address in your testing. This month we look at Selenium screenshots and how to go about it.

Find other posts in the series here:

How To…Select Elements In Selenium WebDriver Scripts

How To…Use Explicit Waits In Selenium WebDriver

How To…Manage Exceptions In Selenium WebDriver

How To…Execute Javascript In Selenium

How To…take Screenshots in Selenium

How To…Interact with sliders in Selenium

How to…Interact with Modal Windows

How to…Use Expected Conditions

How To..Find Elements

——————————————————————————————————————–

Sometimes, test automation scripts fail because of exceptions.

The exception can happen by

  • an element that could not be found
  • an element that has a different status from the expected one
  • a popup that does not load
  • an element that is not clickable

When the exception happens, the test automation script should collect as much information about the exception as possible.

We can use this information when looking for the exception’s cause.

Getting the screenshot of the page where the exception happened is useful.

This is what we look at in this article.

Selenium Screenshot

There are a few different ways of getting screenshots with Selenium WebDriver when exceptions happen.

Get the exception with a try/catch statement

There are 3 parts to taking a screenshot of the current page:

  1. creating the name of the file that will store the screenshot
  2. taking the screenshot
  3. saving it to a file

The easiest way of taking the screenshot is by wrapping the code that generates the exception with a try/catch statement.

Then, take the screenshot in the catch() section.

Info

For example:

public class TestClass2 {

By searchTextBoxId = By.id("globalQuery1");
By searchButtonLocator = By.xpath("//input[@class='search_button']");
String url = "http://www.vpl.ca";
String expectedResultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
WebDriver driver;

@Before
public void setUp() {
driver = new FirefoxDriver();
}

@After
public void tearDown() {
driver.quit();
}

<a class='bp-suggestions-mention' href='https://huddle.eurostarsoftwaretesting.com/members/test/' rel='nofollow'>@Test</a>
public void testHomePageSearch() throws Exception {

try{

driver.get(url);

//next statement generates an exception because the element locator is invalid
WebElement searchTextBox = driver.findElement(searchTextBoxId);

searchTextBox.sendKeys("java");
WebElement searchButton = driver.findElement(searchButtonLocator);
searchButton.click();
assertEquals(driver.getTitle(), expectedResultsPageTitle);

}
catch(Exception ex) {

Screenshot screenShot = new Screenshot(driver);
screenShot.capture("testScriptName");
fail("test script failed!);

}

}
}

In the catch() section, a screenshot is taken and the test script is failed using the fail() JUNIT method.

A Screenshot class is used for taking the screenshot:

public class Screenshot {

WebDriver driver;
String folderPath = "src/screenshots/";

public Screenshot(WebDriver driver) throws Exception {
this.driver = driver;
validateFolderExists();
cleanFolder();
}
private void validateFolderExists() throws Exception {
File screenShotFolder = new File(folderPath);
if (!screenShotFolder.exists())
throw new Exception("screenshot folder does not exist");
}

public void cleanFolder() throws Exception {
try{
File screenShotFolder = new File(folderPath);
for(File file: screenShotFolder.listFiles())
file.delete();
}
catch(Exception ex) {
throw new Exception("cannot clean folder;", ex);
}
}

public void capture(String fileName) throws Exception
{
try {
FileOutputStream screenShotFile = new FileOutputStream(folderPath + fileName + ".png");
screenShotFile.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
screenShotFile.close();
}
catch (Exception ex) {
throw new Exception("cannot create screenshot;", ex);
}
}
}

The class has a few methods for:

  • checking if the folder (where the screenshots should be saved) exists
  • removing the previous screenshot files from the folder
  • taking a screenshot of a page

There is a problem with this code.

It is always using the same name for the screenshot file.

Hardcoding a value in the code does not lead to clean code.

So we should remove it.

If we want to take screenshots in multiple test scripts, we need a better name for the screenshot file.

How about using the test script name for the screenshot file name?

This is possible using a JUNIT rule as follows:

public class TestClass2 {

........................
........................

@Rule
public TestName testName = new TestName();

@Before
public void setUp() {
driver = new FirefoxDriver();
}

@After
public void tearDown() {
driver.quit();
}

<a class='bp-suggestions-mention' href='https://huddle.eurostarsoftwaretesting.com/members/test/' rel='nofollow'>@Test</a>
public void testHomePageSearch() throws Exception {

try{

driver.get(url);

//the next line generates an exception
WebElement searchTextBox = driver.findElement(searchTextBoxId);

searchTextBox.sendKeys("java");
WebElement searchButton = driver.findElement(searchButtonLocator);
searchButton.click();
assertEquals(driver.getTitle(), expectedResultsPageTitle);

}
catch(Exception ex) {
Screenshot screenShot = new Screenshot(driver);
screenShot.capture(testName.getMethodName());
fail("error generated - screenshot taken");
}
}
}

The TestName JUNIT rule provides access to the test script name.

So we can use the test script name for the screenshot name.

The code that we have so far works.

But what if you want to take screenshots for exceptions that may happen in multiple classes and methods?

Since we should always aim at writing clean code, it is a bad idea to duplicate the “take screenshot” code by copying it in lots of other methods.

We need a way of taking the screenshots on exceptions automatically.

This can be done using mechanisms built in the unit testing framework (rules for JUNIT, listeners for Test NG).

Use JUNIT rules to take screenshots automatically

JUNIT rules allow very flexible addition or redefinition of the behavior of each test method in a test class.

Testers can reuse or extend one of the provided Rules below.

Or write their own.

Default JUNIT rules:

    • TemporaryFolder Rule; The TemporaryFolder Rule allows creation of files and folders that are deleted when the test method finishes (whether it passes or fails).

 

    • ExternalResource Rules; ExternalResource is a base class for Rules (like TemporaryFolder) that set up an external resource before a test (a file, socket, server, database connection, etc.), and guarantee to tear it down afterward.

 

    • ErrorCollector Rule; The ErrorCollector Rule allows execution of a test to continue after the first problem is found.

 

 

    • TestName Rule; The TestName Rule makes the current test name available inside test methods.

 

    • Timeout Rule; The Timeout Rule applies the same timeout to all test methods in a class.

 

    • ExpectedException Rules; The ExpectedException Rule allows in-test specification of expected exception types and messages.

 

We will write our own rule for taking the screenshot automatically on exception.

public class ScreenshotTestRule implements TestRule {

WebDriver driver;

public ScreenshotTestRule (WebDriver driver) {
this.driver = driver;
}

public Statement apply(final Statement statement, final Description description) {

return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
statement.evaluate();
}
catch (Throwable t) {
String methodName = getName(description.getMethodName());
Screenshot screenshot = new Screenshot(methodName);
screenshot.capture(methodName);
throw t;
}
}
};
}
}

The custom JUNIT rule class overrides the evaluate() method of TestRule interface.

In the evaluate() method, the current test script is evaluated.

If the test script generates an exception, the exception is caught using try/catch.

Then, in the catch() clause, we take first the screenshot of the page and then throw the exception.

Info

The test class changes are minimal:

public class TestClass4 {

By searchTextBoxId = By.id("globalQuery1");
By searchButtonLocator = By.xpath("//input[@class='search_button']");
String url = "http://www.vpl.ca";
String expectedResultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";

WebDriver driver;

@Rule
public TestName testName = new TestName();
@Rule
public ScreenshotTestRule screenshotTestRule = new ScreenshotTestRule(driver);
@Before
public void setUp() {
driver = new FirefoxDriver();
}

@After
public void tearDown() {
driver.quit();
}

<a class='bp-suggestions-mention' href='https://huddle.eurostarsoftwaretesting.com/members/test/' rel='nofollow'>@Test</a>
public void testHomePageSearch1() throws Exception
{

driver.get(url);

//this line generates an exception
WebElement searchTextBox = driver.findElement(searchTextBoxId);

searchTextBox.sendKeys("java");
WebElement searchButton = driver.findElement(searchButtonLocator);
searchButton.click();
assertEquals(driver.getTitle(), expectedResultsPageTitle);

}
}

The test script does not include code for taking the Selenium screenshot any longer.

It includes instead an object for the new rule:

@Rule
public ScreenshotTestRule screenshotTestRule = new ScreenshotTestRule(driver);

When the test script generates the exception, the rule takes the screenshot automatically.

 

See more articles in the How To Selenium Series

About the Author

Alex

Software Tester from Vancouver, Canada. Blogs on test automation topics for manual testers on http://test-able.blogspot.ca When not testing or creating test automation scripts for my clients, I teach manual testers programming with Java and test automation with Selenium WebDriver.
Find out more about @alexsiminiuc