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

eu.tsystems.mms.tic.testframework.webdrivermanager.WebDriverSessionsManager Maven / Gradle / Ivy

There is a newer version: 2.10
Show newest version
/*
 * Testerra
 *
 * (C) 2020, Peter Lehmann, T-Systems Multimedia Solutions GmbH, Deutsche Telekom AG
 *
 * Deutsche Telekom AG and all other contributors /
 * copyright owners license this file to you 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 eu.tsystems.mms.tic.testframework.webdrivermanager;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import eu.tsystems.mms.tic.testframework.common.Testerra;
import eu.tsystems.mms.tic.testframework.constants.TesterraProperties;
import eu.tsystems.mms.tic.testframework.events.ContextUpdateEvent;
import eu.tsystems.mms.tic.testframework.exceptions.SystemException;
import eu.tsystems.mms.tic.testframework.internal.utils.DriverStorage;
import eu.tsystems.mms.tic.testframework.report.model.context.SessionContext;
import eu.tsystems.mms.tic.testframework.report.utils.ExecutionContextUtils;
import eu.tsystems.mms.tic.testframework.report.utils.IExecutionContextController;
import eu.tsystems.mms.tic.testframework.useragents.BrowserInformation;
import eu.tsystems.mms.tic.testframework.utils.ObjectUtils;
import eu.tsystems.mms.tic.testframework.utils.StringUtils;
import eu.tsystems.mms.tic.testframework.utils.WebDriverUtils;
import eu.tsystems.mms.tic.testframework.webdriver.WebDriverFactory;
import java.net.URL;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.apache.commons.lang3.time.StopWatch;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.support.events.EventFiringWebDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @todo Migrate to {@link DefaultWebDriverManager}
 */
