com.seleniumtests.core.SeleniumTestsContextManager.java Source code

Java tutorial

Introduction

Here is the source code for com.seleniumtests.core.SeleniumTestsContextManager.java

Source

/**
 * 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.core;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.testng.ISuite;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.xml.XmlSuite;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.seleniumtests.core.runner.SeleniumRobotTestListener;
import com.seleniumtests.customexception.ConfigurationException;
import com.seleniumtests.driver.TestType;
import com.seleniumtests.util.TestConfigurationParser;
import com.seleniumtests.util.logging.SeleniumRobotLogger;

/**
 * SeleniumTestsContextManager provides ways to manage global context, thread context and test level context.
 */
public class SeleniumTestsContextManager {

    private static final Logger logger = SeleniumRobotLogger.getLogger(SeleniumTestsContext.class);

    private static String rootPath;
    private static String dataPath;
    private static String cachePath;
    private static String appDataPath;
    private static String featuresPath;
    private static String configPath;
    private static String applicationName;
    private static String applicationNameWithVersion;
    private static String applicationVersion;
    private static String coreVersion;
    private static Boolean deployedMode;

    public static final String DATA_FOLDER_NAME = "data";
    public static final String CACHE_FOLDER_NAME = "cache";
    public static final String SELENIUM_VERSION = "3.14.0";

    // global level context
    private static SeleniumTestsContext globalContext;

    // thread level SeleniumTestsContext
    private static ThreadLocal<SeleniumTestsContext> threadLocalContext = new ThreadLocal<>();

    // relationship between a SeleniumTestsContext and a TestNG test (<test> tag in XML)
    private static Map<String, SeleniumTestsContext> testContext = Collections
            .synchronizedMap(new HashMap<String, SeleniumTestsContext>());

    // relationship between a SeleniumTestsContext and a test class
    private static Map<String, SeleniumTestsContext> classContext = Collections
            .synchronizedMap(new HashMap<String, SeleniumTestsContext>());

    // relationship between a SeleniumTestsContext and a test method
    private static Map<String, SeleniumTestsContext> methodContext = Collections
            .synchronizedMap(new HashMap<String, SeleniumTestsContext>());

    private SeleniumTestsContextManager() {
        // As a utility class, it is not meant to be instantiated.
    }

    public static SeleniumTestsContext getGlobalContext() {
        if (globalContext == null) {
            throw new ConfigurationException(
                    "SeleniumTestsContextManager.getGlobalContext() MUST be called after SeleniumTestsContextManager.initGlobalContext()");
        }

        return globalContext;
    }

    public static SeleniumTestsContext getThreadContext() {
        if (threadLocalContext.get() == null) {
            throw new ConfigurationException(
                    "SeleniumTestsContextManager.getThreadContext() MUST be called after SeleniumTestsContextManager.initThreadContext()");
        }

        return threadLocalContext.get();
    }

    public static void initGlobalContext(ISuite suiteContext) {
        if (suiteContext != null) {
            generateApplicationPath(suiteContext.getXmlSuite());
        }

        ITestContext testNGCtx = new DefaultTestNGContext(suiteContext);
        ITestContext newTestNGCtx = getContextFromConfigFile(testNGCtx);
        globalContext = new SeleniumTestsContext(newTestNGCtx);
    }

    public static void initGlobalContext(ITestContext testNGCtx) {

        // generate all paths used by test application
        if (testNGCtx != null && testNGCtx.getCurrentXmlTest() != null) {
            generateApplicationPath(testNGCtx.getCurrentXmlTest().getSuite());
        }

        ITestContext newTestNGCtx = getContextFromConfigFile(testNGCtx);
        globalContext = new SeleniumTestsContext(newTestNGCtx);
    }

    private static String getKeyForMethod(ITestContext testNGCtx, String className, String methodName) {
        return testNGCtx.getName() + "_" + className + "." + methodName;
    }

    private static String getKeyForClass(ITestContext testNGCtx, String className) {
        return testNGCtx.getName() + "_" + className;
    }

    public static SeleniumTestsContext storeTestContext(ITestContext testNGCtx) {
        SeleniumTestsContext tstContext = new SeleniumTestsContext(getContextFromConfigFile(testNGCtx));
        setTestContext(testNGCtx, tstContext);
        return tstContext;
    }

