Saturday, June 25, 2011

Screenshots in JBehave reports

JBehave with Selenium is able to create screenshots when scenario fails. It is very useful feature when you want to see what was happening on the screen when the test failed. Unfortunately JBehave Web documentation is not very helpful at this point.

The trader-runner-webdriver project from the examples in JBehave Web source code contains example of the solution. The screenshot feature is implemented through WebDriverScreenshotOnFailure class. This class just needs to be added to the step candidate list.

JBehave also supports linking these screenshots into the HTML report. Example is available in the tutorial project. This behaviour can be added to above trader-runner-webdriver example project very easily. There is a class WebDriverHtmlOutput in the JBehave/Selenium module which extends HtmlOutput. All you need to do is to replace HTML format in you format configuration with WebDriverHtmlOutput.WEB_DRIVER_HTML format. Modify TraderWebStories.configuration() - change:

    .withFormats(CONSOLE, TXT, HTML, XML));

to

    .withFormats(CONSOLE, TXT, WebDriverHtmlOutput.WEB_DRIVER_HTML, XML));

This solution is not perfect though. WebDriverScreenshotOnFailure class does not play well with stories executed with "doIgnoreFailureInStories(true)" flag (this setting is useful when you want to have report generated even when scenarios fail). There is a bug in JBehave (3.4.5). In case multiple scenarios in story fail, all screenshots are saved to the same file, i.e. rewriting each other. If you then try to open these screenshots from the HTML report first link opens but it will show a screenshot for the last failure, rest of the links won't work at all.

Trader-runner-webdriver project can be simply modified to show this bug:

  1. Add this method to TraderWebStories class:
        @Before
        public void init(){
            configuredEmbedder().embedderControls()
            .doGenerateViewAfterStories(true)
            .doIgnoreFailureInStories(true)
            .doIgnoreFailureInView(false);
        }
  2. Introduce failures to existing stories - add exception throwing to TraderWebSteps.stepsFound(List stepsOrMethods).

Run test and check report for the screenshot problems. You will see the the second link does not work at all and only single screenshot file exists.

There is a workaround for this problem:

  1. As WebDriverScreenshotOnFailure class does not provide required screenshots it is removed. Modify method TraderWebStories.stepsFactory() - remove WebDriverScreenshotOnFailure class from the list of step candidates.
  2. Create a new class ScreenshootingHtmlOutput which extends class WebDriverHtmlOutput. This class will extend failure handling with screenshot generation by calling WebDriverScreenshotOnFailure:
    public class ScreenshootingHtmlOutput extends WebDriverHtmlOutput {
    
        private WebDriverScreenshotOnFailure screenshotMaker;
    
        public ScreenshootingHtmlOutput(PrintStream output,
                StoryReporterBuilder reporterBuilder,
                WebDriverProvider webDriverProvider) {
            super(output, reporterBuilder.keywords());
            screenshotMaker = new WebDriverScreenshotOnFailure(
                webDriverProvider);
        }
    
        @Override
        public void failed(String step, Throwable storyFailure) {
            super.failed(step, storyFailure);
            try {
                UUIDExceptionWrapper uuidWrappedFailure =
                    (UUIDExceptionWrapper) storyFailure;
                screenshotMaker.afterScenarioFailure(uuidWrappedFailure);
            } catch (Exception e) {
                System.out.println("Screenshot failed");
            }
        }
    }
  3. Then a new format class is needed:
    public class ScreenshootingHtmlFormat extends Format{
        private WebDriverProvider webDriverProvider;
    
        public ScreenshootingHtmlFormat(WebDriverProvider webDriverProvider) {
            super("HTML");
            this.webDriverProvider = webDriverProvider;
        }
    
        @Override
        public StoryReporter createStoryReporter(
                FilePrintStreamFactory factory,
                StoryReporterBuilder builder) {
            factory.useConfiguration(
                    builder.fileConfiguration("html"));
            return new ScreenshootingHtmlOutput(factory.createPrintStream(),
                        builder, webDriverProvider)
                    .doReportFailureTrace(builder.reportFailureTrace())
                    .doCompressFailureTrace(builder.compressFailureTrace());
        }
    }
  4. And finally you need to modify the TraderWebStories class. Add field:
        private Format screenshootingFormat =
                new ScreenshootingHtmlFormat(driverProvider);
    
    and replace WebDriverHtmlOutput.WEB_DRIVER_HTML with screenshootingFormat so you get:
                        .withFormats(CONSOLE, TXT, screenshootingFormat, XML));
    

It is not very nice solution but it works. Hopefully the JBehave bug is going to be fixed soon and this nasty workaround won't be needed any more.