001    /*
002     * Created on May 6, 2007
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2007-2009  the original author or authors.
014     */
015    package org.fest.swing.testng.listener;
016    
017    import java.lang.reflect.Method;
018    import java.util.logging.Logger;
019    
020    import org.testng.ITestContext;
021    import org.testng.ITestResult;
022    import org.testng.Reporter;
023    
024    import org.fest.swing.annotation.GUITestFinder;
025    import org.fest.swing.image.ImageException;
026    import org.fest.swing.image.ScreenshotTaker;
027    import org.fest.util.VisibleForTesting;
028    
029    import static java.io.File.separator;
030    import static java.util.logging.Level.SEVERE;
031    
032    import static org.fest.swing.testng.listener.ScreenshotFileNameGenerator.screenshotFileNameFrom;
033    import static org.fest.util.Strings.*;
034    
035    /**
036     * Understands a <a href="http://testng.org" target="_blank">TestNG</a> listener that takes a screenshot when a GUI test
037     * fails.
038     * <p>
039     * <strong>Note:</strong> A test is consider a GUI test if it is marked with the annotation
040     * <code>{@link org.fest.swing.annotation.GUITest}</code>.
041     * </p>
042     * <p>
043     * To use this listener, we just need to make TestNG aware of it. The following is an example using Ant:
044     * <pre>
045     * &lt;testng <strong><span style="text-decoration: underline">listeners=&quot;org.fest.swing.testng.listener.ScreenshotOnFailureListener&quot;</span></strong> outputDir=&quot;${target.test.results.dir}&quot; haltOnFailure=&quot;true&quot; verbose=&quot;2&quot;&gt;
046     *   &lt;classfileset dir=&quot;${target.test.classes.dir}&quot; includes=&quot;&#42;&#42;/&#42;Test.class&quot; /&gt;
047     *   &lt;classpath location=&quot;${target.test.classes.dir}&quot; /&gt;
048     *   &lt;classpath location=&quot;${target.classes.dir}&quot; /&gt;
049     *   &lt;classpath refid=&quot;test.classpath&quot; /&gt;
050     * &lt;/testng&gt;
051     * </pre>
052     * </p>
053     * <p>
054     * You can find more information
055     * <a href="http://www.jroller.com/page/alexRuiz?entry=screenshots_of_failures_in_test" target="_blank">here</a>.
056     * </p>
057     *
058     * @author Alex Ruiz
059     */
060    public class ScreenshotOnFailureListener extends AbstractTestListener {
061    
062      private static Logger logger = Logger.getAnonymousLogger();
063    
064      private ScreenshotTaker screenshotTaker;
065      private OutputDirectory output;
066      private boolean ready;
067    
068      /**
069       * Creates a new <code>{@link ScreenshotOnFailureListener}</code>.
070       */
071      public ScreenshotOnFailureListener() {
072        try {
073          screenshotTaker = new ScreenshotTaker();
074        } catch (ImageException e) {
075          logger.log(SEVERE, "Unable to create ScreenshotTaker", e);
076        }
077      }
078    
079      @VisibleForTesting
080      String output() { return output.path(); }
081    
082      /**
083       * Gets the output directory from the given context after the test class is instantiated and before any configuration
084       * method is called.
085       * @param context the given method context.
086       */
087      @Override public void onStart(ITestContext context) {
088        output = new OutputDirectory(context);
089        logger.info(concat("TestNG output directory: ", quote(output.path())));
090        ready = output.hasPath() && screenshotTaker != null;
091      }
092    
093      /**
094       * When a test fails, this method takes a screenshot of the desktop and adds an hyperlink to the screenshot it in the
095       * HTML test report.
096       * @param result contains information about the failing test.
097       */
098      @Override public void onTestFailure(ITestResult result) {
099        if (!ready || !isGUITest(result)) return;
100        String screenshotFileName = takeScreenshotAndReturnFileName(result);
101        if (isEmpty(screenshotFileName)) return;
102        logger.info(concat("Screenshot of desktop saved as: ", quote(screenshotFileName)));
103        Reporter.setCurrentTestResult(result);
104        Reporter.log(concat("<a href=\"", screenshotFileName, "\">Screenshot</a>"));
105      }
106    
107      private static boolean isGUITest(ITestResult testResult) {
108        Class<?> realClass = testResult.getTestClass().getRealClass();
109        Method testMethod = testResult.getMethod().getMethod();
110        return GUITestFinder.isGUITest(realClass, testMethod);
111      }
112    
113      private String takeScreenshotAndReturnFileName(ITestResult result) {
114        String imageName = screenshotFileNameFrom(result);
115        String imagePath = concat(output(), separator, imageName);
116        try {
117          output.createIfNecessary();
118          screenshotTaker.saveDesktopAsPng(imagePath);
119        } catch (Exception e) {
120          logger.log(SEVERE, e.getMessage(), e);
121          return null;
122        }
123        return imageName;
124      }
125    }