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

com.seleniumtests.reporter.reporters.SeleniumTestsReporter2 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.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
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.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.testng.IReporter;
import org.testng.IResultMap;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.xml.XmlSuite;

import com.seleniumtests.core.SeleniumTestsContext;
import com.seleniumtests.core.SeleniumTestsContextManager;
import com.seleniumtests.core.SeleniumTestsPageListener;
import com.seleniumtests.core.runner.SeleniumRobotTestListener;
import com.seleniumtests.driver.DriverMode;
import com.seleniumtests.driver.TestType;
import com.seleniumtests.reporter.PluginsHelper;
import com.seleniumtests.reporter.logger.TestLogging;
import com.seleniumtests.reporter.logger.TestStep;
import com.seleniumtests.util.logging.SeleniumRobotLogger;

/**
 * Class for generating test report
 * @author behe
 *
 */
public class SeleniumTestsReporter2 extends CommonReporter implements IReporter {

	private static final String STATUS = "status";
	private static final String HEADER = "header";
	private static final String APPLICATION = "application";
	private static final String APPLICATION_TYPE = "applicationType";
	private static final String METHOD_RESULT_FILE_NAME = "methodResultFileName";

	protected PrintWriter mOut;

	private String outputDirectory;
	private String resources;
	private String generationErrorMessage = null;

	
	/**
	 * Copy resources necessary for result file
	 * @throws IOException
	 */
	public void copyResources() throws IOException {
		
		List styleFiles = Arrays.asList("seleniumRobot.css", "app.min.js", "seleniumRobot_solo.css", "seleniumtests_test1.gif",
											"seleniumtests_test2.gif", "seleniumtests_test3.gif", "seleniumRobot.js");
		styleFiles = new ArrayList<>(styleFiles);
		
		if (!SeleniumTestsContextManager.getGlobalContext().getOptimizeReports()) {
			styleFiles.add("bootstrap.min.css");
			styleFiles.add("bootstrap.min.js");
			styleFiles.add("Chart.min.js");
			styleFiles.add("jQuery-2.2.0.min.js");
			styleFiles.add("AdminLTE.min.css");
			styleFiles.add("lobsterTwo.css");
			styleFiles.add("css/font-awesome.min.css");
			styleFiles.add("fonts/fontawesome-webfont.eot");
			styleFiles.add("fonts/fontawesome-webfont.svg");
			styleFiles.add("fonts/fontawesome-webfont.ttf");
			styleFiles.add("fonts/fontawesome-webfont.woff");
			styleFiles.add("fonts/fontawesome-webfont.woff2");
			styleFiles.add("fonts/FontAwesome.otf");
		} 
		
		for (String fileName: styleFiles) {
			FileUtils.copyInputStreamToFile(Thread.currentThread().getContextClassLoader().getResourceAsStream("reporter/templates/" + fileName), 
											Paths.get(outputDirectory, RESOURCES_DIR, "templates", fileName).toFile());
		}
	}

	/**
	 * Completes HTML stream.
	 *
	 * @param  out
	 */
	protected void endHtml() {
		//Add footer
		
		try {
			VelocityEngine ve = initVelocityEngine();

			Template t = ve.getTemplate( "reporter/templates/report.part.test.footer.vm");
			StringWriter writer = new StringWriter();
			VelocityContext context = new VelocityContext();
			t.merge( context, writer );

			mOut.write(writer.toString());
			mOut.flush();
			mOut.close();
		} catch (Exception e) {
			logger.error("error writing result file end: " + e.getMessage());
		}
	}

	/**
	 * Generate result for a single test method
	 * @param ve			velocity engine used to generate file
	 * @param testResult	result for this test method
	 */
	public void generatePanel(final VelocityEngine ve, final ITestResult testResult) {

		try {
			Template t = ve.getTemplate( "reporter/templates/report.part.test.step.vm" );
			VelocityContext context = new VelocityContext();
			
			List testSteps = TestLogging.getTestsSteps().get(testResult);
			if (testSteps == null) {
				return;
			}
			
			for (TestStep testStep: testSteps) {
				
				TestStep encodedTestStep = testStep.encode("html");
				// step status
				if (encodedTestStep.getFailed()) {
					context.put(STATUS, FAILED_TEST);
				} else {
					context.put(STATUS, PASSED_TEST);
				}
				
				context.put("stepName", encodedTestStep.getName());
				context.put("stepDuration", encodedTestStep.getDuration() / (double)1000);
				context.put("step", encodedTestStep);	
				
				StringWriter writer = new StringWriter();
				t.merge( context, writer );
				mOut.write(writer.toString());
			}
			

		} catch (Exception e) {
			generationErrorMessage = "generatePanel, Exception creating a singleTest:" + e.getMessage();
			logger.error("Exception creating a singleTest.", e);
		}
	}
	
	/**
	 * Generate result for a single test method
	 * @param ve			velocity engine used to generate file
	 * @param testResult	result for this test method
	 */
	public void generateExecutionLogs(final VelocityEngine ve, final ITestResult testResult) {
		
		try {
			Template t = ve.getTemplate( "reporter/templates/report.part.test.logs.vm" );
			VelocityContext context = new VelocityContext();
			
			// add logs
			String logs = SeleniumRobotLogger.getTestLogs().get(getTestName(testResult));
			if (logs == null) {
				logs = "";
			}
			
			
			// exception handling
			String[] stack = null;
			if (testResult.getThrowable() != null) {
				StringBuilder stackString = new StringBuilder();
				generateTheStackTrace(testResult.getThrowable(), testResult.getThrowable().getMessage(), stackString);
				stack = stackString.toString().split("\n");
			}
			
			// encode logs
			List logLines = new ArrayList<>();
			for (String line: logs.split("\n")) {
				logLines.add(StringEscapeUtils.escapeHtml4(line));
			}
			context.put(STATUS, getTestStatus(testResult));
			context.put("stacktrace", stack);
			context.put("logs", logLines);
			
			StringWriter writer = new StringWriter();
			t.merge( context, writer );
			mOut.write(writer.toString());
			
			
		} catch (Exception e) {
			generationErrorMessage = "generateExecutionLogs, Exception creating execution logs:" + e.getMessage();
			logger.error("Exception creating execution logs.", e);
		}
	}
	
	/**
	 * Returns the test status as a string
	 * @param testResult
	 * @return
	 */
	private String getTestStatus(ITestResult testResult) {
		String testStatus = SKIPPED_TEST;
		if (testResult.getStatus() == ITestResult.SUCCESS) {
			testStatus = PASSED_TEST;
		} else if (testResult.getStatus() == ITestResult.FAILURE) {
			testStatus = FAILED_TEST;
		}
		return testStatus;
	}


	/**
	 * Generate all test reports
	 */
	@Override
	public void generateReport(final List xml, final List suites, final String outdir) {
		ITestContext testCtx = SeleniumTestsContextManager.getGlobalContext().getTestNGContext();
		if (testCtx == null) {
			logger.error("Looks like your class does not extend from SeleniumTestPlan!");
			return;
		}
		
		File f = new File(SeleniumTestsContextManager.getGlobalContext().getOutputDirectory());
		setOutputDirectory(f.getAbsolutePath());
		setResources(getOutputDirectory() + "/" + RESOURCES_DIR);      
		
		// Generate general report
		Map> methodResultsMap = new HashMap<>(); 
		try {
			mOut = createWriter(getOutputDirectory(), "SeleniumTestReport.html");
			startHtml(null, mOut, "complete");
			methodResultsMap = generateSuiteSummaryReport(suites);
			endHtml();
			mOut.flush();
			mOut.close();
			copyResources();
			logger.info("Completed Report Generation.");

		} catch (IOException e) {
			logger.error("Error writing summary report", e);
		}  
		
		// Generate test method reports
		for (Map.Entry> entry: methodResultsMap.entrySet()) {
			
			for (ITestResult testResult: entry.getValue()) {
				
				// issue #81: recreate test context from this context (due to multithreading, this context may be null if parallel testing is done
				SeleniumTestsContextManager.setThreadContextFromTestResult(entry.getKey(), getTestName(testResult), getClassName(testResult), testResult);
				
				try {
					mOut = createWriter(SeleniumTestsContextManager.getThreadContext().getOutputDirectory(), "TestReport.html");
					startHtml(getTestStatus(testResult), mOut, "simple");
					generateExecutionReport(testResult);
					endHtml();
					logger.info("Completed Report Generation.");
				} catch (IOException e) {
					logger.error("Error writing test report: " + getTestName(testResult), e);
				}  
			}
		}

	}

	/**
	 * Generate summary report for all test methods
	 * @param suites
	 * @param suiteName
	 * @param map
	 * @return	map containing test results
	 */
	public Map> generateSuiteSummaryReport(final List suites) {
		
		// build result list for each TestNG test
		Map> methodResultsMap = new LinkedHashMap<>();
		
		for (ISuite suite : suites) {
			Map tests = suite.getResults();
			for (ISuiteResult r : tests.values()) {
				ITestContext context = r.getTestContext();
				List resultList = new ArrayList<>();
				
				Collection methodResults = new ArrayList<>();
				methodResults.addAll(context.getFailedTests().getAllResults());
				methodResults.addAll(context.getPassedTests().getAllResults());
				methodResults.addAll(context.getSkippedTests().getAllResults());

				methodResults = methodResults.stream()
							.sorted((r1, r2) -> Long.compare(r1.getStartMillis(), r2.getStartMillis()))
							.collect(Collectors.toList());
				
				for (ITestResult result: methodResults) {
					SeleniumTestsContext testContext = (SeleniumTestsContext)result.getAttribute(SeleniumRobotTestListener.TEST_CONTEXT);
					
					String fileName;
					if (testContext != null) {
						fileName = testContext.getRelativeOutputDir() + "/TestReport.html";
					} else {
						fileName = getTestName(result) + "/TestReport.html";
					}
					result.setAttribute(METHOD_RESULT_FILE_NAME, fileName);
					result.setAttribute(SeleniumRobotLogger.UNIQUE_METHOD_NAME, getTestName(result));
					
				}
				resultList.addAll(methodResults);
				
				methodResultsMap.put(context, resultList);
			}
		}

		try {
			VelocityEngine ve = initVelocityEngine();

			Template t = ve.getTemplate("/reporter/templates/report.part.suiteSummary.vm");
			VelocityContext context = new VelocityContext();

			context.put("tests", methodResultsMap);
			context.put("steps", TestLogging.getTestsSteps());

			StringWriter writer = new StringWriter();
			t.merge(context, writer);
			mOut.write(writer.toString());

		} catch (Exception e) {
			generationErrorMessage = "generateSuiteSummaryReport error:" + e.getMessage();
			logger.error("generateSuiteSummaryReport error: ", e);
		}
		
		return methodResultsMap;

	}

	/**
	 * @param   tests
	 *
	 * @return
	 */
	protected Collection getResultSet(final IResultMap tests, final ITestNGMethod method) {
		Set r = new TreeSet<>();
		for (ITestResult result : tests.getAllResults()) {
			if (result.getMethod().getMethodName().equals(method.getMethodName())) {
				r.add(result);
			}
		}

		return r;
	}

	public void setOutputDirectory(final String outtimestamped) {
		this.outputDirectory = outtimestamped;
	}

	public void setResources(final String resources) {
		this.resources = resources;
	}
	
	public String getOutputDirectory() {	
		return outputDirectory;
	}

	public String getResources() {
		return resources;
	}

	/**
	 * Begin HTML file
	 * @param testPassed	true if test is OK, false if test is KO, null if test is skipped
	 * @param out
	 * @param type
	 */
	protected void startHtml(final String testStatus, final PrintWriter out, final String type) {
		try {
			VelocityEngine ve = initVelocityEngine();

			Template t = ve.getTemplate("/reporter/templates/report.part.header.vm");
			VelocityContext context = new VelocityContext();

			String userName = System.getProperty("user.name");
			context.put("userName", userName);
			context.put("staticPathPrefix", "complete".equals(type) ? "": "../");
			
			// optimize reports means that resources are get from internet
			context.put("localResources", !SeleniumTestsContextManager.getGlobalContext().getOptimizeReports());
			context.put("currentDate", new Date().toString());

			DriverMode mode = SeleniumTestsContextManager.getGlobalContext().getRunMode();
			List hubUrls = SeleniumTestsContextManager.getGlobalContext().getWebDriverGrid();
			String hubLink = "";
			for (String hubUrl: hubUrls) {
				hubLink += "" + hubUrl + "";
			}
			context.put("gridHub", hubLink);

			context.put("mode", mode.toString());

			StringBuilder sbGroups = new StringBuilder();
			sbGroups.append("envt,test");

			List pageListenerList = PluginsHelper.getInstance().getPageListeners();
			if (pageListenerList != null && !pageListenerList.isEmpty()) {
				for (SeleniumTestsPageListener abstractPageListener : pageListenerList) {
					sbGroups.append(",").append(abstractPageListener.getClass().getSimpleName());
				}
			}
			context.put("groups", sbGroups.toString());
			context.put("report", type);

			if (type == "simple"){
				context.put(HEADER, testStatus);
			}
			StringWriter writer = new StringWriter();
			t.merge(context, writer);
			out.write(writer.toString());

		} catch (Exception e) {
			generationErrorMessage = "startHtml error:" + e.getMessage();
			logger.error("startHtml error:", e);
		}

	}

	public String getGenerationErrorMessage() {
		return generationErrorMessage;
	}
	
	/**
	 * Fill velocity context with test context
	 * @param velocityContext
	 */
	private void fillContextWithTestParams(VelocityContext velocityContext) {
		SeleniumTestsContext selTestContext = SeleniumTestsContextManager.getThreadContext();

		if (selTestContext != null) {
			String browser = selTestContext.getBrowser().getBrowserType();

			String app = selTestContext.getApp();
			String appPackage = selTestContext.getAppPackage();
			TestType testType = selTestContext.getTestType();

			if (browser != null) {
				browser = browser.replace("*", "");
			}

			String browserVersion = selTestContext.getWebBrowserVersion();
			if (browserVersion != null) {
				browser = browser + browserVersion;
			}
			velocityContext.put(APPLICATION, "");
			
			if (testType == null) {
				velocityContext.put(APPLICATION_TYPE, "Error in initialization");
		
			
			// Log URL for web test and app info for app test
			} else if (testType.family().equals(TestType.WEB)) {
				velocityContext.put(APPLICATION_TYPE, "Browser :");
				velocityContext.put(APPLICATION, browser);
			} else if (testType.family().equals(TestType.APP)) {
				
				// Either app Or app package and app activity is specified to run test on app
				if (StringUtils.isNotBlank(appPackage)) {
					velocityContext.put(APPLICATION_TYPE, "App Package :");
					velocityContext.put(APPLICATION, appPackage);
				} else  if (StringUtils.isNotBlank(app)) {
					velocityContext.put(APPLICATION_TYPE, "App :");
					velocityContext.put(APPLICATION, app);
				} 
			} else if (testType.family().equals(TestType.NON_GUI)) {
				velocityContext.put(APPLICATION_TYPE, "");

			} else {
				velocityContext.put(APPLICATION_TYPE, "Invalid Test type");
			}
		}  
	}

	/**
	 * Method for generating a report for test method
	 * @param suite			suite this test belongs to
	 * @param testContext
	 */
	public void generateExecutionReport(ITestResult testResult) {
		try {
			VelocityEngine ve = initVelocityEngine();
			Template t = ve.getTemplate( "reporter/templates/report.part.test.vm" );
			
			// create a context and add data
			VelocityContext velocityContext = new VelocityContext();
			
			Object[] testParameters = testResult.getParameters();
			StringBuilder testName = new StringBuilder(getTestName(testResult));
			
			// issue #163: add test parameter to test name
			if (testParameters.length > 0) {
				testName.append(" with params: (");
				
				int i = 0;
				
				for (Object param: testParameters) {
					testName.append(param.toString());
					if (i < testParameters.length - 1) {
						testName.append(",");
					}
					i++;
				}
				testName.append(")");
			}
			
			velocityContext.put("testName", testName);
			velocityContext.put("description", testResult.getMethod().getDescription());
			
			
			// Application information
			fillContextWithTestParams(velocityContext);       
			
			// write file
			StringWriter writer = new StringWriter();
			t.merge( velocityContext, writer );
			mOut.write(writer.toString());
			
			generatePanel(ve, testResult);
			generateExecutionLogs(ve, testResult);
			
			
			
		} catch (Exception e) {
			logger.error("Error generating execution report: " + e.getMessage());
		}
	}
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy