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
——————————————————————————————————————–
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:
- creating the name of the file that will store the screenshot
- taking the screenshot
- 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.
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(); } @Test 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(); } @Test 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.
-
- TestWatcher Rules; TestWatcher are base classes for Rules that take note of the testing action, without modifying it.
See example on how to use TestWatcher rule to generate a test report
- TestWatcher Rules; TestWatcher are base classes for Rules that take note of the testing action, without modifying it.
-
- 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.
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(); } @Test 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.