22

When my test case fails, especially on our build server, I want to take a picture / screenshot of the screen to help me debug what happened later on. I know how to take a screenshot, but I was hoping for a way in JUnit to call my takeScreenshot() method if a test fails, before the browser is closed.

No, I don't want to go edit our bazillions of tests to add a try/catch. I could maybe, just possibly be talked into an annotation, I suppose. All of my tests have a common parent class, but I can't think of anything I can do there to solve this.

Ideas?

Ripon Al Wasim
  • 36,924
  • 42
  • 155
  • 176
Ryan Shillington
  • 23,006
  • 14
  • 93
  • 108
  • 1
    In what kind of failure? When it can't click on a element? When it can't find an element immeaditely? When it can't find an element after waiting a little bit? When it can't launch the browser? The way I have done this is have a central method when the browser finds elements, wrap it in a try catch and in the catch, take a screenshot. Whenever I need to find an element, call this. Then this will handle it for you. – Arran Sep 14 '12 at 23:13
  • 1
    With TestNG you can easly achive that. You only need a listner that takes the screenshot. If you are interested in a TestNG solution I will post my code for that tomorrow. – Tarken Sep 18 '12 at 07:18
  • 1
    Possible dup of: [Selenium 2 and JUnit4: How to capture screenshot on exception?](http://stackoverflow.com/a/7649994/413020) – Alberto Dec 13 '13 at 12:08

3 Answers3

18

A few quick searches led me to a relevant article, "Grabbing Screenshots of Failed Selenium Tests" by Jason Lee, dated 24 Jan 2012. (Defunct original blogs.steeplesoft.com url: http://blogs.steeplesoft.com/posts/2012/grabbing-screenshots-of-failed-selenium-tests.html, now available from an archive.org mirror dated 7 Mar 2017.)

In short, he recommends creating a JUnit4 Rule that wraps the test Statement in a try/catch block in which he calls:

imageFileOutputStream.write(
    ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));

In the comments, Leukipp links to a similar article "Performing an action when a test fails", by Thomas Sundberg, dated 8 Jul 2012, which offers a very similar @Rule but using getScreenshotAs(OutputType.FILE) which is then copied/moved to the intended destination.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Yes, that's awesome. I wish I'd seen this earlier. I've already gone with Tarken's approach. – Ryan Shillington Nov 21 '12 at 22:16
  • 1
    I found this searching for a c# way to do it. So for posterity in c# the way to do it is something like `((ITakesScreenshot)driver).GetScreenShot().SaveAsFile(@"path\to\file.png" ImageFormat.Png)` – George Mauer Jun 26 '14 at 22:54
  • @bummi Great, thanks! (And thanks Luc for the edit!) – Jeff Bowman Apr 09 '15 at 18:58
  • 3
    The following article describes how to use a TestWatcher for taking screenshots: http://www.thinkcode.se/blog/2012/07/08/performing-an-action-when-a-test-fails I found it more helpful than the article mentioned by Jeff. – Leukipp Jul 28 '16 at 00:03
  • This article back from 2012 doesn't exist anymore. – user1053510 Aug 23 '23 at 08:56
  • 1
    @user1053510 I've updated the post with an archive.org link and integrated Leukipp's other blog (which is very similar and still available). – Jeff Bowman Aug 23 '23 at 13:35
7

If you want to quickly add this behavior to ALL your tests in the run you can use the RunListener interface to listen for test failures.

public class ScreenshotListener extends RunListener {

    private TakesScreenshot screenshotTaker;

    @Override
    public void testFailure(Failure failure) throws Exception {
        File file = screenshotTaker.getScreenshotAs(OutputType.File);
        // do something with your file
    }

}

Add the listener to your test runner like this...

JUnitCore junit = new JUnitCore();
junit.addListener(new ScreenshotListener((TakesScreenShots) webDriver));

// then run your test...

Result result = junit.run(Request.classes(FullTestSuite.class));
dellsala
  • 932
  • 1
  • 9
  • 16
3

If you want to take a screenshot on test failure, add this class

import java.io.File;

import java.io.IOException;

import java.util.UUID;

import org.apache.commons.io.FileUtils;

import org.junit.rules.MethodRule;

import org.junit.runners.model.FrameworkMethod;

import org.junit.runners.model.Statement;

import org.openqa.selenium.OutputType;

import org.openqa.selenium.TakesScreenshot;

import org.openqa.selenium.WebDriver;

public class ScreenShotOnFailure implements MethodRule {

    private WebDriver driver;

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

    public Statement apply(final Statement statement, final FrameworkMethod frameworkMethod, final Object o) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    statement.evaluate();
                } catch (Throwable t) {
                    captureScreenShot(frameworkMethod.getName());
                    throw t;
                }
            }

            public void captureScreenShot(String fileName) throws IOException {
                File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
                fileName += UUID.randomUUID().toString();
                File targetFile = new File("./Screenshots/" + fileName + ".png");
                FileUtils.copyFile(scrFile, targetFile);
            }
        };
    }
}

And Before all tests, you should use this Rule:

@Rule
public ScreenShotOnFailure failure = new ScreenShotOnFailure(driver));

@Before
public void before() {
   ...
}
Norayr Sargsyan
  • 1,737
  • 1
  • 12
  • 26