    public static void setTestContext(ITestContext testNGCtx, SeleniumTestsContext tstContext) {
        testContext.put(testNGCtx.getName(), tstContext);
    }

    /**
     * Returns test context if it exists. Else, create a new one
     * @param testNGCtx
     * @return
     */
    public static SeleniumTestsContext getTestContext(ITestContext testNGCtx) {
        if (testContext.get(testNGCtx.getName()) != null) {
            return testContext.get(testNGCtx.getName());
        } else {
            return new SeleniumTestsContext(getContextFromConfigFile(testNGCtx));
        }
    }

    public static SeleniumTestsContext storeClassContext(ITestContext testNGCtx, String className) {
        // unicity is on test + class because a class could be executed by 2 tests at the same time (e.g: ParallelMode.TESTS)
        String key = getKeyForClass(testNGCtx, className);
        SeleniumTestsContext clsContext;
        if (classContext.get(key) != null) {
            return classContext.get(key);
        } else if (testContext.get(testNGCtx.getName()) != null) {
            clsContext = new SeleniumTestsContext(testContext.get(testNGCtx.getName()));
        } else {
            clsContext = new SeleniumTestsContext(getContextFromConfigFile(testNGCtx));
        }
        setClassContext(testNGCtx, className, clsContext);
        ;
        return clsContext;
    }

    public static void setClassContext(ITestContext testNGCtx, String className, SeleniumTestsContext clsContext) {
        classContext.put(getKeyForClass(testNGCtx, className), clsContext);
    }

    /**
     * Returns class context from test NG context and class name
     * @param testNGCtx
     * @param className
     * @return
     */
    public static SeleniumTestsContext getClassContext(ITestContext testNGCtx, String className) {
        String keyClass = getKeyForClass(testNGCtx, className);
        if (classContext.get(keyClass) != null) {
            return classContext.get(keyClass);
        } else if (testContext.get(testNGCtx.getName()) != null) {
            return testContext.get(testNGCtx.getName());
        } else {
            return new SeleniumTestsContext(getContextFromConfigFile(testNGCtx));
        }
    }

    public static SeleniumTestsContext storeMethodContext(ITestContext testNGCtx, String className,
            String methodName) {
        // unicity is on test + class + method because the same method name may exist in several classes or 2 testNG tests could execute the same test methods 
        SeleniumTestsContext mtdContext = getMethodContext(testNGCtx, className, methodName, true);
        setMethodContext(testNGCtx, className, methodName, mtdContext);
        return mtdContext;
    }

    public static void setMethodContext(ITestContext testNGCtx, String className, String methodName,
            SeleniumTestsContext mtdContext) {
        methodContext.put(getKeyForMethod(testNGCtx, className, methodName), mtdContext);
    }

    /**
     * Get the context that will be used for the test method
     * Search for (by order) a specific method context, class context, test context. If none is found, create one 
     * If found, returns a copy of the context if 'createCopy' is true
     * @param testNGCtx
     * @param className
     * @param methodName
     * @param createCopy      if true and we find class or test context, returns a copy
     * @return
     */
    public static SeleniumTestsContext getMethodContext(ITestContext testNGCtx, String className, String methodName,
            boolean createCopy) {

        // unicity is on test + class + method because the same method name may exist in several classes or 2 testNG tests could execute the same test methods 
        String keyMethod = getKeyForMethod(testNGCtx, className, methodName);
        String keyClass = getKeyForClass(testNGCtx, className);

        if (methodContext.get(keyMethod) != null) {
            return methodContext.get(keyMethod);
        } else if (classContext.get(keyClass) != null) {
            return createCopy ? new SeleniumTestsContext(classContext.get(keyClass)) : classContext.get(keyClass);
        } else if (testContext.get(testNGCtx.getName()) != null) {
            return createCopy ? new SeleniumTestsContext(testContext.get(testNGCtx.getName()))
                    : testContext.get(testNGCtx.getName());
        } else {
            return new SeleniumTestsContext(getContextFromConfigFile(testNGCtx));
        }
    }

    public static Map<String, SeleniumTestsContext> getTestContext() {
        return testContext;
    }

    public static Map<String, SeleniumTestsContext> getClassContext() {
        return classContext;
    }

    public static Map<String, SeleniumTestsContext> getMethodContext() {
        return methodContext;
    }

