All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.seleniumtests.reporter.reporters.ReporterControler Maven / Gradle / Ivy

There is a newer version: 4.23.18
Show newest version
/**
 * Orignal work: Copyright 2015 www.seleniumtests.com
 * Modified work: Copyright 2016 www.infotel.com
 * 				Copyright 2017-2019 B.Hecquet
 *
 * 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 com.seleniumtests.reporter.reporters;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import org.testng.IReporter;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.SuiteRunner;
import org.testng.TestRunner;
import org.testng.reporters.FailedReporter;
import org.testng.xml.XmlSuite;

import com.seleniumtests.connectors.selenium.SeleniumRobotSnapshotServerConnector;
import com.seleniumtests.connectors.tms.reportportal.ReportPortalService;
import com.seleniumtests.core.SeleniumTestsContext;
import com.seleniumtests.core.SeleniumTestsContextManager;
import com.seleniumtests.core.testanalysis.ErrorCause;
import com.seleniumtests.core.testanalysis.ErrorCauseFinder;
import com.seleniumtests.core.utils.TestNGResultUtils;
import com.seleniumtests.customexception.CustomSeleniumTestsException;
import com.seleniumtests.customexception.ScenarioException;
import com.seleniumtests.driver.screenshots.ScreenshotUtil;
import com.seleniumtests.driver.screenshots.SnapshotComparisonBehaviour;
import com.seleniumtests.reporter.logger.TestMessage;
import com.seleniumtests.reporter.logger.TestMessage.MessageType;
import com.seleniumtests.reporter.logger.TestStep;
import com.seleniumtests.util.logging.SeleniumRobotLogger;
import com.seleniumtests.util.video.VideoUtils;

/**
 * This reporter controls the execution of all other reporter because TestNG
 * @author s047432
 *
 */
public class ReporterControler implements IReporter {
	

	private static final Object reporterLock = new Object();
	private static final Logger logger = SeleniumRobotLogger.getLogger(ReporterControler.class);
	private JUnitReporter junitReporter;
	private FailedReporter failedReporter;
	private ReportPortalReporter reportPortalReporter;

	public ReporterControler() {
		this(null);
	}
	
	public ReporterControler(ReportPortalService reportPortalService) {
		junitReporter = new JUnitReporter();
		failedReporter = new FailedReporter();
		if (reportPortalService != null) {
			reportPortalReporter = new ReportPortalReporter(reportPortalService);
		}
	}
	
	@Override
	public void generateReport(List xmlSuites, List suites, String outputDirectory) {
		generateReport(xmlSuites, suites, outputDirectory, null);
	}

	public void generateReport(List xmlSuites, List suites, String outputDirectory, ITestResult currentTestResult) {
		
		synchronized (reporterLock) {

			SeleniumRobotLogger.parseLogFile();
			Map> resultSet = updateTestSteps(suites, currentTestResult);
			try {
				new File(SeleniumTestsContextManager.getGlobalContext().getOutputDirectory()).mkdirs();
			} catch (Exception e) {
				logger.warn(String.format("Problem creating output directory %s: %s", SeleniumTestsContextManager.getGlobalContext().getOutputDirectory(), e.getMessage()));
			}
			
			// are we at the end of all suites (suite.getResults() has the same size as the returned result map)
			boolean suiteFinished = true;
			for (ISuite suite: suites) {
				if (suite.getResults().size() != resultSet.size()) {
					suiteFinished = false;
					break;
				}
			}
			
			// change / add test result according to snapshot comparison results
			if (suiteFinished) {
				changeTestResultsWithSnapshotComparison(suites);
			}
			
			// test steps to Report Portal
			if (reportPortalReporter != null && currentTestResult != null) {
				reportPortalReporter.generateReport(currentTestResult);
			}

			try {
				if (suiteFinished) {
					junitReporter.generateReport(xmlSuites, suites, SeleniumTestsContextManager.getGlobalContext().getOutputDirectory());
					failedReporter.generateReport(xmlSuites, suites, SeleniumTestsContextManager.getGlobalContext().getOutputDirectory());
				} else {
					junitReporter.generateReport(resultSet, SeleniumTestsContextManager.getGlobalContext().getOutputDirectory(), true);
				}
			} catch (Exception e) {
				logger.error("Error generating JUnit report", e);
			}

			// find error causes once tests are finished
			if (suiteFinished && SeleniumTestsContextManager.getGlobalContext().isFindErrorCause()) {
				findErrorCauses(resultSet);
			}
			
			
			for (Class reporterClass: SeleniumTestsContextManager.getGlobalContext().getReporterPluginClasses()) {
				try {
					if (suiteFinished) {
						CommonReporter reporter = CommonReporter.getInstance(reporterClass);
						reporter.generateReport(xmlSuites, suites, outputDirectory);
						
					// when the tests are currently running, do optimize reports (for example, html results will have their resources on CDN)
					} else {
						
						CommonReporter reporter = CommonReporter.getInstance(reporterClass);
						reporter.generateReport(resultSet, outputDirectory, true);
					}
					
				} catch (Exception e) {
					logger.error("Error generating report", e);
				}
			}

			cleanAttachments(currentTestResult);

		}
	}
	
	private void findErrorCauses(Map> resultSet) {
		for (Set rs: resultSet.values()) {
			for (ITestResult testResult: rs) {
				
				
				// When SeleniumRobotTestRecorder has been run, results are stored on seleniumRobot server and it's then possible 
				// to compare reference snapshot with current failed step (if any)
				if (!testResult.isSuccess() && TestNGResultUtils.isSeleniumServerReportCreated(testResult) && testResult.getThrowable() != null && !(testResult.getThrowable() instanceof AssertionError)) {

					logger.info("Search error cause for " + TestNGResultUtils.getTestName(testResult));
					List errorCauses = new ErrorCauseFinder(testResult).findErrorCause();
					TestNGResultUtils.setErrorCauses(testResult, errorCauses);
				} else {
					logger.info("Do not search error cause (requirements not satisfied) for " + TestNGResultUtils.getTestName(testResult));
				}
			}
		}
		
		
	}
	
	/**
	 * If snapshot comparison has been enabled, request snapshot server for each test result to know if comparison was successful
	 * /!\ This method is aimed to be called only once all test suites have been completed 
	 * @param suites	test suites
	 */
	private void changeTestResultsWithSnapshotComparison(List suites) {
		
		if (!(SeleniumTestsContextManager.getGlobalContext().getSeleniumRobotServerActive()
				&& SeleniumTestsContextManager.getGlobalContext().getSeleniumRobotServerCompareSnapshot())) {
			return;
		}
		
		SeleniumRobotSnapshotServerConnector snapshotServer = SeleniumRobotSnapshotServerConnector.getInstance();
		
		for (ISuite suite: suites) {
			for (String suiteString: suite.getResults().keySet()) {
				ISuiteResult suiteResult = suite.getResults().get(suiteString);
				
				Set resultSet = new HashSet<>(); 
				resultSet.addAll(suiteResult.getTestContext().getFailedTests().getAllResults());
				resultSet.addAll(suiteResult.getTestContext().getPassedTests().getAllResults());
				resultSet.addAll(suiteResult.getTestContext().getSkippedTests().getAllResults());
				
				for (ITestResult testResult: resultSet) {
					
					// check if we have an id from snapshot server
					Integer testCaseInSessionId = TestNGResultUtils.getSnapshotTestCaseInSessionId(testResult);
					if (testCaseInSessionId == null) {
						continue;
					}
					
					StringBuilder errorMessage = new StringBuilder();
					int snapshotComparisonResult = snapshotServer.getTestCaseInSessionComparisonResult(testCaseInSessionId, errorMessage);
					
					// update snapshot comparison result of the run test.
					TestNGResultUtils.setSnapshotComparisonResult(testResult, snapshotComparisonResult);
					
					// create a step for snapshot comparison
					createTestStepForComparisonResult(testResult, snapshotComparisonResult, errorMessage.toString());
					
					changeTestResultWithSnapshotComparison(suiteResult, testResult, snapshotComparisonResult);
				}
			}
		}
	}

	/**
	 * Create a step with the comparison result
	 * @param testResult
	 * @param snapshotComparisonResult
	 * @return
	 */
	private void createTestStepForComparisonResult(ITestResult testResult, int snapshotComparisonResult, String errorMessage) {
		// create a step for snapshot comparison
		TestStep testStep = new TestStep("Snapshot comparison", testResult, new ArrayList<>(), false);
	
		if (snapshotComparisonResult == ITestResult.FAILURE) {
			testStep.addMessage(new TestMessage("Comparison failed: " + errorMessage, MessageType.ERROR));
			testStep.setFailed(true);
		} else if (snapshotComparisonResult == ITestResult.SUCCESS) {
			testStep.addMessage(new TestMessage("Comparison successful", MessageType.INFO));
		} else if (snapshotComparisonResult == ITestResult.SKIP && !errorMessage.isEmpty()) {
			testStep.addMessage(new TestMessage("Comparison skipped: " + errorMessage, MessageType.ERROR));
			testStep.setFailed(true);
		} else if (snapshotComparisonResult == ITestResult.SKIP && errorMessage.isEmpty()) {
			testStep.addMessage(new TestMessage("No comparison to do (no snapshots)", MessageType.LOG));
		}
		
		getAllTestSteps(testResult).add(testStep);
	}

	/**
	 * @param suiteResult
	 * @param testResult
	 * @param snapshotComparisonResult
	 */
	private void changeTestResultWithSnapshotComparison(ISuiteResult suiteResult, ITestResult testResult, int snapshotComparisonResult) {
		// based on snapshot comparison flag, change test result only if comparison is KO
		if (SeleniumTestsContextManager.getGlobalContext().getSeleniumRobotServerCompareSnapshotBehaviour() == SnapshotComparisonBehaviour.CHANGE_TEST_RESULT && snapshotComparisonResult == ITestResult.FAILURE ) {
			testResult.setStatus(ITestResult.FAILURE);
			testResult.setThrowable(new ScenarioException("Snapshot comparison failed"));
			
		} else if (SeleniumTestsContextManager.getGlobalContext().getSeleniumRobotServerCompareSnapshotBehaviour() == SnapshotComparisonBehaviour.ADD_TEST_RESULT) {
			
			ITestResult newTestResult;
			try {
				newTestResult = TestNGResultUtils.copy(testResult, "snapshots-" +testResult.getName(), TestNGResultUtils.getTestDescription(testResult) + " FOR SNAPSHOT COMPARISON");
			} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
				throw new ScenarioException(e.getMessage(), e);
			}
			
			// add the test result
			newTestResult.setStatus(snapshotComparisonResult);
			if (snapshotComparisonResult == ITestResult.SUCCESS) {
				suiteResult.getTestContext().getPassedTests().addResult(newTestResult, newTestResult.getMethod());
			} else if (snapshotComparisonResult == ITestResult.FAILURE) {
				newTestResult.setThrowable(new ScenarioException("Snapshot comparison failed"));
				suiteResult.getTestContext().getFailedTests().addResult(newTestResult, newTestResult.getMethod());
			} else if (snapshotComparisonResult == ITestResult.SKIP) {
				suiteResult.getTestContext().getSkippedTests().addResult(newTestResult, newTestResult.getMethod());
			}
			
			// add a snapshot comparison result for the newly created test result (which correspond to snapshot comparison)
			TestNGResultUtils.setSnapshotComparisonResult(newTestResult, snapshotComparisonResult);
		}
	}
	
	/**
	 * Add configurations methods to list of test steps so that they can be used by reporters
	 * @param suites				List of test suite to parse
	 * @param currentTestResult		When we generate temp results, we get a current test result so that we do not wait test2 to be executed to get test1 displayed in report
	 */
	private Map> updateTestSteps(List suites, ITestResult currentTestResult) {
		Map> allResultSet = new LinkedHashMap<>();
		
		for (ISuite suite: suites) {
			
			Field testRunnersField;
			List testContexts;
			try {
				testRunnersField = SuiteRunner.class.getDeclaredField("testRunners");
				testRunnersField.setAccessible(true);
				testContexts = (List) testRunnersField.get(suite);
			} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException | ClassCastException e) {
				throw new CustomSeleniumTestsException("TestNG may have changed");
			}
			
			// If at least one test (not a test method, but a TestNG test) is finished, suite contains its results
			for (String suiteString: suite.getResults().keySet()) {
				ISuiteResult suiteResult = suite.getResults().get(suiteString);

				Set resultSet = new HashSet<>(); 
				resultSet.addAll(suiteResult.getTestContext().getFailedTests().getAllResults());
				resultSet.addAll(suiteResult.getTestContext().getPassedTests().getAllResults());
				resultSet.addAll(suiteResult.getTestContext().getSkippedTests().getAllResults());
				allResultSet.put(suiteResult.getTestContext(), resultSet);
			}
			
			// complete the suite result with remaining, currently running tests
			for (TestRunner testContext: testContexts) {

				if (allResultSet.containsKey(testContext)) {
					continue;
				}

				Set resultSet = removeUnecessaryResults(testContext, currentTestResult);

				allResultSet.put(testContext, resultSet);
			}

			for (Set resultSet: allResultSet.values()) {
				for (ITestResult testResult: resultSet) {
					List testSteps = getAllTestSteps(testResult);
					
					Long testDuration = 0L;
					synchronized (testSteps) {
						for (TestStep step: testSteps) {
							testDuration += step.getDuration();
						}
					}
					
					testResult.setEndMillis(testResult.getStartMillis() + testDuration);
				}
			}
		}
		
		return allResultSet;
	}
	
	/**
	 * Remove duplicated results (when a test is reexecuted, we have several results for the same scenario)
	 * 
	 * TODO: see if we could remove the same method in SeleniumRobotTestListener
	 * @param context
	 * @param currentTestResult
	 * @return
	 */
	public Set removeUnecessaryResults(ITestContext context, ITestResult currentTestResult) {
		
		// copy current results in context so that it does not change during processing when several threads are used
		List allResults = new ArrayList<>();
		Set passedTests = new TreeSet<>(context.getPassedTests().getAllResults());
		Set failedTests = new TreeSet<>(context.getFailedTests().getAllResults());
		Set skippedTests = new TreeSet<>(context.getSkippedTests().getAllResults());
		
		allResults.addAll(passedTests);
		allResults.addAll(failedTests);
		allResults.addAll(skippedTests);
		
		if (currentTestResult != null && currentTestResult.getTestContext() != null && currentTestResult.getTestContext().equals(context)) {
			allResults.add(currentTestResult);
		}

		// get an ordered list of test results so that we keep the last one of each test
		allResults = allResults.stream()
				.sorted((r1, r2) -> Long.compare(r1.getStartMillis(), r2.getStartMillis()))
				.collect(Collectors.toList());
		
		// contains only the results to keep, that means, the last execution of each test
		Map uniqueResults = new HashMap<>();
		for (ITestResult result: allResults) {
			String hash = TestNGResultUtils.getHashForTest(result);
			uniqueResults.put(hash, result);
		}
		
		// remove results we do not want from context
		List resultsToKeep = new ArrayList<>(uniqueResults.values());
		
		for (ITestResult result: failedTests) {
			if (!resultsToKeep.contains(result)) {
				context.getFailedTests().removeResult(result);
			}
		}
		for (ITestResult result: skippedTests) {
			if (!resultsToKeep.contains(result)) {
				context.getSkippedTests().removeResult(result);
			}
		}
		for (ITestResult result: passedTests) {
			if (!resultsToKeep.contains(result)) {
				context.getPassedTests().removeResult(result);
			}
		}
		
		Set resultSet = new HashSet<>(); 
		resultSet.addAll(context.getFailedTests().getAllResults());
		resultSet.addAll(context.getPassedTests().getAllResults());
		resultSet.addAll(context.getSkippedTests().getAllResults());
		
		// it's our current result, so we want if context matches
		if (currentTestResult != null && currentTestResult.getTestContext() != null && currentTestResult.getTestContext().equals(context)) {
			resultSet.add(currentTestResult);
		}
		
		return resultSet;
	}
	
	/**
	 * Delete all files in html and screenshot folders that are not directly references by test steps in current result
	 * @param suites
	 */
	private void cleanAttachments(ITestResult currentResult) {

		if (currentResult == null) {
			return;
		}
		
		List usedFiles = new ArrayList<>();
		List allFiles = new ArrayList<>();
		
		// without context, nothing can be done
		SeleniumTestsContext testContext = TestNGResultUtils.getSeleniumRobotTestContext(currentResult);
		if (testContext == null) {
			return;
		}
		
		// get files referenced by the steps
		for (TestStep testStep: testContext.getTestStepManager().getTestSteps()) {
			try {
				testStep.moveAttachments(testContext.getOutputDirectory());
			} catch (IOException e) {
				logger.error("Cannot move attachment " + e.getMessage());
			}
			usedFiles.addAll(testStep.getAllAttachments());
			
		}

		allFiles.addAll(listAttachments(testContext));
		
		for (File file: allFiles) {
			if (!usedFiles.contains(file)) {
				try {
					Files.deleteIfExists(file.toPath());
				} catch (IOException e)  {
					logger.info(String.format("File %s not deleted: %s", file.getAbsolutePath(), e.getMessage()));
				}
			}
		}
	}
	
	/**
	 * List all attachments in output directory folder
	 * @param testContext
	 * @return
	 */
	private List listAttachments(SeleniumTestsContext testContext) {
		
		List allFiles = new ArrayList<>();

		String outputSubDirectory = new File(testContext.getOutputDirectory()).getName();
		String outputDirectoryParent = new File(testContext.getOutputDirectory()).getParent();
		File htmlDir = Paths.get(outputDirectoryParent, outputSubDirectory, ScreenshotUtil.HTML_DIR).toFile();
		File htmlBeforeDir = Paths.get(outputDirectoryParent, "before-" + outputSubDirectory, ScreenshotUtil.HTML_DIR).toFile();
		File screenshotDir = Paths.get(outputDirectoryParent, outputSubDirectory, ScreenshotUtil.SCREENSHOT_DIR).toFile();
		File screenshotBeforeDir = Paths.get(outputDirectoryParent, "before-" + outputSubDirectory, ScreenshotUtil.SCREENSHOT_DIR).toFile();
		File videoDir = Paths.get(outputDirectoryParent, outputSubDirectory, VideoUtils.VIDEO_DIR).toFile();
		
		// get list of existing files
		if (htmlDir.isDirectory()) {
			allFiles.addAll(Arrays.asList(htmlDir.listFiles()));
		}
		if (screenshotDir.isDirectory()) {
			allFiles.addAll(Arrays.asList(screenshotDir.listFiles()));
		}
		if (htmlBeforeDir.isDirectory()) {
			allFiles.addAll(Arrays.asList(htmlBeforeDir.listFiles()));
		}
		if (screenshotBeforeDir.isDirectory()) {
			allFiles.addAll(Arrays.asList(screenshotBeforeDir.listFiles()));
		}
		if (videoDir.isDirectory()) {
			allFiles.addAll(Arrays.asList(videoDir.listFiles()));
		}
		
		return allFiles;
	}

	/**
	 * Returns the list of all test steps, including configuration method calls
	 * Use TestStep created in LogAction.java
	 */
	protected List getAllTestSteps(final ITestResult testResult) {
		if (TestNGResultUtils.getSeleniumRobotTestContext(testResult) == null) {
			return new ArrayList<>();
		}
		return TestNGResultUtils.getSeleniumRobotTestContext(testResult).getTestStepManager().getTestSteps();

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy