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

com.shaft.tools.io.internal.ReportManagerHelper Maven / Gradle / Ivy

Go to download

SHAFT is a unified test automation engine. Powered by best-in-class frameworks, SHAFT provides a wizard-like syntax to drive your automation efficiently, maximize your ROI, and minimize your learning curve. Stop reinventing the wheel. Upgrade now!

There is a newer version: 8.2.20240402
Show newest version
package com.shaft.tools.io.internal;

import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.MediaEntityBuilder;
import com.aventstack.extentreports.markuputils.CodeLanguage;
import com.aventstack.extentreports.markuputils.MarkupHelper;
import com.aventstack.extentreports.reporter.ExtentSparkReporter;
import com.aventstack.extentreports.reporter.configuration.Theme;
import com.aventstack.extentreports.reporter.configuration.ViewName;
import com.shaft.api.RestActions;
import com.shaft.cli.FileActions;
import com.shaft.cli.TerminalActions;
import com.shaft.driver.SHAFT;
import com.shaft.listeners.CucumberFeatureListener;
import com.shaft.properties.internal.PropertyFileManager;
import com.shaft.tools.internal.support.JavaHelper;
import com.shaft.tools.io.ReportManager;
import io.qameta.allure.Allure;
import io.qameta.allure.Step;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.StatusDetails;
import lombok.Getter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
import org.testng.ITestResult;
import org.testng.Reporter;

import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@SuppressWarnings("unused")
public class ReportManagerHelper {
    private static final String TIMESTAMP_FORMAT = "dd-MM-yyyy HH:mm:ss.SSSS aaa";
    @Getter
    private static final ExtentReports extentReport = new ExtentReports();
    private static final String SHAFT_ENGINE_VERSION_PROPERTY_NAME = "shaftEngineVersion";
    private static final String ALLURE_VERSION_PROPERTY_NAME = "allureVersion";
    private static final String REPORT_MANAGER_PREFIX = "[ReportManager] ";
    private static final String SHAFT_ENGINE_LOGS_ATTACHMENT_TYPE = "SHAFT Engine Logs";
    private static final String allureExtractionLocation = System.getProperty("user.home") + File.separator + ".m2"
            + File.separator + "repository" + File.separator + "allure" + File.separator;
    private static String issuesLog = "";
    private static int issueCounter = 1;
    private static boolean discreteLogging = false;
    private static int totalNumberOfTests = 0;
    private static int testCasesCounter = 0;
    private static boolean debugMode = false;
    private static int openIssuesForFailedTestsCounter = 0;
    private static int openIssuesForPassedTestsCounter = 0;
    private static int failedTestsWithoutOpenIssuesCounter = 0;
    private static String allureResultsFolderPath = "";
    private static String allureBinaryPath = "";
    // TODO: refactor to regular class that can be instantiated within the test and
    private static List> listOfOpenIssuesForFailedTests = new ArrayList<>();
    private static List> listOfOpenIssuesForPassedTests = new ArrayList<>();
    private static List> listOfNewIssuesForFailedTests = new ArrayList<>();
    private static String featureName = "";

    private static String extentReportsFolderPath = "";
    private static final ThreadLocal extentTest = new ThreadLocal<>();
    private static Logger logger;
    @Getter
    private static String extentReportFileName = "";
    private static boolean generateExtentReports = true;

    private ReportManagerHelper() {
        throw new IllegalStateException("Utility class");
    }

    public static void setOpenIssuesForFailedTestsCounter(int openIssuesForFailedTestsCounter) {
        ReportManagerHelper.openIssuesForFailedTestsCounter = openIssuesForFailedTestsCounter;
    }

    public static void setOpenIssuesForPassedTestsCounter(int openIssuesForPassedTestsCounter) {
        ReportManagerHelper.openIssuesForPassedTestsCounter = openIssuesForPassedTestsCounter;
    }

    public static void setFailedTestsWithoutOpenIssuesCounter(int failedTestsWithoutOpenIssuesCounter) {
        ReportManagerHelper.failedTestsWithoutOpenIssuesCounter = failedTestsWithoutOpenIssuesCounter;
    }

    public static void setListOfOpenIssuesForFailedTests(List> listOfOpenIssuesForFailedTests) {
        ReportManagerHelper.listOfOpenIssuesForFailedTests = listOfOpenIssuesForFailedTests;
    }

    public static void setListOfOpenIssuesForPassedTests(List> listOfOpenIssuesForPassedTests) {
        ReportManagerHelper.listOfOpenIssuesForPassedTests = listOfOpenIssuesForPassedTests;
    }

    public static void setListOfNewIssuesForFailedTests(List> listOfNewIssuesForFailedTests) {
        ReportManagerHelper.listOfNewIssuesForFailedTests = listOfNewIssuesForFailedTests;
    }

    public static int getIssueCounter() {
        return (issueCounter - 1);
    }
    public static int getFailedTestsWithoutOpenIssuesCounter() {
        return failedTestsWithoutOpenIssuesCounter;
    }
    public static int getOpenIssuesForPassedTestsCounter() {
        return openIssuesForPassedTestsCounter;
    }
    public static int getOpenIssuesForFailedTestsCounters() {
        return openIssuesForFailedTestsCounter;
    }

    public static void logIssue(String issue) {
        if (issuesLog.trim().equals("")) {
            issuesLog += issueCounter + ", " + issue.trim();
        } else {
            issuesLog += System.lineSeparator() + issueCounter + ", " + issue.trim();
        }
        issueCounter++;
    }

    public static String prepareIssuesLog() {
        if (!listOfNewIssuesForFailedTests.isEmpty()) {
            listOfNewIssuesForFailedTests.forEach(issue -> logIssue("Test Method '" + issue.get(0) + "." + issue.get(1)
                    + "' failed. Please investigate and open a new Issue if needed.\n"));
        }
        if (!listOfOpenIssuesForPassedTests.isEmpty()) {
            listOfOpenIssuesForPassedTests.forEach(issue -> {
                if (issue.get(3)!=null && !issue.get(3).trim().equals("")) {
                    logIssue("Test Method '" + issue.get(0) + "." + issue.get(1)
                            + "' passed. Please validate and close this open issue '" + issue.get(2) + "': '"
                            + issue.get(3) + "'.\n");
                } else {
                    logIssue("Test Method '" + issue.get(0) + "." + issue.get(1)
                            + "' passed. Please validate and close this open issue '" + issue.get(2) + "'.\n");
                }

            });
        }
        if (!listOfOpenIssuesForFailedTests.isEmpty()) {
            listOfOpenIssuesForFailedTests.forEach(issue -> {
                if (!issue.get(3).trim().equals("")) {
                    logIssue("Test Method '" + issue.get(0) + "." + issue.get(1) + "' failed with open issue '"
                            + issue.get(2) + "': '" + issue.get(3) + "'.\n");
                } else {
                    logIssue("Test Method '" + issue.get(0) + "." + issue.get(1) + "' failed with open issue '"
                            + issue.get(2) + "'.\n");
                }
            });
        }

        if (!issuesLog.trim().equals("")) {
            return "Issue Summary: Total Issues = " + (issueCounter - 1) + ", New issues for Failed Tests = "
                    + failedTestsWithoutOpenIssuesCounter + ", Open issues for Passed Tests = "
                    + openIssuesForPassedTestsCounter + ", Open issues for Failed Tests = "
                    + openIssuesForFailedTestsCounter + ". Kindly check the attached Issue details.";
        } else {
            return "";
        }
    }

    /**
     * @return the discreteLogging
     */
    public static boolean getDiscreteLogging() {
        return discreteLogging;
    }

    /**
     * @param discreteLogging the discreteLogging to set
     */
    public static void setDiscreteLogging(boolean discreteLogging) {
        if (debugMode) {
            ReportManager.logDiscrete("Setting discrete logging to: '" + discreteLogging + "'");
        }
        ReportManagerHelper.discreteLogging = discreteLogging;
    }

    public static int getTotalNumberOfTests() {
        return totalNumberOfTests;
    }

    public static void setTotalNumberOfTests(int totalNumberOfTests) {
        ReportManagerHelper.totalNumberOfTests = totalNumberOfTests;
    }

    public static void setDebugMode(Boolean debugMode) {
        ReportManagerHelper.debugMode = debugMode;
    }

    public static void initializeAllureReportingEnvironment() {
        ReportManager.logDiscrete("Initializing Allure Reporting Environment...");
        ReportHelper.disableLogging();
        allureResultsFolderPath = SHAFT.Properties.paths.allureResults();
        cleanAllureResultsDirectory();
        downloadAndExtractAllureBinaries();
        writeGenerateReportShellFilesToProjectDirectory();
        writeEnvironmentVariablesToAllureResultsDirectory();
        ReportHelper.enableLogging();
    }

    private static void initializeLogger() {
        Configurator.initialize(null, PropertyFileManager.getCUSTOM_PROPERTIES_FOLDER_PATH() + "/log4j2.properties");
        logger = LogManager.getLogger(ReportManager.class.getName());
    }

    public static void logEngineVersion() {
        if (logger == null) {
            initializeLogger();
        }
        System.setOut(new PrintStream(new LogRedirector(logger, Level.INFO)));
        System.setErr(new PrintStream(new LogRedirector(logger, Level.WARN)));
        String engineVersion = "Powered by " + SHAFT.Properties.internal.shaftEngineVersion();
        createImportantReportEntry(engineVersion);
    }

    public static void logEngineClosure() {
        String copyrights = "This test run was powered by "
                + SHAFT.Properties.internal.shaftEngineVersion() + "\n"
                + "SHAFT Engine is *and will always be* 100% FREE for commercial and private use\n"
                + "in compliance with the MIT license\n"
                + "https://github.com/ShaftHQ/SHAFT_ENGINE/blob/master/LICENSE";
        createImportantReportEntry(copyrights);
    }

    public static void logTestInformation(String className, String testMethodName,
                                          String testDescription) {
        testCasesCounter++;
        StringBuilder reportMessage = new StringBuilder();

        if (totalNumberOfTests > 0) {
            reportMessage.append("Starting Execution: ");
            reportMessage.append("'");
            reportMessage.append(testCasesCounter);
            reportMessage.append(" out of ");
            reportMessage.append(totalNumberOfTests);
            reportMessage.append("' test cases in the current suite");
        } else {
            //it should never be ZERO
            reportMessage.append("Starting Dynamic Test Suite Execution: ");
        }
        reportMessage.append("\nTest Method: '").append(className).append(".").append(testMethodName).append("'");

        if (!testDescription.equals("")) {
            reportMessage.append("\nTest Description: '").append(testDescription).append("'");
        }

        createImportantReportEntry(reportMessage.toString());
    }

    public static void logScenarioInformation(String keyword, String name, String steps) {
        testCasesCounter++;
        createImportantReportEntry("Starting Execution: '" + testCasesCounter + " out of " + totalNumberOfTests
                + "' scenarios in the '" + featureName + "' feature"
                + "\n" + keyword + " Name: '" + name
                + "'\n" + keyword + " Steps:\n" + steps);
    }

    public static void logConfigurationMethodInformation(String className, String testMethodName, String configurationMethodType) {
        // In TestNG Reporter, this log entry is logged at the end of the previous test
        // (or null for the first test)
        createImportantReportEntry("Starting execution of " + JavaHelper.convertToSentenceCase(configurationMethodType).toLowerCase() + " configuration method\n'"
                + className + "." + testMethodName + "'");
    }

    public static void logExecutionSummary(String total, String passed, String failed, String skipped) {
        String copyrights = "Test Execution Summary Results" + "\n"
                + "Total Cases: " + total + " --> Passed: " + passed + " | Failed: " + failed + " | Skipped: " + skipped;
        createImportantReportEntry(copyrights);
    }

    public static String formatStackTraceToLogEntry(Throwable t) {
        return formatStackTraceToLogEntry(t, false);
    }


    /**
     * Adds a new attachment using the input parameters provided. The attachment is
     * displayed as a step in the execution report. Used for Screenshots.
     *
     * @param attachmentType    the type of this attachment
     * @param attachmentName    the name of this attachment
     * @param attachmentContent the content of this attachment
     */
    public static void attach(String attachmentType, String attachmentName, InputStream attachmentContent) {
        createAttachment(attachmentType, attachmentName, attachmentContent);
    }

    /**
     * Adds a new attachment using the input parameters provided. The attachment is
     * displayed as a step in the execution report. Used for Screenshots.
     *
     * @param attachmentType    the type of this attachment
     * @param attachmentName    the name of this attachment
     * @param attachmentContent the content of this attachment
     */
    public static void attach(String attachmentType, String attachmentName, String attachmentContent) {
        if (!attachmentContent.trim().equals("")) {
            createAttachment(attachmentType, attachmentName, new ByteArrayInputStream(attachmentContent.getBytes()));
        }
    }

    public static void attach(List screenshot) {
        attach((String) screenshot.get(0), (String) screenshot.get(1), (InputStream) screenshot.get(2));
    }

    /**
     * Returns the log of the current test, and attaches it in the end of the test
     * execution report.
     *
     * @param currentMethodName name of the current test method to be used in the attachment name
     * @param testLog           content of the text log to be used as the attachment value
     */
    public static void attachTestLog(String currentMethodName, String testLog) {
        if (!testLog.isBlank()) {
            createAttachment(SHAFT_ENGINE_LOGS_ATTACHMENT_TYPE, "Current Method log: " + currentMethodName,
                    new ByteArrayInputStream(testLog.getBytes()));
        }
    }

    public static void attachEngineLog(String executionEndTimestamp) {
        if (!SHAFT.Properties.reporting.disableLogging()) {
            String engineLogCreated = "Successfully created attachment '" + SHAFT_ENGINE_LOGS_ATTACHMENT_TYPE + " - "
                    + "Execution log" + "'";
            var initialLoggingState = ReportManagerHelper.getDiscreteLogging();
            ReportManagerHelper.setDiscreteLogging(true);
            createLogEntry(engineLogCreated, true);
            byte[] engineLog = new byte[0];
            try {
                engineLog = FileActions.getInstance().readFileAsByteArray(System.getProperty("appender.file.fileName"));
                FileActions.getInstance().deleteFile(System.getProperty("appender.file.fileName"));
            } catch (Exception throwable) {
                logDiscrete(throwable);
            }
            ReportManagerHelper.setDiscreteLogging(initialLoggingState);
            createAttachment(SHAFT_ENGINE_LOGS_ATTACHMENT_TYPE, "Execution log: " + executionEndTimestamp,
                    new ByteArrayInputStream(engineLog));
        }
    }

    public static void attachIssuesLog(String executionEndTimestamp) {
        String issueSummary = prepareIssuesLog();
        if (!issuesLog.trim().equals("")) {
            log(issueSummary,
                    Collections.singletonList(
                            Arrays.asList(SHAFT_ENGINE_LOGS_ATTACHMENT_TYPE, "Issues log CSV: " + executionEndTimestamp,
                                    new ByteArrayInputStream(issuesLog.trim().getBytes()))));
        }
    }

    public static void openAllureReportAfterExecution() {
        String commandToOpenAllureReport;
        if (Boolean.TRUE.equals(SHAFT.Properties.reporting.openAllureReportAfterExecution())) {
            if (SystemUtils.IS_OS_WINDOWS) {
                commandToOpenAllureReport = ("generate_allure_report.bat");
            } else {
                commandToOpenAllureReport = ("sh generate_allure_report.sh");
            }
            TerminalActions.getInstance(true, true).performTerminalCommand(commandToOpenAllureReport);
        }
    }

    public static void generateAllureReportArchive() {
        if (Boolean.TRUE.equals(SHAFT.Properties.reporting.generateAllureReportArchive())) {
            ReportManager.logDiscrete("Generating Allure Report Archive...");
            ReportHelper.disableLogging();
            writeOpenReportShellFilesToGeneratedDirectory();
            writeAllureReportToGeneratedDirectory();
            createAllureReportArchiveAndCleanGeneratedDirectory();
            ReportHelper.enableLogging();
        }
    }

    public static String getCallingMethodFullName() {
        StackTraceElement[] callingStack = Thread.currentThread().getStackTrace();
        var callingMethodFullName = new StringBuilder();
        for (var i = 1; i < callingStack.length; i++) {
            if (!callingStack[i].getClassName().contains("shaft")) {
                callingMethodFullName.append(callingStack[i].getClassName());
                if (!callingStack[i].getMethodName().isEmpty()) {
                    callingMethodFullName.append(".");
                    callingMethodFullName.append(callingStack[i].getMethodName());
                }
                break;
            }
        }
        return callingMethodFullName.toString();
    }

    public static String getTestClassName() {
        return Reporter.getCurrentTestResult().getMethod().getTestClass().getName();
    }

    public static String getTestMethodName() {
        if (Reporter.getCurrentTestResult() != null) {
            return Reporter.getCurrentTestResult().getMethod().getMethodName();
        } else {
            // this happens when running a cucumber feature file directly because there is no testNG Reporter instance
            return JavaHelper.removeSpecialCharacters(CucumberFeatureListener.getLastStartedScenarioName());
        }
    }

    public static void setTestCaseName(String scenarioName) {
        Allure.getLifecycle().updateTestCase(testResult -> testResult.setName(scenarioName));
        if (!"".equals(featureName)) {
            Allure.getLifecycle().updateTestCase(testResult -> testResult.setFullName(featureName + ": " + scenarioName));
        }
    }

    public static void setTestCaseDescription(String scenarioSteps) {
        if (scenarioSteps.contains("و")) {
            Allure.getLifecycle().updateTestCase(testResult -> testResult.setDescriptionHtml("

" + scenarioSteps + "

")); } else { Allure.getLifecycle().updateTestCase(testResult -> testResult.setDescriptionHtml("

" + scenarioSteps + "

")); } } public static Boolean isCurrentTestPassed() { if (Reporter.getCurrentTestResult() != null) { return Reporter.getCurrentTestResult().isSuccess(); } else { // this happens in case of native cucumber execution without TestNG Test Runner return CucumberFeatureListener.getIsLastFinishedStepOK(); } } public static void setFeatureName(String featureName) { ReportManagerHelper.featureName = featureName; } public static void initializeExtentReportingEnvironment() { generateExtentReports = SHAFT.Properties.reporting.generateExtentReports(); if (generateExtentReports) { ReportManager.logDiscrete("Initializing Extent Reporting Environment..."); ReportHelper.disableLogging(); extentReportsFolderPath = SHAFT.Properties.paths.extentReports(); cleanExtentReportsDirectory(); extentReportFileName = extentReportsFolderPath + "ExtentReports_" + (new SimpleDateFormat("dd-MM-yyyy_HH-mm-ss-SSSS-aaa")).format(System.currentTimeMillis()) + ".html"; var spark = new ExtentSparkReporter(extentReportFileName) .viewConfigurer() .viewOrder() .as(new ViewName[]{ViewName.DASHBOARD, ViewName.TEST, ViewName.EXCEPTION, ViewName.LOG}) .apply(); spark.config().setTheme(Theme.STANDARD); spark.config().setDocumentTitle("Extent Reports"); spark.config().setReportName("Extent Reports - Powered by SHAFT_Engine"); extentReport.attachReporter(spark); ReportHelper.enableLogging(); } } private static void cleanExtentReportsDirectory() { if (SHAFT.Properties.reporting.cleanExtentReportsDirectoryBeforeExecution()) { FileActions.getInstance().deleteFolder(extentReportsFolderPath.substring(0, extentReportsFolderPath.length() - 1)); } } public static void extentReportsReset() { extentTest.remove(); } public static void extentReportsCreateTest(String testName, String testDescription) { if (!extentReport.equals(new ExtentReports())) { if (testDescription.equals("")) { extentTest.set(extentReport.createTest(testName)); } else { extentTest.set(extentReport.createTest(testDescription)); } } } public static void extentReportsPass(String message) { if (generateExtentReports && extentTest.get()!=null) { extentTest.get().pass(message); } } public static void extentReportsFail(String message) { if (generateExtentReports && extentTest.get()!=null) { extentTest.get().fail(message); } } public static void extentReportsFail(Throwable t) { if (generateExtentReports && extentTest.get()!=null) { extentTest.get().fail(t); } } public static void extentReportsSkip(String message) { if (generateExtentReports && extentTest.get()!=null) { extentTest.get().skip(message); } } public static void extentReportsSkip(Throwable t) { if (generateExtentReports && extentTest.get()!=null) { extentTest.get().skip(t); } } public static void extentReportsFlush() { if (generateExtentReports) { extentReport.flush(); } } private static String formatStackTraceToLogEntry(Throwable t, boolean isCause) { var logBuilder = new StringBuilder(); if (t != null) { StackTraceElement[] trace = t.getStackTrace(); if (isCause) { logBuilder.append(System.lineSeparator()).append("Caused by: "); } logBuilder.append(t.getClass().getName()).append(":").append(" ").append(t.getMessage()).append(System.lineSeparator()); for (StackTraceElement stackTraceElement : trace) { logBuilder.append(" ").append(stackTraceElement.toString()).append(System.lineSeparator()); } logBuilder.append(formatStackTraceToLogEntry(t.getCause(), true)); } return logBuilder.toString(); } public static void createLogEntry(String logText, Level loglevel) { if (SHAFT.Properties.reporting != null && !SHAFT.Properties.reporting.disableLogging()) { String timestamp = (new SimpleDateFormat(TIMESTAMP_FORMAT)).format(new Date(System.currentTimeMillis())); if (logText == null) { logText = "null"; } String log = REPORT_MANAGER_PREFIX + logText.trim() + " @" + timestamp; Reporter.log(log, false); if (logger == null) { initializeLogger(); } logger.log(loglevel, logText.trim()); } } private static void createLogEntry(String logText, boolean addToConsoleLog) { if (!SHAFT.Properties.reporting.disableLogging()) { String timestamp = (new SimpleDateFormat(TIMESTAMP_FORMAT)).format(new Date(System.currentTimeMillis())); if (logText == null) { logText = "null"; } String log = REPORT_MANAGER_PREFIX + logText.trim() + " @" + timestamp; Reporter.log(log, false); if (extentTest.get() != null && !logText.contains("created attachment") && !logText.contains(" { var trailingSpacing = ""; var spaces = Math.round((float) (144 - line.trim().length()) / 2); if (spaces > 0) { lineByLine.append(" ".repeat(spaces)); trailingSpacing = lineByLine.toString(); } lineByLine.append(line); lineByLine.append(trailingSpacing); augmentedText.append(lineByLine); augmentedText.append(System.lineSeparator()); lineByLine.delete(0, lineByLine.length()); }); return augmentedText.toString(); } private static String createSeparator(@SuppressWarnings("SameParameterValue") char ch) { return String.valueOf(ch).repeat(144); } private static void createImportantReportEntry(String logText) { boolean initialLoggingStatus = discreteLogging; setDiscreteLogging(false); // force log even if discrete logging was turned on String log = System.lineSeparator() + createSeparator('-') + addSpacing(logText.trim()) + createSeparator('-') + System.lineSeparator(); Reporter.log(log, false); if (logger == null) { initializeLogger(); } logger.log(Level.INFO, log); setDiscreteLogging(initialLoggingStatus); } /** * Formats logText and adds timestamp, then logs it as a step in the execution * report. * * @param logText the text that needs to be logged in this action */ // @Step("{logText}") public static void writeStepToReport(String logText) { if (!SHAFT.Properties.reporting.disableLogging()) { createLogEntry(logText, true); Allure.step(logText, getAllureStepStatus(logText)); } } private static Status getAllureStepStatus(String logText) { if (logText != null && logText.toLowerCase().contains("failed")) { return Status.FAILED; } if (Reporter.getCurrentTestResult() != null) { var testNgStatus = Reporter.getCurrentTestResult().getStatus(); return switch (testNgStatus) { case ITestResult.FAILURE -> Status.FAILED; case ITestResult.SKIP -> Status.SKIPPED; default -> Status.PASSED; }; } else { return Status.PASSED; } } @Step("{logText}") static void writeStepToReport(String logText, List> attachments, CheckpointStatus status) { createLogEntry(logText, false); if (attachments != null && !attachments.isEmpty()) { attachments.forEach(attachment -> { if (attachment != null && !attachment.isEmpty() && attachment.get(2).getClass().toString().toLowerCase().contains("string") && !attachment.get(2).getClass().toString().contains("StringInputStream")) { if (!attachment.get(2).toString().isEmpty()) { attach(attachment.get(0).toString(), attachment.get(1).toString(), attachment.get(2).toString()); } } else if (attachment != null && !attachment.isEmpty()) { if (attachment.get(2) instanceof byte[]) { attach(attachment.get(0).toString(), attachment.get(1).toString(), new ByteArrayInputStream((byte[]) attachment.get(2))); } else { attach(attachment.get(0).toString(), attachment.get(1).toString(), (InputStream) attachment.get(2)); } } if (status.equals(CheckpointStatus.FAIL)) { Allure.getLifecycle().updateStep(update -> { update.setStatus(Status.FAILED); if (attachment != null && !attachment.isEmpty() && attachment.get(2) != null) { String trace = update.getStatusDetails() == null ? attachment.get(2).toString() : update.getStatusDetails().getTrace() + System.lineSeparator() + attachment.get(2).toString(); StatusDetails details = update.getStatusDetails() == null ? new StatusDetails() : update.getStatusDetails(); details.setTrace(trace.trim()); update.setStatusDetails(details); } }); } }); } } private static void createAttachment(String attachmentType, String attachmentName, InputStream attachmentContent) { if (attachmentContent != null) { var byteArrayOutputStream = new ByteArrayOutputStream(); try { attachmentContent.transferTo(byteArrayOutputStream); } catch (IOException e) { var error = "Error while creating Attachment"; if (logger == null) { initializeLogger(); } logger.info(error, e); Reporter.log(error, false); } String attachmentDescription = attachmentType + " - " + attachmentName; attachBasedOnFileType(attachmentType, attachmentName, byteArrayOutputStream, attachmentDescription); logAttachmentAction(attachmentType, attachmentName, byteArrayOutputStream); } } @SuppressWarnings("SpellCheckingInspection") private static void attachBasedOnFileType(String attachmentType, String attachmentName, ByteArrayOutputStream attachmentContent, String attachmentDescription) { var content = new ByteArrayInputStream(attachmentContent.toByteArray()); if (attachmentType.toLowerCase().contains("screenshot")) { Allure.addAttachment(attachmentDescription, "image/png", content, ".png"); attachImageToExtentReport("image/png", new ByteArrayInputStream(attachmentContent.toByteArray())); } else if (attachmentType.toLowerCase().contains("recording")) { Allure.addAttachment(attachmentDescription, "video/mp4", content, ".mp4"); } else if (attachmentType.toLowerCase().contains("gif")) { Allure.addAttachment(attachmentDescription, "image/gif", content, ".gif"); attachImageToExtentReport("image/gif", new ByteArrayInputStream(attachmentContent.toByteArray())); } else if (attachmentType.toLowerCase().contains("csv") || attachmentName.toLowerCase().contains("csv")) { Allure.addAttachment(attachmentDescription, "text/csv", content, ".csv"); attachCodeBlockToExtentReport("text/csv", new ByteArrayInputStream(attachmentContent.toByteArray())); } else if (attachmentType.toLowerCase().contains("xml") || attachmentName.toLowerCase().contains("xml")) { Allure.addAttachment(attachmentDescription, "text/xml", content, ".xml"); attachCodeBlockToExtentReport("text/xml", new ByteArrayInputStream(attachmentContent.toByteArray())); } else if (attachmentType.toLowerCase().contains("excel") || attachmentName.toLowerCase().contains("excel")) { Allure.addAttachment(attachmentDescription, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content, ".xlsx"); } else if (attachmentType.toLowerCase().contains("json") || attachmentName.toLowerCase().contains("json")) { Allure.addAttachment(attachmentDescription, "text/json", content, ".json"); attachCodeBlockToExtentReport("text/json", new ByteArrayInputStream(attachmentContent.toByteArray())); } else if (attachmentType.toLowerCase().contains("properties")) { Allure.addAttachment(attachmentDescription, "text/plain", content, ".properties"); } else if (attachmentType.toLowerCase().contains("link")) { Allure.addAttachment(attachmentDescription, "text/uri-list", content, ".uri"); } else if (attachmentType.toLowerCase().contains("engine logs")) { Allure.addAttachment(attachmentDescription, "text/plain", content, ".txt"); } else if (attachmentType.toLowerCase().contains("page snapshot")) { Allure.addAttachment(attachmentDescription, "multipart/related", content, ".mhtml"); } else if (attachmentType.toLowerCase().contains("html")) { Allure.addAttachment(attachmentDescription, "text/html", content, ".html"); } else { Allure.addAttachment(attachmentDescription, content); } } private static void logAttachmentAction(String attachmentType, String attachmentName, ByteArrayOutputStream attachmentContent) { createLogEntry("Successfully created attachment '" + attachmentType + " - " + attachmentName + "'", Level.INFO); if (debugMode && !attachmentType.contains(SHAFT_ENGINE_LOGS_ATTACHMENT_TYPE) && !attachmentType.equalsIgnoreCase("Selenium WebDriver Logs") && !attachmentType.toLowerCase().contains("screenshot") && !attachmentType.toLowerCase().contains("recording") && !attachmentType.toLowerCase().contains("gif") && !attachmentType.toLowerCase().contains("engine logs")) { String timestamp = (new SimpleDateFormat(TIMESTAMP_FORMAT)).format(new Date(System.currentTimeMillis())); String theString; var br = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(attachmentContent.toByteArray()), StandardCharsets.UTF_8)); theString = br.lines().collect(Collectors.joining(System.lineSeparator())); if (!theString.isEmpty()) { String logEntry = REPORT_MANAGER_PREFIX + "Debugging Attachment Entry" + " @" + timestamp + System.lineSeparator() + theString + System.lineSeparator(); if (logger == null) { initializeLogger(); } logger.info(logEntry); } } } private static void attachCodeBlockToExtentReport(String attachmentType, InputStream attachmentContent) { if (extentTest.get() !=null) { try { var codeBlock = IOUtils.toString(attachmentContent, StandardCharsets.UTF_8); switch (attachmentType) { case "text/json" -> extentTest.get().info(MarkupHelper.createCodeBlock(codeBlock, CodeLanguage.JSON)); case "text/xml" -> extentTest.get().info(MarkupHelper.createCodeBlock(codeBlock, CodeLanguage.XML)); default -> extentTest.get().info(MarkupHelper.createCodeBlock(codeBlock)); } } catch (IOException e) { ReportManager.logDiscrete("Failed to attach code block to extentReport."); } } } private static void attachImageToExtentReport(String attachmentType, InputStream attachmentContent) { if (extentTest.get() !=null) { try { var image = Base64.getEncoder().encodeToString(IOUtils.toByteArray(attachmentContent)); if (attachmentType.toLowerCase().contains("gif")) { extentTest.get().addScreenCaptureFromBase64String(image); } else { extentTest.get().info(MediaEntityBuilder.createScreenCaptureFromBase64String(image).build()); } } catch (IOException e) { ReportManager.logDiscrete("Failed to attach screenshot to extentReport."); } } } private static void cleanAllureResultsDirectory() { // clean allure-results directory before execution if (SHAFT.Properties.reporting.cleanAllureResultsDirectoryBeforeExecution()) { try { FileActions.getInstance().deleteFolder(allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1)); } catch (Exception t) { ReportManager.log("Failed to delete allure-results as it is currently open. Kindly restart your device to unlock the directory."); } } } private static void writeEnvironmentVariablesToAllureResultsDirectory() { // reads all environment variables and then formats and writes them to be read // by the Allure report var props = System.getProperties(); var propertiesFileBuilder = new StringBuilder(); propertiesFileBuilder.append(""); // read properties from any explicit properties files for (var i = 0; i < props.size(); i++) { String propertyKey = ((String) (props.keySet().toArray())[i]).trim(); String propertyValue = props.getProperty(propertyKey).trim(); // excluding empty values, system properties (all system properties have "." in // their names), and any git branch issues if (!propertyValue.equals("") && !propertyValue.contains("==") && !propertyKey.contains(">>>") && !propertyKey.contains("<<<")) { if (propertyValue.contains("&")) { propertyValue = propertyValue.replace("&", "&"); } String parameter = "" + "" + propertyKey + "" + "" + propertyValue + "" + ""; if (propertyKey.equals(SHAFT_ENGINE_VERSION_PROPERTY_NAME)) { // there's an open issue, when fixed this will be displayed properly // https://github.com/allure-framework/allure2/issues/382 propertiesFileBuilder.insert(13, parameter); } else { propertiesFileBuilder.append(parameter); } } } propertiesFileBuilder.append(""); FileActions.getInstance().writeToFile(SHAFT.Properties.paths.allureResults(), "environment.xml", RestActions.formatXML(propertiesFileBuilder.toString())); } private static void downloadAndExtractAllureBinaries() { // extract allure from jar file to src/main/resources directory if it doesn't // already exist String allureVersion = SHAFT.Properties.internal.allureVersion(); allureBinaryPath = allureExtractionLocation + "allure-" + allureVersion + File.separator + "bin" + File.separator + "allure"; if (!FileActions.getInstance().doesFileExist(allureBinaryPath)) { try { FileActions.getInstance().deleteFolder(allureExtractionLocation); } catch (AssertionError e) { ReportManager.logDiscrete("Couldn't clear the allure extraction directory. Kindly terminate any running java process or restart your machine to fix this issue."); ReportManagerHelper.logDiscrete(e); } // download allure binary URL allureArchive = FileActions.getInstance().downloadFile( "https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/" + allureVersion + "/allure-commandline-" + allureVersion + ".zip", "target" + File.separator + "allureBinary.zip"); FileActions.getInstance().unpackArchive(allureArchive, allureExtractionLocation); // extract allure from SHAFT_Engine jar URL allureSHAFTConfigArchive = ReportManagerHelper.class.getResource("/resources/allure/allureBinary_SHAFTEngineConfigFiles.zip"); FileActions.getInstance().unpackArchive(allureSHAFTConfigArchive, allureExtractionLocation + "allure-" + allureVersion + File.separator); if (!SystemUtils.IS_OS_WINDOWS) { // make allure executable on Unix-based shells TerminalActions.getInstance(false, false).performTerminalCommand("chmod u+x " + allureBinaryPath); } } } private static void writeGenerateReportShellFilesToProjectDirectory() { String allureVersion = SHAFT.Properties.internal.allureVersion(); // create generate_allure_report.sh or generate_allure_report.bat List commandsToServeAllureReport; if (SystemUtils.IS_OS_WINDOWS) { // create windows batch file commandsToServeAllureReport = Arrays.asList("@echo off", ":: If you already have a valid JAVA_HOME environment variable set, feel free to comment the below two lines", "set JAVA_HOME=" + System.getProperty("java.home"), "set path=%JAVA_HOME%\\bin;%path%", "set path=" + allureExtractionLocation + "allure-" + allureVersion + "\\bin;%path%", "allure serve " + allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1) + " -h localhost", "pause", "exit"); FileActions.getInstance().writeToFile("", "generate_allure_report.bat", commandsToServeAllureReport); } else { // create Unix-based sh file commandsToServeAllureReport = Arrays .asList("#!/bin/bash", "parent_path=$( cd \"$(dirname \"${BASH_SOURCE[0]}\")\" ; pwd -P )", "cd '" + allureExtractionLocation + "allure-" + allureVersion + "/bin/'", "bash allure serve $parent_path'/" + allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1) + "'" + " -h localhost", "exit" ); FileActions.getInstance().writeToFile("", "generate_allure_report.sh", commandsToServeAllureReport); // make allure executable on Unix-based shells TerminalActions.getInstance(false, false).performTerminalCommand("chmod u+x generate_allure_report.sh"); } } public static boolean isInternalStep() { var callingMethodName = (new Throwable()).getStackTrace()[2].toString(); return callingMethodName.contains("shaft"); } /** * Adds a new attachment using the input parameters provided. The attachment is * displayed as a step in the execution report. Used for Screenshots. * * @param attachmentType the type of this attachment * @param attachmentName the name of this attachment * @param attachmentContent the content of this attachment */ @Step("Attachment: {attachmentType} - {attachmentName}") public static void attachAsStep(String attachmentType, String attachmentName, InputStream attachmentContent) { createAttachment(attachmentType, attachmentName, attachmentContent); } private static void writeOpenReportShellFilesToGeneratedDirectory() { List commandsToOpenAllureReport; // create Unix-based sh file commandsToOpenAllureReport = Arrays.asList("#!/bin/bash", "parent_path=$( cd '$(dirname '${BASH_SOURCE[0]}')' ; pwd -P )", "cd '$parent_path/allure/allure-" + SHAFT.Properties.internal.allureVersion() + "/bin/'", "bash allure serve '$parent_path/allure-results'", "exit"); FileActions.getInstance().writeToFile("generatedReport/", "open_allure_report.sh", commandsToOpenAllureReport); // create windows batch file commandsToOpenAllureReport = Arrays.asList("@echo off", ":: If you're using a portable Java version, uncomment the below two lines and update them to use the correct path", ":: set JAVA_HOME=" + System.getProperty("java.home"), ":: set path=%JAVA_HOME%\\bin;%path%", "set path=allure\\allure-" + SHAFT.Properties.internal.allureVersion() + "\\bin;%path%", "allure serve allure-results", "pause", "exit"); FileActions.getInstance().writeToFile("generatedReport/", "open_allure_report.bat", commandsToOpenAllureReport); } private static void writeAllureReportToGeneratedDirectory() { // add correct file extension based on target OS String commandToCreateAllureReport; allureBinaryPath = allureExtractionLocation + "allure-" + SHAFT.Properties.internal.allureVersion() + "/bin/allure"; if (SystemUtils.IS_OS_WINDOWS) { commandToCreateAllureReport = allureBinaryPath + ".bat" + " generate '" + allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1) + "' -o 'generatedReport/allure-report'"; } else { commandToCreateAllureReport = allureBinaryPath + " generate " + allureResultsFolderPath.substring(0, allureResultsFolderPath.length() - 1) + " -o generatedReport/allure-report"; } TerminalActions.getInstance(false, false).performTerminalCommand(commandToCreateAllureReport); } private static void createAllureReportArchiveAndCleanGeneratedDirectory() { if (FileActions.getInstance().doesFileExist(allureExtractionLocation)) { FileActions.getInstance().copyFolder(FileActions.getInstance().getAbsolutePath(allureExtractionLocation), "generatedReport/allure"); FileActions.getInstance().copyFolder("allure-results", "generatedReport/allure-results"); FileActions.getInstance().zipFiles("generatedReport/", "generatedReport_" + new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + ".zip"); } FileActions.getInstance().deleteFile("generatedReport/"); } public static void cleanExecutionSummaryReportDirectory() { if (SHAFT.Properties.reporting.cleanSummaryReportsDirectoryBeforeExecution()) { String executionSummaryReportFolderPath = SHAFT.Properties.paths.executionSummaryReport(); FileActions.getInstance().deleteFolder(executionSummaryReportFolderPath.substring(0, executionSummaryReportFolderPath.length() - 1)); } } public static void openExecutionSummaryReportAfterExecution() { if (SHAFT.Properties.reporting.openExecutionSummaryReportAfterExecution()) { if (SystemUtils.IS_OS_WINDOWS) { SHAFT.CLI.terminal().performTerminalCommand(".\\" + SHAFT.Properties.paths.executionSummaryReport() + "ExecutionSummaryReport_*.html"); } else { SHAFT.CLI.terminal().performTerminalCommand("open ./" + SHAFT.Properties.paths.executionSummaryReport() + "ExecutionSummaryReport_*.html"); } } } public static void openExtentReportAfterExecution() { if (SHAFT.Properties.reporting.openExtentReportAfterExecution()) { if (SystemUtils.IS_OS_WINDOWS) { SHAFT.CLI.terminal().performTerminalCommand(".\\" + SHAFT.Properties.paths.extentReports() + "ExtentReports_*.html"); } else { SHAFT.CLI.terminal().performTerminalCommand("open ./" + SHAFT.Properties.paths.extentReports() + "ExtentReports_*.html"); } } } public static void log(String logText, List> attachments) { if (!SHAFT.Properties.reporting.disableLogging()) { if (!logText.toLowerCase().contains("failed") && getDiscreteLogging() && isInternalStep()) { createLogEntry(logText, Level.INFO); if (attachments != null && !attachments.isEmpty() && (attachments.size() > 1 || (attachments.get(0) != null && !attachments.get(0).isEmpty()))) { attachments.forEach(attachment -> { if (attachment != null && !attachment.isEmpty()) { if (attachment.get(2) instanceof String) { attachAsStep(attachment.get(0).toString(), attachment.get(1).toString(), new ByteArrayInputStream(attachment.get(2).toString().getBytes())); } else { attachAsStep(attachment.get(0).toString(), attachment.get(1).toString(), (InputStream) attachment.get(2)); } } }); } } else { if (attachments != null && !attachments.isEmpty() && (attachments.size() > 1 || (attachments.get(0) != null && !attachments.get(0).isEmpty()))) { CheckpointStatus status = (logText.toLowerCase().contains("passed")) ? CheckpointStatus.PASS : CheckpointStatus.FAIL; writeStepToReport(logText, attachments, status); } else { writeStepToReport(logText); } } } } public static void logNestedSteps(String logText, List customLogMessages, List> attachments) { CheckpointStatus status = (logText.toLowerCase().contains("passed")) ? CheckpointStatus.PASS : CheckpointStatus.FAIL; CheckpointType type = (logText.toLowerCase().contains("verification")) ? CheckpointType.VERIFICATION : CheckpointType.ASSERTION; if (type.equals(CheckpointType.VERIFICATION) && status.equals(CheckpointStatus.FAIL) || !SHAFT.Properties.reporting.disableLogging()) { if (customLogMessages != null && customLogMessages.size() > 0 && !"".equals(customLogMessages.get(0).trim())) { String customLogText = customLogMessages.get(0); if (status == CheckpointStatus.PASS) { customLogText = (type == CheckpointType.VERIFICATION) ? "Verification Passed: " + customLogText : "Assertion Passed: " + customLogText; } else { customLogText = (type == CheckpointType.VERIFICATION) ? "Verification Failed: " + customLogText : "Assertion Failed: " + customLogText; } ReportManager.logDiscrete(logText); writeNestedStepsToReport(customLogText, attachments); CheckpointCounter.increment(type, customLogMessages.get(0), status); ExecutionSummaryReport.validationsIncrement(status); } else { if (attachments != null && !attachments.isEmpty() && !attachments.get(0).isEmpty()) { writeStepToReport(logText, attachments, status); } else { writeStepToReport(logText); } CheckpointCounter.increment(type, logText, status); ExecutionSummaryReport.validationsIncrement(status); } } } public static void logNestedSteps(String logText, List> attachments) { writeNestedStepsToReport(logText, attachments); } @Step("{customLog}") private static void writeNestedStepsToReport(String customLog, List> attachments) { createLogEntry(customLog, false); if (attachments != null && !attachments.isEmpty()) { attachments.forEach(attachment -> { if (attachment != null && !attachment.isEmpty() && attachment.get(2).getClass().toString().toLowerCase().contains("string") && !attachment.get(2).getClass().toString().contains("StringInputStream")) { if (!attachment.get(2).toString().isEmpty()) { attach(attachment.get(0).toString(), attachment.get(1).toString(), attachment.get(2).toString()); } } else if (attachment != null && !attachment.isEmpty()) { if (attachment.get(2) instanceof byte[]) { attach(attachment.get(0).toString(), attachment.get(1).toString(), new ByteArrayInputStream((byte[]) attachment.get(2))); } else { attach(attachment.get(0).toString(), attachment.get(1).toString(), (InputStream) attachment.get(2)); } } }); } } /** * Format an exception message and stack trace, and calls attach to add it as a * log entry. * * @param throwable the throwable (exception or error) that will be logged in this * action */ public static void log(Throwable throwable) { String logText; logText = formatStackTraceToLogEntry(throwable); if (throwable.getMessage() != null) { log("An Exception Occurred with this Message: " + throwable.getMessage().split("\n")[0].trim() + ".", Collections.singletonList(Arrays.asList("Exception Stack Trace", throwable.getClass().getName(), logText))); } else { log("An Exception Occurred", Collections.singletonList(Arrays.asList("Exception Stack Trace", throwable.getClass().getName(), logText))); } } public static void logDiscrete(Throwable t) { createLogEntry(formatStackTraceToLogEntry(t), Level.ERROR); } public static void logDiscrete(Throwable t, org.apache.logging.log4j.Level logLevel) { createLogEntry(formatStackTraceToLogEntry(t), logLevel); } /** * Creates a custom log entry that will not be added as a step in the execution report, but you can see it in the attached execution log txt file * * @param logText the text that will be logged by action * @param logLevel Level.ERROR, TRACE, INFO, WARN, DEBUG, FATAL */ public static void logDiscrete(String logText, org.apache.logging.log4j.Level logLevel) { createLogEntry(logText, logLevel); } public static String getExecutionDuration(long startTime, long endTime) { long durationWithMillis = TimeUnit.MILLISECONDS.toMillis(endTime - startTime); String duration = ""; if (durationWithMillis > 0 && durationWithMillis < 1000) { duration = durationWithMillis + " millis"; } else if (durationWithMillis >= 1000 && durationWithMillis < 60000) { duration = String.format("%02d sec, %02d millis", TimeUnit.MILLISECONDS.toSeconds(durationWithMillis), TimeUnit.MILLISECONDS.toMillis(durationWithMillis) - TimeUnit.SECONDS.toMillis(TimeUnit.MILLISECONDS.toSeconds(durationWithMillis)) ); } else if (durationWithMillis >= 60000 && durationWithMillis < 3600000) { duration = String.format("%02d min, %02d sec", TimeUnit.MILLISECONDS.toMinutes(durationWithMillis), TimeUnit.MILLISECONDS.toSeconds(durationWithMillis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(durationWithMillis)) ); } else if (durationWithMillis >= 3600000) { duration = String.format("%02d hr, %02d min", TimeUnit.MILLISECONDS.toHours(durationWithMillis), TimeUnit.MILLISECONDS.toMinutes(durationWithMillis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(durationWithMillis)) ); } return duration; } }