13

Does any of you know possible solution for taking screenshots on test failures and exceptions?

I've added following code in TearDown() but as a result it also makes screenshots on passed tests, so it is not the best solution:

DateTime time = DateTime.Now;
string dateToday = "_date_" + time.ToString("yyyy-MM-dd") + "_time_" + time.ToString("HH-mm-ss");
Screenshot screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile((settings.filePathForScreenShots + "Exception" + dateToday + ".png"), System.Drawing.Imaging.ImageFormat.Png);

I've already found that idea: http://yizeng.me/2014/02/08/take-a-screenshot-on-exception-with-selenium-csharp-eventfiringwebdriver/, to use WebDriverExceptionEventArgs, but for some reasons it makes also some random screenshots without any reasonable explanation.

Other ideas I found are for Java and not for NUnit which I use with Selenium, so they are pretty useless.

Tomek
  • 701
  • 2
  • 8
  • 20

7 Answers7

17

If you put the screenshot logic in your TearDown method it will be called after each test finishes, no matter if it succeeded or failed.

I use a base class that has a function which wraps the tests and catches all exceptions. When a test fails the exception is caught and a screenshot is taken.

I use this base class for all my Selenium tests and it looks something like this:

public class PageTestBase
{
    protected IWebDriver Driver;

    protected void UITest(Action action)
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            var screenshot = Driver.TakeScreenshot();

            var filePath = "<some appropriate file path goes here>";

            screenshot.SaveAsFile(filePath, ImageFormat.Png);

            // This would be a good place to log the exception message and
            // save together with the screenshot

            throw;
        }
    }
}

The test classes then look like this:

[TestFixture]
public class FooBarTests : PageTestBase
{
    // Make sure to initialize the driver in the constructor or SetUp method,
    // depending on your preferences

    [Test]
    public void Some_test_name_goes_here()
    {
        UITest(() =>
        {
            // Do your test steps here, including asserts etc.
            // Any exceptions will be caught by the base class
            // and screenshots will be taken
        });
    }

    [TearDown]
    public void TearDown()
    {
        // Close and dispose the driver
    }
}
Erik Öjebo
  • 10,821
  • 4
  • 54
  • 75
  • It would be nice if you could capture the name of the test and include that in the filename of the screenshot. But I don't see how that can be done with this method? – Frank H. Mar 21 '17 at 08:57
  • @FrankH. You should be able to to that via reflection in the UITest method in this example. Since the UITest method is called from the test method, you should be able to get the name of the calling method from the UITest method. Try something along these lines: http://stackoverflow.com/questions/171970/how-can-i-find-the-method-that-called-the-current-method – Erik Öjebo Mar 22 '17 at 10:00
  • Are you sure your exceptions are logging the line number correctly? Is it logging exceptions from the correct assembly? We do something similar but have lost our line numbers because of AssemblyInfo is different when using wrappers. To clarify, we get Exceptions logged with the correct messages, but the line number info is missing and always 0. Is this the same case for you? – AussieJoe May 04 '18 at 20:00
  • This approach is working well for me, and isn't dependent on a particular unit test framework (xUnit, NUnit etc). Worth noting though that the `ImageFormat` enum seems to have been replaced with `ScreenshotImageFormat` – sbridewell Sep 11 '22 at 15:20
12

In C# I use NUnit 3.4. This offeres the OneTimeTearDown method that is able to access the TestContext including the state of the previous executed test. Do not use TearDown because it is not executed after a test fails ;)

using OpenQA.Selenium;
using System.Drawing.Imaging;

...

[OneTimeTearDown]
public void OneTimeTearDown()
{
    if (TestContext.CurrentContext.Result.Outcome != ResultState.Success)
    {
        var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
        screenshot.SaveAsFile(@"C:\TEMP\Screenshot.jpg", ImageFormat.Jpeg);
    }
}
Lukas
  • 434
  • 3
  • 14
  • 5
    So long as any SetUp method runs without error, the TearDown method is guaranteed to run. [Source](https://github.com/nunit/docs/wiki/TearDown-Attribute). I just found your answer and used the if statement in a TearDown without any issues. IMO this should be the accepted answer, though. – kirbycope Dec 24 '17 at 19:07
  • 2
    Please correct me if I'm wrong, but I thought `OneTimeTearDown` is [only executed](https://github.com/nunit/docs/wiki/OneTimeTearDown-Attribute) after all the tests in the fixture are done.So it'll be too late to get any screenshots. – trailmax May 31 '19 at 13:44
  • I agree with trailmax. It'd be too late to capture when something else or some other tests clicked other stuff. It should be done in the catch block and I like the global try catch example from the top example, in addition you can use it in extension methods instead. – Nate R May 21 '21 at 20:14
3

For greater justice here is the code for the MSTest

public TestContext TestContext { get; set; }

[TestCleanup]
public void TestCleanup()
{
  if (TestContext.CurrentTestOutcome == UnitTestOutcome.Failed)
  {
    var screenshotPath = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss.fffff}.png";
    MyDriverInstance.TakeScreenshot().SaveAsFile(screenshotPath);
    TestContext.AddResultFile(screenshotPath);
  }
}
Andrey Stukalin
  • 5,328
  • 2
  • 31
  • 50
1

YOu can achieve this easily in TestNG suite FIle Create a ScreenShot method like Below

public static void CaptureDesktop (String imgpath)
    {
        try
        {

            Robot robot = new Robot();
            Dimension screensize=Toolkit.getDefaultToolkit().getScreenSize();
            Rectangle screenRect = new Rectangle(screensize);
            BufferedImage screenshot = robot.createScreenCapture(screenRect);
            //RenderedImage screenshot = robot.createScreenCapture(screenRect);
        ImageIO.write(screenshot, "png" , new File(imgpath));

        }

In above method i used robot class so that you can take screen shot of Dekstop also(window+WebPage) and you can call this method in different Listener class which will implements ITestListener Interface. call your screen Shot method in OntestFailure() of that Listener Class

@Override
    public void onTestFailure(ITestResult arg0) {


        String methodname = arg0.getMethod().getMethodName();
        String imgpath = "./Screenshot/"+methodname+".jpg";
        Guru99TakeScreenshot.CaptureDesktop(imgpath);

    }

This code is working for me. But this code is written in JAVA. I hope this will work in C# if not i wish this code can help you

Rahil Kumar
  • 426
  • 8
  • 23
0

Customizing a bit of ExtentReport can give extremely useful report having exception+screenshot captured exactly at time of test failure . Screenshot can be placed alongside exception which user can use to know what was website doing when error occurred.

Report Example

enter image description here

Test

    @Test (enabled=true)                           
public void verifySearch() {
    extentlogger = extent.createTest("To verify verifySearch");
    //Your other code here.....
    soft.assertEquals("xxx", "xxxx");
    soft.assertAll();
   }

AfterMethod

     @AfterMethod
public void getResult(ITestResult result) throws Exception{
    if(result.getStatus() == ITestResult.FAILURE)
    {
        extentlogger.log(Status.FAIL, MarkupHelper.createLabel(result.getThrowable() + " - Test Case Failed", ExtentColor.RED));
        
        try {
     // get path of captured screenshot using custom failedTCTakeScreenshot method
            String screenshotPath = failedTCTakeScreenshot( result);
            extentlogger.fail("Test Case Failed Snapshot is below " + extentlogger.addScreenCaptureFromPath(screenshotPath));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Vikas Piprade
  • 272
  • 2
  • 7
0

Extension to Andrey Stukalin solution. If you are using SpecFlow 3.0, if(TestContext.CurrentTestOutcome == UnitTestOutcome.Failed)
doesn't work anymore.

Have to use if (scenarioContext.TestError != null)

0
var status = TestContext.CurrentContext.Result.Outcome.Status;
if (status == TestStatus.Failed)
   { 
       ITakesScreenshot ts= (ITakesScreenshot)driver;
       Screenshot screenshot = ts.GetScreenshot();
       screenshot.SaveAsFile("Path\\Screenshot1.png", ScreenshotImageFormat.Png);
   }