    /**
      * Get parameters from configuration file.
      * @param iTestContext
      * @param configParser
      * @return Map with parameters from the given file.
      */
    private static Map<String, String> getParametersFromConfigFile(final ITestContext iTestContext,
            TestConfigurationParser configParser) {
        Map<String, String> parameters;

        // get parameters
        if (iTestContext.getCurrentXmlTest() != null) {
            parameters = iTestContext.getCurrentXmlTest().getSuite().getParameters();
        } else {
            parameters = iTestContext.getSuite().getXmlSuite().getParameters();
        }
        // insert parameters
        for (Node node : configParser.getParameterNodes()) {
            parameters.put(node.getAttributes().getNamedItem("name").getNodeValue(),
                    node.getAttributes().getNamedItem("value").getNodeValue());
        }
        return parameters;
    }

    /**
     * 
     * @param iTestContext
     * @return run mode corresponding to the given test context
     */
    private static String setRunMode(final ITestContext iTestContext) {
        String runMode;
        if (System.getProperty(SeleniumTestsContext.RUN_MODE) != null) {
            runMode = System.getProperty(SeleniumTestsContext.RUN_MODE);
        } else if (iTestContext.getSuite().getParameter(SeleniumTestsContext.RUN_MODE) != null) {
            runMode = iTestContext.getSuite().getParameter(SeleniumTestsContext.RUN_MODE);
        } else {
            runMode = "LOCAL";
        }
        return runMode;
    }

    /**
     * Get service parameters from configuration file.
     * Only the parameters corresponding to the defined runMode.
     * @param parameters
     * @param runMode
     * @param iTestContext
     * @param configParser
     * @return Map with service parameters from the given file.
     */
    private static Map<String, String> getServiceParameters(Map<String, String> parameters, String runMode,
            TestConfigurationParser configParser) {

        for (Node node : configParser.getServiceNodes()) {

            if (node.getAttributes().getNamedItem("name").getNodeValue().equalsIgnoreCase(runMode)) {

                NodeList nList = node.getChildNodes();
                for (int i = 0; i < nList.getLength(); i++) {
                    Node paramNode = nList.item(i);
                    if ("parameter".equals(paramNode.getNodeName())) {
                        parameters.put(paramNode.getAttributes().getNamedItem("name").getNodeValue(),
                                paramNode.getAttributes().getNamedItem("value").getNodeValue());
                    }
                }
            }
        }
        return parameters;
    }

    /**
     * Set the parameters for the test with parameters from XML configuration file.
     * @param   iTestContext
     * @return  iTestContext set with parameters from external config file
     */
    public static ITestContext getContextFromConfigFile(final ITestContext iTestContext) {
        if (iTestContext != null
                && iTestContext.getSuite().getParameter(SeleniumTestsContext.TEST_CONFIGURATION) != null) {

            File suiteFile = new File(iTestContext.getSuite().getXmlSuite().getFileName());
            String configFile = suiteFile.getPath().replace(suiteFile.getName(), "")
                    + iTestContext.getSuite().getParameter(SeleniumTestsContext.TEST_CONFIGURATION);

            TestConfigurationParser configParser = new TestConfigurationParser(configFile);

            Map<String, String> parameters = getParametersFromConfigFile(iTestContext, configParser);

            // get configuration for services.
            String runMode = setRunMode(iTestContext);
            parameters = getServiceParameters(parameters, runMode, configParser);

            // 
            parameters.put(SeleniumTestsContext.DEVICE_LIST, configParser.getDeviceNodesAsJson());

            if (iTestContext.getCurrentXmlTest() != null) {
                // iTestContext is a test context provided by TestNG
                iTestContext.getCurrentXmlTest().getSuite().setParameters(parameters);
            } else {
                // iTestContext is a DefaultTestNGContext
                iTestContext.getSuite().getXmlSuite().setParameters(parameters);
            }
        }

        return iTestContext;
    }

    public static void initThreadContext() {
        initThreadContext(globalContext.getTestNGContext(), null, null, null);
    }

    public static void initThreadContext(ITestContext testNGCtx, String testName, String className,
            ITestResult testResult) {

        ITestContext newTestNGCtx = getContextFromConfigFile(testNGCtx);
        SeleniumTestsContext seleniumTestsCtx = new SeleniumTestsContext(newTestNGCtx);

        threadLocalContext.set(seleniumTestsCtx);

        // update some values after init. These init call the thread context previously created
        if (testResult != null) {
            seleniumTestsCtx.configureContext(testResult);
        }
    }

    /**
     * Update the current thread context without recreating it
     * This is a correction for issue #94
     * @param testName
     */
    public static void updateThreadContext(ITestResult testResult) {
        if (threadLocalContext.get() != null) {
            threadLocalContext.get().configureContext(testResult);
        }
    }

    public static void setGlobalContext(final SeleniumTestsContext ctx) {
        globalContext = ctx;
    }

    public static void setThreadContext(final SeleniumTestsContext ctx) {
        threadLocalContext.set(ctx);
    }

    /**
     * get SR context stored in test result if it exists. Else, create a new one (happens when a test method has been skipped for example)
     * called from reporters only
     * @param testNGCtx
     * @param testName
     * @param testResult
     */
    public static void setThreadContextFromTestResult(ITestContext testNGCtx, String testName, String className,
            ITestResult testResult) {
        if (testResult == null) {
            throw new ConfigurationException("Cannot set context from testResult as it is null");
        }
        if (testResult.getAttribute(SeleniumRobotTestListener.TEST_CONTEXT) != null) {
            setThreadContext(
                    (SeleniumTestsContext) testResult.getAttribute(SeleniumRobotTestListener.TEST_CONTEXT));
        } else {
            logger.error("Result did not contain thread context, initializing a new one");
            initThreadContext(testNGCtx, testName, className, testResult);
            testResult.setAttribute(SeleniumRobotTestListener.TEST_CONTEXT, getThreadContext());
        }
    }

    public static void removeThreadContext() {
        threadLocalContext.remove();
    }

    /**
     * Build the root path of STF 
     * method for guessing it is different if we are inside a jar (built mode) or in development
     * @param clazz
     * @param path
     * @return
     */
    public static void getPathFromClass(Class<?> clazz, StringBuilder path) {

        try {
            String url = URLDecoder.decode(clazz.getProtectionDomain().getCodeSource().getLocation().getFile(),
                    "UTF-8");
            if (url.endsWith(".jar")) {
                path.append((new File(url).getParentFile().getAbsoluteFile().toString() + "/")
                        .replace(File.separator, "/"));
                deployedMode = true;
            } else {
                path.append((new File(url).getParentFile().getParentFile().getAbsoluteFile().toString() + "/")
                        .replace(File.separator, "/"));
                deployedMode = false;
            }
        } catch (UnsupportedEncodingException e) {
            logger.error(e);
        }
    }

    /**
    * reads <app>-version.txt file which should be available for all application
    * It's generated by maven antrun task
    * From the version found, generate an application version by removing SNAPSHOT (if any) and trailing build version
    * 1.2.0-SNAPSHOT => 1.2
    * @return
    */
    private static String readApplicationVersion() {
        return readApplicationVersion(String.format("%s-version.txt", applicationName));
    }

    private static String readCoreVersion() {
        return readApplicationVersion("core-version.txt");
    }

    public static String readApplicationVersion(String resourceName) {
        try {
            String version = IOUtils
                    .toString(SeleniumTestsContextManager.class.getClassLoader().getResourceAsStream(resourceName));
            if (version.isEmpty()) {
                return "0.0";
            }
            String[] versionParts = version.split("\\.", 3);
            try {
                return String.format("%s.%s", versionParts[0], versionParts[1]);
            } catch (IndexOutOfBoundsException e) {
                return versionParts[0];
            }
        } catch (IOException | NullPointerException e) {
            logger.warn(
                    "application version has not been read. It may have not been generated. Execute maven build before launching test");
            return "0.0";
        }
    }

    /**
     * Generate all applications path
     * - root
     * - data
     * - config
     * @param xmlSuite
     */
    public static void generateApplicationPath(XmlSuite xmlSuite) {

        StringBuilder path = new StringBuilder();
        getPathFromClass(SeleniumTestsContext.class, path);

        rootPath = path.toString();

        // in case launching unit test from eclipse, a temp file is generated outside the standard folder structure
        // APPLICATION_NAME and DATA_PATH must be rewritten
        // application name is get from the testNG file path (the subdir name after 'data')
        try {
            applicationNameWithVersion = xmlSuite.getFileName().replace(File.separator, "/")
                    .split("/" + DATA_FOLDER_NAME + "/")[1].split("/")[0];
            Pattern appVersion = Pattern.compile("([a-zA-Z0-9-]+)(_.*)?");
            Matcher appVersionMatcher = appVersion.matcher(applicationNameWithVersion);
            if (appVersionMatcher.matches()) {
                applicationName = appVersionMatcher.group(1);
            } else {
                applicationName = applicationNameWithVersion;
            }
            dataPath = xmlSuite.getFileName().replace(File.separator, "/").split("/" + DATA_FOLDER_NAME + "/")[0]
                    + "/" + DATA_FOLDER_NAME + "/";
        } catch (IndexOutOfBoundsException | NullPointerException e) {
            applicationName = "core";
            applicationNameWithVersion = "core";
            dataPath = Paths.get(rootPath, DATA_FOLDER_NAME).toString() + "/";
        }

        featuresPath = Paths.get(dataPath, applicationNameWithVersion, "features").toString();
        configPath = Paths.get(dataPath, applicationNameWithVersion, "config").toString();
        appDataPath = Paths.get(dataPath, applicationNameWithVersion).toString();
        cachePath = Paths.get(rootPath, CACHE_FOLDER_NAME, applicationNameWithVersion).toString();

        if (applicationVersion == null) {
            applicationVersion = readApplicationVersion();
        }
        if (coreVersion == null) {
            coreVersion = readCoreVersion();
        }

        // create data folder if it does not exist (it should already exist)
        if (!new File(dataPath).isDirectory()) {
            new File(dataPath).mkdirs();
        }
        if (!new File(appDataPath).isDirectory()) {
            new File(appDataPath).mkdirs();
        }
        if (!new File(cachePath).isDirectory()) {
            new File(cachePath).mkdirs();
        }
    }

    /**
     * Returns application root path
     * @return
     */
    public static String getRootPath() {
        return rootPath;
    }

    /**
     * Returns location of feature files
     * @return
     */
    public static String getFeaturePath() {
        return featuresPath;
    }

    public static String getApplicationName() {
        return applicationName;
    }

    public static String getApplicationNameWithVersion() {
        return applicationNameWithVersion;
    }

    public static String getApplicationVersion() {
        return applicationVersion;
    }

    public static String getCoreVersion() {
        return coreVersion;
    }

    /**
     * Returns location of config files
     * @return
     */
    public static String getConfigPath() {
        return configPath;
    }

    /**
     * Returns location of data folder
     * @return
     */
    public static String getDataPath() {
        return dataPath;
    }

    /**
     * Returns location of data folder for this application
     * @return
     */
    public static String getApplicationDataPath() {
        return appDataPath;
    }

    /**
     * Returns location of cache folder for this application
     * @return
     */
    public static String getCachePath() {
        return cachePath;
    }

    public static Boolean getDeployedMode() {
        if (deployedMode == null) {
            getPathFromClass(SeleniumTestsContext.class, new StringBuilder());
        }
        return deployedMode;
    }

    public static boolean isWebTest() {
        return getThreadContext().getTestType().family().equals(TestType.WEB);
    }

    public static boolean isMobileTest() {
        return getThreadContext().getTestType().isMobile();
    }

    public static boolean isNonGuiTest() {
        return getThreadContext().getTestType().family().equals(TestType.NON_GUI);
    }

    public static boolean isAppTest() {
        return getThreadContext().getTestType().family().equals(TestType.APP);
    }

    public static boolean isMobileAppTest() {
        return getThreadContext().getTestType().family().equals(TestType.APP)
                && getThreadContext().getTestType().isMobile();
    }

    public static boolean isMobileWebTest() {
        return getThreadContext().getTestType().family().equals(TestType.WEB)
                && getThreadContext().getTestType().isMobile();
    }

    public static boolean isDesktopAppTest() {
        return getThreadContext().getTestType().family().equals(TestType.APP)
                && !getThreadContext().getTestType().isMobile();
    }

    public static boolean isDesktopWebTest() {
        return getThreadContext().getTestType().family().equals(TestType.WEB)
                && !getThreadContext().getTestType().isMobile();
    }

    public static String getSuiteName() {
        return getGlobalContext().getTestNGContext().getSuite().getName();
    }
}