/* * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. * * Copyright 2013 - 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sakuli.exceptions; import net.sf.sahi.report.ResultType; import org.sakuli.actions.screenbased.RegionImpl; import org.sakuli.aop.RhinoAspect; import org.sakuli.datamodel.*; import org.sakuli.datamodel.actions.LogResult; import org.sakuli.loader.ScreenActionLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; /** * @author tschneck Date: 12.07.13 */ @SuppressWarnings("ThrowableResultOfMethodCallIgnored") @Component public class SakuliExceptionHandler { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private ScreenActionLoader loader; private List<Throwable> processedExceptions = new ArrayList<>(); private List<Throwable> resumeExceptions = new ArrayList<>(); /** * @return all exceptions from the test suite and there underlying test cases. */ public static List<Throwable> getAllExceptions(TestSuite testSuite) { List<Throwable> result = new ArrayList<>(); if (testSuite.getException() != null) { result.add(testSuite.getException()); } if (testSuite.getTestCases() != null) { for (TestCase tc : testSuite.getTestCases().values()) { if (tc.getException() != null) { result.add(tc.getException()); } for (TestCaseStep step : tc.getSteps()) { if (step.getException() != null) { result.add(step.getException()); } } } } return result; } /** * Checks if a {@link Throwable} is an instance of the {@link SakuliExceptionWithScreenshot}. * * @param e any {@link Throwable} * @return If true the method returns a valid {@link Path}. */ public static Path getScreenshotFile(Throwable e) { if (e instanceof SakuliExceptionWithScreenshot) { if (((SakuliExceptionWithScreenshot) e).getScreenshot() != null) { return ((SakuliExceptionWithScreenshot) e).getScreenshot(); } } if (e != null && e.getSuppressed() != null) { for (Throwable ee : e.getSuppressed()) { if (ee instanceof SakuliExceptionWithScreenshot) { return ((SakuliExceptionWithScreenshot) ee).getScreenshot(); } } } //retrun null if nothing matches return null; } static boolean containsException(TestSuite testSuite) { if (testSuite.getException() != null) { return true; } if (!CollectionUtils.isEmpty(testSuite.getTestCases())) { for (TestCase tc : testSuite.getTestCases().values()) { if (tc.getException() != null) { return true; } for (TestCaseStep step : tc.getSteps()) { if (step.getException() != null) { return true; } } } } return false; } /** * handleException methode for Eception, where no testcase could be identified; The default value for non {@link * SakuliException} is there that the Execution of the tescase will stop! * * @param e any Throwable */ public void handleException(Throwable e) { //avoid nullpointer for missing messages if (e.getMessage() == null) { e = new SakuliException(e, e.getClass().getSimpleName()); } //e.g. Proxy Exception should only be handled if no other exceptions have been added if (!(e instanceof SakuliInitException) || (!containsException(loader.getTestSuite()))) { //if the exception have been already processed do no exception handling! if (isAlreadyProcessed(e)) { logger.debug("ALREADY PROCESSED: " + e.getMessage(), e); } else { processException(e); } } } /** * Finally transfromes and saves the exception. This method should be called if the exception should really be * processed. * * @param e any {@link Throwable} */ protected void processException(Throwable e) { SakuliException transformedException = transformException(e); //Do different exception handling for different use cases: if (!resumeToTestExcecution(e)) { //normal handling logger.error(transformedException.getMessage(), transformedException); saveException(transformedException); // a {@link SakuliForwarderException}, should only added to the report and not stop sahi, because // this error types only on already started the tear down of test suite. if (e instanceof SakuliForwarderException) { addExceptionToSahiReport(transformedException); } //stop the execution and add to report if the exception is not caused by Sahi else if (!(e instanceof SahiActionException)) { stopExecutionAndAddExceptionToSahiReport(transformedException); } } //if exceptions should not stop the test case execution else if (!loader.getSakuliProperties().isSuppressResumedExceptions()) { // if suppressResumedExceptions == false logger.error(e.getMessage(), transformedException); saveException(transformedException); addExceptionToSahiReport(transformedException); } else { //if suppressResumedExceptions == true logger.debug(transformedException.getMessage(), transformedException); } processedExceptions.add(e); processedExceptions.add(transformedException); } /** * @return true if the exception have been already processed by Sakuli */ public boolean isAlreadyProcessed(Throwable e) { String message = e.getMessage() != null ? e.getMessage() : e.toString(); return message.contains(RhinoAspect.ALREADY_PROCESSED) || message.contains(("Logging exception:")) || processedExceptions.contains(e); } /** * @return true if, the exception should NOT stop the test case execution */ public boolean resumeToTestExcecution(Throwable e) { return resumeExceptions.contains(e); } /** * save the exception to the current testcase. If the current testcase is not reachale, then the exception will be * saved to the test suite. * * @param e any {@link SakuliException} */ void saveException(SakuliException e) { if (loader.getCurrentTestCase() != null) { if (loader.getCurrentTestCaseStep() != null) { loader.getCurrentTestCaseStep().addException(e); } else { loader.getCurrentTestCase().addException(e); } } else { loader.getTestSuite().addException(e); } loader.getTestSuite().refreshState(); } /** * save the exception to the current sahi report (HTML Report in the log folder). * * @param e any {@link SakuliException} */ private void addExceptionToSahiReport(SakuliException e) { if (loader.getSahiReport() != null) { loader.getSahiReport().addResult( e.getMessage(), ResultType.ERROR, e.getStackTrace().toString(), e.getMessage() + RhinoAspect.ALREADY_PROCESSED); } } /** * stops the execution of the current test case and add the exception to the sahi report (HTML Report in the log * folder). * * @param e any {@link SakuliException} */ private void stopExecutionAndAddExceptionToSahiReport(SakuliException e) { if (loader.getRhinoScriptRunner() != null) { loader.getRhinoScriptRunner().setStopOnError(true); loader.getRhinoScriptRunner().setHasError(); throw new RuntimeException(RhinoAspect.ALREADY_PROCESSED + e.getMessage()); } } /** * transforms any {@link Throwable} to SakuliException. If the property 'sakuli.screenshot.onError=true' is set, the * methods add a Screenshot. * * @param e a {@link Throwable} * @return <EX> {@link SakuliException} or any child. */ private SakuliException transformException(Throwable e) { if (loader.getActionProperties().isTakeScreenshots() && !(e instanceof NonScreenshotException)) { //try to get a screenshot try { Path screenshot = loader.getScreenshotActions().takeScreenshot( e.getMessage(), loader.getActionProperties().getScreenShotFolder()); return addResumeOnException(new SakuliExceptionWithScreenshot(e, screenshot), resumeToTestExcecution(e)); } catch (IOException e2) { logger.error("Screenshot could not be created", e2); e.addSuppressed(e2); } } return addResumeOnException((e instanceof SakuliException) ? (SakuliException) e : new SakuliException(e), resumeToTestExcecution(e)); } public void handleException(Throwable e, boolean resumeOnException) { handleException(addResumeOnException(e, resumeOnException)); } public void handleException(String exceptionMessage, boolean resumeOnException) { handleException(new SakuliException(exceptionMessage), resumeOnException); } public void handleException(String exceptionMessage, RegionImpl lastRegion, boolean resumeOnException) { handleException(addResumeOnException( new SakuliActionException(exceptionMessage, lastRegion), resumeOnException )); } public void handleException(Throwable e, RegionImpl lastRegion, boolean resumeOnException) { handleException(addResumeOnException( lastRegion != null ? new SakuliActionException(e, lastRegion) : e, resumeOnException )); } public void handleException(LogResult logResult) { handleException(new SahiActionException(logResult)); } private <T extends Throwable> T addResumeOnException(T e, boolean resumeOnException) { if (resumeOnException) { resumeExceptions.add(e); } return e; } /** * Throws a new {@link SakuliRuntimeException} for all collected resumed exceptions. A resumed exception have been * created by{@link #handleException(Throwable, boolean)} with resumeOnException==true. */ public void throwCollectedResumedExceptions() { if (resumeExceptions.size() > 0) { SakuliRuntimeException e = new SakuliRuntimeException("test contains some suppressed resumed exceptions!"); resumeExceptions.stream().forEach(t -> e.addSuppressed(t)); throw e; } } }