@Deprecated
public final class WebDriverSessionsManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebDriverSessionsManager.class);

    private static final Map WEB_DRIVER_FACTORIES = new HashMap<>();

    public static final String EXCLUSIVE_PREFIX = "EXCLUSIVE_";

    public static final Map SESSION_STARTUP_ERRORS = new LinkedHashMap<>();

    private static final Map EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP = new ConcurrentHashMap<>();
    private static final Map THREAD_SESSION_KEY_WEBDRIVER_MAP = new ConcurrentHashMap<>();
    private static final Map WEBDRIVER_THREAD_ID_MAP = new ConcurrentHashMap<>();
    static final Map WEBDRIVER_SESSIONS_CONTEXTS_MAP = new ConcurrentHashMap<>();
    private static final Queue> beforeQuitActions = new ConcurrentLinkedQueue<>();
    private static final Queue> afterQuitActions = new ConcurrentLinkedQueue<>();
    private static final Queue> WEBDRIVER_STARTUP_HANDLERS = new ConcurrentLinkedQueue<>();

    private static final String FULL_SESSION_KEY_SPLIT_MARKER = "___";
    private static final Set webDriverFactories = Testerra.getInjector().getInstance(Key.get(new TypeLiteral>(){}));
    static final Queue> webDriverRequestConfigurators = new ConcurrentLinkedQueue<>();
    private static final IExecutionContextController executionContextController = Testerra.getInjector().getInstance(IExecutionContextController.class);

    private WebDriverSessionsManager() {

    }

    private static String getThreadSessionKey(String sessionKey) {
        Thread currentThread = Thread.currentThread();
        return currentThread.getId() + FULL_SESSION_KEY_SPLIT_MARKER + sessionKey;
    }

    static {
        /**
         * Getting multi binder set programmatically
         * @see {https://groups.google.com/forum/#!topic/google-guice/EUnNStmrhOk}
         */
        webDriverFactories.stream()
                .sorted(Comparator.comparing(f -> f.getClass().getSimpleName()))
                .forEach(webDriverFactory -> {
                    webDriverFactory.getSupportedBrowsers().forEach(browser -> WEB_DRIVER_FACTORIES.put(browser, webDriverFactory));
                });
    }

    private static void storeWebDriverSession(EventFiringWebDriver eventFiringWebDriver, SessionContext sessionContext) {
        WebDriverRequest webDriverRequest = sessionContext.getWebDriverRequest();
        final String sessionKey = webDriverRequest.getSessionKey();
        final String threadSessionKey = getThreadSessionKey(sessionKey);
        THREAD_SESSION_KEY_WEBDRIVER_MAP.put(threadSessionKey, eventFiringWebDriver);

        final long threadId = Thread.currentThread().getId();
        WEBDRIVER_THREAD_ID_MAP.put(eventFiringWebDriver, threadId);

        /*
        storing driver into driver storage, for whatever reason
         */
        if (Testerra.Properties.REUSE_DATAPROVIDER_DRIVER_BY_THREAD.asBool()) {
            String methodName = ExecutionContextUtils.getMethodNameFromCurrentTestResult();
            String threadName = Thread.currentThread().getId() + "";
            LOGGER.debug("Saving driver in " + DriverStorage.class.getSimpleName() + " for : " + methodName + ": " + threadName);
            DriverStorage.saveDriverForTestMethod(eventFiringWebDriver, threadName, methodName);
        }

        /*
        store driver to session context relation
         */
        WEBDRIVER_SESSIONS_CONTEXTS_MAP.put(eventFiringWebDriver, sessionContext);
    }

    private static void unlinkFromThread(String sessionKey, WebDriver eventFiringWebDriver) {
        final String sessionIdentifier = createSessionIdentifier(eventFiringWebDriver, sessionKey);
        LOGGER.trace("Unlink from thread: " + sessionIdentifier);
        String threadSessionKey = getThreadSessionKey(sessionKey);
        THREAD_SESSION_KEY_WEBDRIVER_MAP.remove(threadSessionKey, eventFiringWebDriver);

        final long threadId = Thread.currentThread().getId();
        WEBDRIVER_THREAD_ID_MAP.remove(eventFiringWebDriver, threadId);

        /*
        storing driver into driver storage, for whatever reason
         */
        if (Testerra.Properties.REUSE_DATAPROVIDER_DRIVER_BY_THREAD.asBool()) {
            String methodName = ExecutionContextUtils.getMethodNameFromCurrentTestResult();
            String threadName = Thread.currentThread().getId() + "";
            LOGGER.info("Removing driver in " + DriverStorage.class.getSimpleName() + " for : " + methodName + ": " + threadName);
            DriverStorage.removeSpecificDriver(methodName);
        }

        /*
        Log something about the session handling maps
         */
        String msg = "Removed WebDriver session: " + sessionKey;
        msg += "\n Remaining sessions: ";
        int i = 0;
        for (WebDriver webDriver : WEBDRIVER_THREAD_ID_MAP.keySet()) {
            Long tid = WEBDRIVER_THREAD_ID_MAP.get(webDriver);
            String key = getSessionKey(webDriver);
            msg += "\n  " + key + " in thread " + tid;
            i++;
        }
        msg += "\n => " + i + " sessions (map: " + THREAD_SESSION_KEY_WEBDRIVER_MAP.size() + ")";
        LOGGER.debug(msg);
    }

    /**
     * Introduce an own webdriver object. Selenium session will be released in this case.
     *
     * @param webDriver     .
     * @param sessionKey .
     */
    static void introduceWebDriver(final String sessionKey, WebDriver webDriver) {
        if (!(webDriver instanceof RemoteWebDriver)) {
            throw new IllegalArgumentException(
                    "The driver object of the argument must be an instance of RemoteWebDriver");
        }

        EventFiringWebDriver eventFiringWebDriver;

        if (webDriver instanceof EventFiringWebDriver) {
            eventFiringWebDriver = (EventFiringWebDriver)webDriver;
        } else {
            eventFiringWebDriver = wrapWebDriver(webDriver);
        }

        LOGGER.info("Introducing webdriver object");
        //EventFiringWebDriver eventFiringWebDriver = wrapRawWebDriverWithEventFiringWebDriver(driver);

        UnspecificWebDriverRequest request = new UnspecificWebDriverRequest();
        request.setSessionKey(sessionKey);

        // create new session context
        SessionContext sessionContext = new SessionContext(request);

        // store to method context
        executionContextController.getCurrentMethodContext().ifPresent(methodContext -> {
            methodContext.addSessionContext(sessionContext);
        });
        storeWebDriverSession(eventFiringWebDriver, sessionContext);
    }

    public static void registerWebDriverBeforeShutdownHandler(Consumer beforeQuit) {
        beforeQuitActions.add(beforeQuit);
    }

    public static void registerWebDriverAfterShutdownHandler(Consumer afterQuit) {
        afterQuitActions.add(afterQuit);
    }

    public static void registerWebDriverAfterStartupHandler(Consumer afterStart) {
        WEBDRIVER_STARTUP_HANDLERS.add(afterStart);
    }

    public static void unregisterWebDriverAfterStartupHandler(Consumer afterStart) {
        WEBDRIVER_STARTUP_HANDLERS.remove(afterStart);
    }

    private static String createSessionIdentifier(WebDriver webDriver, String sessionKey) {
        return String.format("%s (sessionKey=%s)", webDriver.getClass().getSimpleName(), sessionKey);
    }

    public static void shutdownWebDriver(WebDriver webDriver) {
        String sessionKey = getSessionKey(webDriver);
        String sessionIdentifier = createSessionIdentifier(webDriver, sessionKey);

        beforeQuitActions.forEach(webDriverConsumer -> {
            try {
                LOGGER.trace("Call before shutdown handler");
                webDriverConsumer.accept(webDriver);
            } catch (Exception e) {
                LOGGER.error("Failed executing before shutdown handler", e);
            }
        });
        LOGGER.info("Shutting down " + sessionIdentifier);
        WebDriverManagerUtils.quitWebDriverSession(webDriver);

        afterQuitActions.forEach(webDriverConsumer -> {
            try {
                LOGGER.trace("Call after shutdown handler");
                webDriverConsumer.accept(webDriver);
            } catch (Exception e) {
                LOGGER.error("Failed executing after shutdown handler", e);
            }
        });
        unlinkFromThread(sessionKey, webDriver);
        WEBDRIVER_SESSIONS_CONTEXTS_MAP.remove(webDriver);
        if (sessionKey.startsWith(EXCLUSIVE_PREFIX)) {
            EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.remove(sessionKey);
        }
        LOGGER.debug("Shut down: " + sessionIdentifier);
    }


    static void shutdownAllThreadSessions() {
        getWebDriversFromCurrentThread().forEach(WebDriverSessionsManager::shutdownWebDriver);
    }

    public static Stream getWebDriversFromCurrentThread() {
        long threadId = Thread.currentThread().getId();
        return getWebDriversFromThread(threadId);
    }

    public static Stream readExclusiveWebDrivers() {
        return EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.values().stream();
    }

    static void shutdownAllSessions() {
        THREAD_SESSION_KEY_WEBDRIVER_MAP.values().forEach(WebDriverSessionsManager::shutdownWebDriver);
        EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.values().forEach(WebDriverSessionsManager::shutdownWebDriver);

        // This should not be necessary but we do it anyway
//        THREAD_SESSION_KEY_WEBDRIVER_MAP.clear();
//        WEBDRIVER_THREAD_ID_MAP.clear();
//        EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.clear();
//        WEBDRIVER_SESSIONS_CONTEXTS_MAP.clear();
    }

    /**
     * Returns true if any session is active.
     *
     * @return .
     */
    static boolean hasAnySessionActive() {
        return hasSessionActiveInThisThread();
    }

    static boolean hasSessionActiveInThisThread() {
        long threadId = Thread.currentThread().getId();
        return getWebDriversFromThread(threadId).findAny().isPresent();
    }

    static synchronized String makeSessionExclusive(final WebDriver webDriver) {
        if (!(webDriver instanceof EventFiringWebDriver)) {
            throw new RuntimeException(webDriver.getClass().getSimpleName() + " is no instance of " + EventFiringWebDriver.class.getSimpleName());
        }

        EventFiringWebDriver eventFiringWebDriver = (EventFiringWebDriver)webDriver;

        if (EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.containsValue(webDriver)) {
            LOGGER.error("Session already set exclusive.");
            return null;
        }

        SessionContext sessionContext = getSessionContext(webDriver).get();
        String sessionKey = sessionContext.getSessionKey();
        unlinkFromThread(sessionKey, webDriver);
        /*
        Add session to exclusive map.
         */
        String exclusiveSessionKey = EXCLUSIVE_PREFIX + UUID.randomUUID().toString();
        EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.put(exclusiveSessionKey, eventFiringWebDriver);

        /*
        introduce session context to execution context
         */
        sessionContext.setSessionKey(exclusiveSessionKey);
        sessionContext.getWebDriverRequest().setShutdownAfterTest(false);
        executionContextController.getExecutionContext().addExclusiveSessionContext(sessionContext);
        Testerra.getEventBus().post(new ContextUpdateEvent().setContext(sessionContext));

        LOGGER.info("Promoted " + createSessionIdentifier(webDriver, sessionKey) + " to " + createSessionIdentifier(webDriver, exclusiveSessionKey));
        return exclusiveSessionKey;
    }

    public static void shutdownSessionKey(final String key) {
        if (EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.containsKey(key)) {
            shutdownWebDriver(EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.get(key));
            EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.remove(key);
        } else if (THREAD_SESSION_KEY_WEBDRIVER_MAP.containsKey(key)) {
            shutdownWebDriver(THREAD_SESSION_KEY_WEBDRIVER_MAP.get(key));
        }
    }

    /**
     * @deprecated Use {@link #getSessionContext(WebDriver)} instead
     */
    static String getSessionKey(WebDriver webDriver) {
        return getSessionContext(webDriver).map(SessionContext::getSessionKey).orElse("no session");
    }

    static Stream getWebDriversFromThread(final long threadId) {
        return WEBDRIVER_THREAD_ID_MAP.entrySet().stream().filter(entry -> entry.getValue() == threadId).map(Map.Entry::getKey);
    }

    public static EventFiringWebDriver getWebDriver(final WebDriverRequest webDriverRequest) {
        String sessionKey = webDriverRequest.getSessionKey();
        /*
        Check for exclusive session
         */
        if (sessionKey.startsWith(EXCLUSIVE_PREFIX)) {
            // returning exclusive session
            if (EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.containsKey(sessionKey)) {
                return EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.get(sessionKey);
            } else {
                throw new SystemException("No Session for key: " + sessionKey);
            }
        }

        String fullSessionKey = getThreadSessionKey(sessionKey);
        EventFiringWebDriver existingWebDriver = THREAD_SESSION_KEY_WEBDRIVER_MAP.get(fullSessionKey);

        /*
        session already exists?
         */
        if (existingWebDriver != null) {
            return existingWebDriver;
        }

        /*
         **** STARTING NEW SESSION ****
         */

        /*
        decide which session manager to use
         */
        String browser = webDriverRequest.getBrowser();

        if (StringUtils.isBlank(browser)) {
            throw new SystemException(String.format("No browser configured. Please define one in %s.setBrowser() or property '%s'", WebDriverRequest.class.getSimpleName(), IWebDriverManager.Properties.BROWSER_SETTING));
        }

        if (WEB_DRIVER_FACTORIES.containsKey(browser)) {
            WebDriverFactory webDriverFactory = WEB_DRIVER_FACTORIES.get(browser);

             /*
            create session context and link to method context
             */
            final WebDriverRequest finalWebDriverRequest = webDriverFactory.prepareWebDriverRequest(webDriverRequest);
            webDriverRequestConfigurators.forEach(handler -> handler.accept(finalWebDriverRequest));

            SessionContext sessionContext = new SessionContext(finalWebDriverRequest);
            executionContextController.getCurrentMethodContext().ifPresent(methodContext -> {
                methodContext.addSessionContext(sessionContext);
            });
            executionContextController.setCurrentSessionContext(sessionContext);

            logRequest(finalWebDriverRequest, sessionContext);

            /*
            setup new session
             */
            StopWatch sw = new StopWatch();
            sw.start();
            WebDriver newRawWebDriver = webDriverFactory.createWebDriver(finalWebDriverRequest, sessionContext);
            sw.stop();

            if (!sessionContext.getActualBrowserName().isPresent()) {
                BrowserInformation browserInformation = WebDriverManagerUtils.getBrowserInformation(newRawWebDriver);
                sessionContext.setActualBrowserName(browserInformation.getBrowserName());
                sessionContext.setActualBrowserVersion(browserInformation.getBrowserVersion());
            }

            if (newRawWebDriver instanceof RemoteWebDriver) {
                SessionId sessionId = ((RemoteWebDriver) newRawWebDriver).getSessionId();
                sessionContext.setRemoteSessionId(sessionId.toString());
            }

            LOGGER.info(String.format(
                    "Started %s (sessionKey=%s, sessionId=%s, node=%s, userAgent=%s) in %s",
                    newRawWebDriver.getClass().getSimpleName(),
                    sessionContext.getSessionKey(),
                    sessionContext.getRemoteSessionId().orElse("(local)"),
                    sessionContext.getNodeUrl().map(Object::toString).orElse("(unknown)"),
                    sessionContext.getActualBrowserName().orElse("(unknown)") + ":" + sessionContext.getActualBrowserVersion().orElse("(unknown)"),
                    sw.toString()
            ));
            EventFiringWebDriver eventFiringWebDriver = wrapWebDriver(newRawWebDriver);
            storeWebDriverSession(eventFiringWebDriver, sessionContext);

            webDriverFactory.setupNewWebDriverSession(eventFiringWebDriver, sessionContext);
            Testerra.getEventBus().post(new ContextUpdateEvent().setContext(sessionContext));
            String sessionIdentifier = createSessionIdentifier(newRawWebDriver, sessionKey);
            /*
            run the handlers
             */
            WEBDRIVER_STARTUP_HANDLERS.forEach(webDriverConsumer -> {
                try {
                    webDriverConsumer.accept(eventFiringWebDriver);
                } catch (Exception e) {
                    LOGGER.error("Failed executing handler after starting up " + sessionIdentifier, e);
                }
            });
            return eventFiringWebDriver;
        } else {
            throw new SystemException(String.format("No %s registered for browser '%s'", WebDriverFactory.class.getSimpleName(), browser));
        }
    }

    private static void logRequest(WebDriverRequest request, SessionContext sessionContext) {
        Map cleanedCapsMap = new WebDriverCapabilityLogHelper().clean(request.getCapabilities());
        Gson gson = new GsonBuilder()
                .setPrettyPrinting()
                .disableHtmlEscaping()
                .create();
        LOGGER.info(String.format(
                "New %s (sessionKey=%s, server=%s) with capabilities:\n%s",
                request.getClass().getSimpleName(),
                sessionContext.getSessionKey(),
                (request.getServerUrl().isPresent()?request.getServerUrl().get(): "local"),
                gson.toJson(cleanedCapsMap)
        ));
        LOGGER.debug(String.format("Starting (sessionKey=%s) here", sessionContext.getSessionKey()), new Throwable());
    }

    @Deprecated
    static void registerWebDriverFactory(WebDriverFactory webDriverFactory, String... browsers) {
        LOGGER.debug("Register " + webDriverFactory.getClass().getSimpleName() + " for browsers " + String.join(", ", browsers));

        for (String browser : browsers) {
            WEB_DRIVER_FACTORIES.put(browser, webDriverFactory);
        }
    }

    public static boolean isExclusiveSession(WebDriver webDriver) {
        return EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.containsValue(webDriver);
    }

    public static Optional getSessionContext(WebDriver webDriver) {
        return Optional.ofNullable(WEBDRIVER_SESSIONS_CONTEXTS_MAP.get(webDriver));
    }

    public static Optional getWebDriver(SessionContext sessionContext) {
        return WEBDRIVER_SESSIONS_CONTEXTS_MAP.entrySet().stream()
                .filter(entry -> entry.getValue() == sessionContext)
                .map(Map.Entry::getKey)
                .findFirst();
    }

    public static Optional getRequestedBrowser(WebDriver webDriver) {
        return getSessionContext(webDriver).map(SessionContext::getWebDriverRequest).map(WebDriverRequest::getBrowser);
    }

    public static WebDriverFactory getWebDriverFactory(String browser) {
        if (WEB_DRIVER_FACTORIES.containsKey(browser)) {
            return WEB_DRIVER_FACTORIES.get(browser);
        } else {
            throw new RuntimeException("No " + WebDriverFactory.class.getSimpleName() + " registered for browser: " + browser);
        }
    }

    public static Stream readSessionContexts() {
        return WEBDRIVER_SESSIONS_CONTEXTS_MAP.values().stream();
    }

    public static Stream readWebDrivers() {
        return WEBDRIVER_SESSIONS_CONTEXTS_MAP.keySet().stream();
    }

    private static EventFiringWebDriver wrapWebDriver(WebDriver webDriver) {
        /*
        wrap the driver with the proxy
         */
        /*
         * Watch out when wrapping the driver here. Any more wraps than EventFiringWebDriver will break at least
         * the MobileDriverAdapter. This is because we need to compare the lowermost implementation of WebDriver in this case.
         * It can be made more robust, if we always can retrieve the storedSessionId of the WebDriver, given a WebDriver object.
         * For more info, please ask @rnhb
         */
        try {
            Class[] interfaces = ObjectUtils.getAllInterfacesOf(webDriver);
            webDriver = ObjectUtils.simpleProxy(WebDriver.class, webDriver, WebDriverProxy.class, interfaces);
        } catch (Exception e) {
            LOGGER.error("Could not create proxy for raw webdriver", e);
        }

        return new EventFiringWebDriver(webDriver);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy