Why take screenshots?

Taking a screenshot of the page after a failed test scenario can save a lot of debugging effort as well as keep track of changes in the site layout and can be a valuable tool when doing quality assurance of a website. A screen capture can immediately show what happened and clarify what caused a step to fail, which is even more useful when running the tests in headless mode. The quicker the problem is found the better, especially in agile software development.

What is needed to do it effectively:
- a way to check the scenario status
- step hooks to take actions after each scenario
- a way to take a screenshot if the scenario failed
- naming convention for the screenshot files

We'll go through each of the items above and combine them for a complete solution for Cucumber and watir-webdriver.

Checking scenario status

For this purpose, we can take advantage of Cucumber scenario hooks. There are many types of hooks that can be used for different purposes. Below is an example cucumber scenario hook that gets executed after a scenario is run:

    After do |scenario|
        puts 'Test scenario run'
    end

Another possibly useful hook in this context is

    AfterStep do |scenario|
        puts 'A useful action'
    end

There is a trade-off between the amount of information stored and how much added value do the extra screenshots provide. Taking a screenshot after every step might be too much information.

Instead of just printing out useless messages, we can call a method to take a screenshot, which we are going to implement later. In this example, the method is only called if the scenario failed.

    After |scenario| do 
        take_screenshot(scenario) if scenario.failed?
    end

Having screenshots of a site can also be useful for documentation purposes, in which case the decision of whether to take a screenshot or not can be based on other factors, for example tags, environment variables or a combination of both. Following example demostrates taking a screenshot of the scenarios having the tag @screenshot:

    After('@screenshot') do |scenario|
        take_screenshot(scenario)
    end

The examples above pass in the scenario to the take_screenshot method, which we are going to use later when determining the output image file name.

Taking the screenshot itself is easy and can be done with the following code, where browser refers to a watir-webdriver browser instance:

    def take_screenshot
        browser.screenshot.save "<name>.png"
    end

Screenshot filename

The screenshot name should somehow reflect the text that was executed. One method of naming a screenshot is to use the scenario name that was executed when it failed. For scenarios, the title is available with the following method:

    scenario.title

For scenario outlines, the name can be retrieved with the following method

    scenario.scenario_outline.name

One way to determine if the current scenario is a plain scenario or a scenario outline is to check if it supports the title method and based on the result of the check use one of the above methods to get the scenario name. respond_to? method is the Ruby way to check if an object has a specific method:

    scenario.respond_to?(:title)

The next step is to convert the output from the previous commands to a valid file name. Scenario names usually contain spaces and may contain other characters not suitable for a file name. One way to make the title suitable as a file name is to replace all non-alphabet characters with the following command using a regular expression to match the characters to be removed:

    scenario.title.tr('^a-zA-Z', '_')

This is only an example and there are plenty of other ways to build the file name. The best way again depends on your environments specific requirements.

I usually also add some additional controls in case the screenshots are not required in some build jobs. An environment variable does the job fine:

$ export SCREENSHOTS=true

and then add a check in the Ruby code

if ENV['SCREENSHOTS'] == 'true' ...

Having all the pieces listed in the beginning of the post, we can combine them to the following:

def screenshot_name(scenario)
    if scenario.respond_to?(:title)
        name = scenario.title
    else 
        name = scenario.scenario_outline.name
    end
    name.tr('^a-zA-Z', '_') + '.png'
end

def take_screenshot(scenario)
    browser.screenshot.save screenshot_name(scenario)
end

After do |scenario|
    if ENV['SCREENSHOTS'] == 'true'
        take_screenshot(scenario) if scenario.failed?
    end
end

That's it. You should be able to get started with this short example.