Taking Screenshots with Selenium

The Problem:

You have some automated Selenium tests that you run periodically. Maybe these tests run as part of a continuous deployment process. Occasionally these tests will fail and you’ll find yourself analyzing error messages or stack traces that may or may not be very helpful. Wouldn’t it be nice to have some other tool available to help you with your debugging?

A Solution:

This solution provides a way of capturing screenshots of your browser, but only when your test fails. This way you don’t have a mess of screenshots to go through when its time to see why your test failed.

We are going to continue adding functionality to the project we created in the Selenium Hello World! post. We will be adding a new class FailureTasks. Place the class before the last squiggly bracket in Google.cs.

public class FailureTasks
{

}

Failure tasks will have two global variables, a webdriver and a function.

private IWebDriver _webDriver;
private Action<IWebDriver> _function;

Next, lets add the mechanism which actually takes the screenshot. In addition to the OpenQA.Selenium library. We will also need to import the System.IO and System.Drawing.Imaging libraries.

using System.IO;
using System.Drawing.Imaging;
public void SaveScreenshot() {
    Screenshot ss = 1)ITakesScreenshot)_webDriver).GetScreenshot();

    ss.SaveAsFile(
        Path.Combine(
            Environment.CurrentDirectory,
            string.Format("{0} - {1}.png",
                "Hello World",
                "Google",
            ImageFormat.Png);
}

Now, lets build the mechanism that takes the screenshot, but only when something in the _function global variable fails. This is done easily with a try-catch.

public void TakeScreenShot() {
    try {
        _function(_webDriver);
    }
    catch {
        SaveScreenshot();

        throw;
    }
}

The last thing we need for the FailureTasks class is a constructor.

public FailureTasks(IWebDriver webDriver, Action<IWebDriver> function)
{
    _webDriver = webDriver;
    _function = function;
}

We now have everything we need to take our screenshot. We just need to wrap the code in SearchForHelloWorld in a FailureTask object by using a lambda expression and tack on the TakeScreenShot method.

public void SearchForHelloWorld()
{
    IWebDriver webDriver = new OpenQA.Selenium.Firefox.FirefoxDriver();
    new FailureTasks(webDriver, f =>
    {
        webDriver.Navigate().GoToUrl("http://www.google.com/");
        IWebElement textBox = webDriver.FindElement(By.Id("gbqfq"));
        textBox.SendKeys("Hello World!" + Keys.Enter);
        new WebDriverWait(webDriver, TimeSpan.FromSeconds(5)).Until<bool>(dr =>
        {
            try
            {
                IWebElement helpButton = dr.FindElement(By.XPath("//span[@id='fsl']/a"));
                Assert.AreEqual("Help", helpButton.Text);
                return true;
            }
            catch
            {
                return false;
            }
        });
    }).TakeScreenShot();
    webDriver.Dispose();
}

The test will now execute as it did before, but now, if the test fails, we will be able to find a screenshot of the browser at the time of failure in the HelloWorld project’s bin/Debug folder. You can make the test fail by removing its WebDriverWait.

Ninja Level

  • One of the things you can do to clean this up is to create a method that creates the FailureTasks object for you. If you do this, you can then use the screenshot mechanism like this:
webDriver.WhenThisFails(dr => {/*some function*/}).TakeScreenshot();

References   [ + ]

1. ITakesScreenshot)_webDriver).GetScreenshot(); ss.SaveAsFile( Path.Combine( Environment.CurrentDirectory, string.Format("{0} - {1}.png", "Hello World", "Google"

The WebDriverWait

It won’t take very long for an automated tester to realize that their test become brittle when dealing with dynamic web pages. Elements the tester is trying to manipulate will change in the middle of your tests causing exceptions.

Take, for example, the test we created in the Hello World! post. The Help button is one of the last things to load on the Google search page.

Lets test that the text of the Help button is indeed “Help”. Add the following to the HelloWorld test right before the WebDriver disposal.

IWebElement imagesLink = webDriver.FindElement(By.XPath("//a[[text()='Images for Hello World!'\]]"));
Assert.AreEqual(true, imagesLink.Displayed);

Run the test and you’ll find that you receive a StaleElementReferenceException.

You may be tempted to use something like c# threading functionality to “pause” the test and wait for a dynamic website to settle. This should be avoided at all cost. “Pauses” of a constant length will waste valuable resource time and you’ll find that your tests still fail occasionally.

Instead, perform a “pause” of dynamic length. The WebDriverWait can be used for this purpose.

The WebDriverWait class is contained in the OpenQA.Selenium.Support.UI library, so import it in addition to the traditional Selenium library. The Support.UI library is contained in the Selenium WebDriver Support Classes nuget package.

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;

Now, we can wrap the code we added to HelloWorld in a WebDriverWait.

new WebDriverWait(webDriver, TimeSpan.FromSeconds(5)).Until<bool>(dr =>
    {
        try
        {
            IWebElement imagesLink= dr.FindElement(By.XPath("//a[[text()='Images for Hello World!']]"));
            Assert.AreEqual(true, imagesLink.Displayed);
            return true;
        }
        catch
        {
            return false;
        }
    });

The WebDriverWait constructor takes two parameters.

  • IWebDriver webDriver – this is your WebDriver object.
  • TimeSpan Timeout – this is a time span which describes the maximum amount of time we will wait for the test to pass.

The Until method takes one parameter, a function. This syntax is referred to as a lambda expression.

The variable ‘dr’ becomes a reference to the calling object. In our case, the WebDriverWait.

What’s going to happen is code between the squiggly brackets is going to loop until either it returns true or the timeout is exceeded. We want to avoid throwing exceptions during this time so we catch all exceptions and return false instead.

With this technique, you can “wait” for an element to appear without wasting resource.

Ninja Level:
  • Make your timeout a global variable. This way if you need to increase/decrease your timeout, you only need to change it in one place.
  • You can change the object between the brackets to that of another object type. In this case, the function loops until a non-null value is returned.