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

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

The 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.events.ContextUpdateEvent;
import eu.tsystems.mms.tic.testframework.exceptions.SystemException;
import eu.tsystems.mms.tic.testframework.internal.metrics.MetricsController;
import eu.tsystems.mms.tic.testframework.internal.metrics.MetricsType;
import eu.tsystems.mms.tic.testframework.report.model.context.SessionContext;
import eu.tsystems.mms.tic.testframework.report.utils.IExecutionContextController;
import eu.tsystems.mms.tic.testframework.testing.WebDriverManagerProvider;
import eu.tsystems.mms.tic.testframework.useragents.BrowserInformation;
import eu.tsystems.mms.tic.testframework.utils.DefaultCapabilityUtils;
import eu.tsystems.mms.tic.testframework.webdriver.WebDriverFactory;
import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.support.events.EventFiringDecorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import java.util.stream.Stream;

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

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

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

    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(WebDriver decoratedWebDriver, SessionContext sessionContext) {
        WebDriverRequest webDriverRequest = sessionContext.getWebDriverRequest();
        final String sessionKey = webDriverRequest.getSessionKey();
        final String threadSessionKey = getThreadSessionKey(sessionKey);
        THREAD_SESSION_KEY_WEBDRIVER_MAP.put(threadSessionKey, decoratedWebDriver);

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

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

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

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

        executionContextController.clearCurrentSessionContext();

        /*
        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");
        }

        LOGGER.info("Introducing webdriver object");

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

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

        VisualEventDriverListener visualListener = new VisualEventDriverListener();
        WebDriver decoratedDriver = new EventFiringDecorator(
                new EventLoggingDriverListener(),
                visualListener
        ).decorate(webDriver);

        visualListener.driver = decoratedDriver;

        // store to method context
        executionContextController.getCurrentMethodContext().ifPresent(methodContext -> {
            methodContext.addSessionContext(sessionContext);
            executionContextController.setCurrentSessionContext(sessionContext);
        });
        storeWebDriverSession(decoratedDriver, 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) {
        WebDriver originalDriver = WEB_DRIVER_MANAGER.getOriginalFromDecorated(webDriver);
        return String.format("%s (sessionKey=%s)", originalDriver.getClass().getSimpleName(), sessionKey);
    }

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

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

        afterQuitActions.forEach(webDriverConsumer -> {
            try {
                LOGGER.trace("Call after shutdown handler");
                webDriverConsumer.accept(decoratedWebDriver);
            } catch (Exception e) {
                LOGGER.error("Failed executing after shutdown handler", e);
            }
        });
        unlinkFromThread(sessionKey, decoratedWebDriver);
        if (WEBDRIVER_SESSIONS_CONTEXTS_MAP.get(decoratedWebDriver) != null) {
            WEBDRIVER_SESSIONS_CONTEXTS_MAP.get(decoratedWebDriver).updateEndTime(new Date());
        }
        WEBDRIVER_SESSIONS_CONTEXTS_MAP.remove(decoratedWebDriver);
        if (sessionKey.startsWith(SessionContext.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) {
        WebDriver decoratedWebDriver = checkForWrappedWebDriver(webDriver);

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

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

        /*
        introduce session context to execution context
         */
        sessionContext.setSessionKey(exclusiveSessionKey);
        sessionContext.getWebDriverRequest().setShutdownAfterTest(false);
        sessionContext.getWebDriverRequest().setShutdownAfterTestFailed(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 WebDriver getWebDriver(final WebDriverRequest webDriverRequest) {
        String sessionKey = webDriverRequest.getSessionKey();
        WebDriver existingWebDriver = null;
        /*
        Check for exclusive session
         */
        if (sessionKey.startsWith(SessionContext.EXCLUSIVE_PREFIX)) {
            // returning exclusive session
            if (EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.containsKey(sessionKey)) {
                existingWebDriver = EXCLUSIVE_SESSION_KEY_WEBDRIVER_MAP.get(sessionKey);
            } else {
                throw new SystemException("No Session for key: " + sessionKey);
            }
        } else {
            String fullSessionKey = getThreadSessionKey(sessionKey);
            existingWebDriver = THREAD_SESSION_KEY_WEBDRIVER_MAP.get(fullSessionKey);
        }

        /*
        session already exists?
         */
        if (existingWebDriver != null) {
            /*
            Link sessionContext to methodContext if not exist
            e.g. Session was created in setup method and reused in test method or exclusive session is used in current test.
            */
            if (sessionKey.startsWith(SessionContext.EXCLUSIVE_PREFIX)) {
                // Link an exclusive sessionContext
                executionContextController.getExecutionContext().readExclusiveSessionContexts()
                        .filter(sessionContext -> sessionContext.getSessionKey().equals(sessionKey))
                        .findFirst()
                        .ifPresent(sessionContext ->
                                executionContextController.getCurrentMethodContext().ifPresent(currentMethodContext ->
                                        currentMethodContext.addSessionContext(sessionContext)
                                ));
            } else {
                // Link a normal sessionContext
                executionContextController.getCurrentSessionContext().ifPresent(currentSessionContext ->
                        executionContextController.getCurrentMethodContext().ifPresent(currentMethodContext ->
                                currentMethodContext.addSessionContext(currentSessionContext)
                        ));
            }
            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);

            // Catch all existing browser caps and add them to specific browser options
            final WebDriverRequest finalWebDriverRequest = webDriverFactory.prepareWebDriverRequest(webDriverRequest);
            // Update webDriverRequest with caps of external or global defined request configurators
            webDriverRequestConfigurators.forEach(handler -> handler.accept(finalWebDriverRequest));

            // Create session context and link to method context
            SessionContext sessionContext = new SessionContext(finalWebDriverRequest);
            executionContextController.getCurrentMethodContext().ifPresent(methodContext -> {
                methodContext.addSessionContext(sessionContext);
            });
            executionContextController.setCurrentSessionContext(sessionContext);

            logRequest(finalWebDriverRequest, sessionContext);

            /*
            setup new session
             */
            MetricsController metricsController = Testerra.getInjector().getInstance(MetricsController.class);
            metricsController.start(sessionContext, MetricsType.SESSION_LOAD);
            WebDriver newRawWebDriver = webDriverFactory.createWebDriver(finalWebDriverRequest, sessionContext);
            metricsController.stop(sessionContext, MetricsType.SESSION_LOAD);

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

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

            Duration diff = metricsController.getDuration(sessionContext, MetricsType.SESSION_LOAD);
            LOGGER.info(String.format(
                    "Started %s (sessionKey=%s, node=%s, userAgent=%s) in %02d:%02d.%03d",
                    newRawWebDriver.getClass().getSimpleName(),
                    sessionContext.getSessionKey(),
                    sessionContext.getNodeUrl().map(Object::toString).orElse("(unknown)"),
                    sessionContext.getActualBrowserName().orElse("(unknown)") + ":" + sessionContext.getActualBrowserVersion().orElse("(unknown)"),
                    diff.toMinutesPart(), diff.toSecondsPart(), diff.toMillisPart()

            ));

            WebDriver decoratedDriver = webDriverFactory.setupNewWebDriverSession(newRawWebDriver, sessionContext);

            storeWebDriverSession(decoratedDriver, sessionContext);

            Testerra.getEventBus().post(new ContextUpdateEvent().setContext(sessionContext));
            String sessionIdentifier = createSessionIdentifier(newRawWebDriver, sessionKey);
            /*
            run the handlers
             */
            WEBDRIVER_STARTUP_HANDLERS.forEach(webDriverConsumer -> {
                try {
                    webDriverConsumer.accept(decoratedDriver);
                } catch (Exception e) {
                    LOGGER.error("Failed executing handler after starting up " + sessionIdentifier, e);
                }
            });
            return decoratedDriver;
        } else {
            throw new SystemException(String.format("Your requested browser %s is unknown. Please check your configuration.", browser));
        }
    }

    private static void logRequest(WebDriverRequest request, SessionContext sessionContext) {

        Map cleanedCapsMap = new DefaultCapabilityUtils().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);
        }
    }

    private static WebDriver checkForWrappedWebDriver(WebDriver webDriver) {
        if (!(webDriver instanceof WebDriver)) {
            throw new IllegalArgumentException(webDriver.getClass().getSimpleName() + " is no instance of " + WebDriver.class.getSimpleName());
        }
        return webDriver;
    }

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

    public static Optional getSessionContext(WebDriver webDriver) {
        webDriver = checkForWrappedWebDriver(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();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy