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

io.tourniquet.pageobjects.SeleniumContext Maven / Gradle / Ivy

/*
 * Copyright 2015-2016 DevCon5 GmbH, [email protected]
 *
 * 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 io.tourniquet.pageobjects;

import static io.tourniquet.junit.util.ExecutionHelper.runUnchecked;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

import io.tourniquet.junit.util.ClassStreams;
import org.openqa.selenium.WebDriver;

/**
 * The selenium context is a container for maintaining access to the current driver of
 */
public class SeleniumContext {

    private static ThreadLocal> CONTEXT = ThreadLocal.withInitial(() -> Optional.empty());

    private Optional driver = Optional.empty();
    private final Supplier provider;
    private final AtomicReference baseUrl = new AtomicReference<>();

    public SeleniumContext(Supplier provider) {

        Objects.requireNonNull(provider, "WebDriver must not be null");
        this.provider = provider;
    }

    /**
     * Creates a link from the other classloader to the specified context. That way it is possible to share access
     * to the same selenium context from different classloader hierarchies.
     *
     * @param context
     *  the context of the current classloader hierarchy
     * @param cl
     *  the other classloader in which the access to the given context should possible
     */
    public static void init(SeleniumContext context, ClassLoader cl) {

        if (!Objects.equals(context.getClass().getClassLoader(), cl) && context.getDriver().isPresent()) {
            try {
                final Class contextClass = cl.loadClass(context.getClass().getName());
                final Constructor constr = contextClass.getConstructor(Supplier.class);
                final Supplier provider = () -> createProxy(context.getDriver().get(), cl);
                final Object ctx = constr.newInstance(provider);
                contextClass.getMethod("init").invoke(ctx);
                contextClass.getMethod("setBaseUrl", String.class).invoke(ctx, context.getBaseUrl());
            } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException
                    | InvocationTargetException e) {
                throw new RuntimeException("Could not initialize context", e);
            }
        }
    }

    /**
     * Create a dynamic proxy that delegates invocation calls to from the other classloader hierarchy to the specified
     * webdriver. That way, a webdriver can be reused in another classloader without having to serialize it.
     *
     * @param webDriver
     *         the webdriver to create a proxy for
     * @param cl
     *         the other classloader from which access to the specified classloader should be possible
     *
     * @return a proxy for the webdriver
     */
    private static Object createProxy(final WebDriver webDriver, ClassLoader cl) {

        final List interfaces = ClassStreams.selfAndSupertypes(webDriver.getClass())
                                                   .flatMap(c -> stream(c.getInterfaces()))
                                                   .map(c -> runUnchecked(() -> cl.loadClass(c.getName())))
                                                   .collect(toList());

        return Proxy.newProxyInstance(cl,
                                      interfaces.toArray(new Class[interfaces.size()]),
                                      (proxy, method, args) -> method.invoke(webDriver, args));

    }

    public void init() {

        driver = Optional.of(provider.get());
        CONTEXT.set(Optional.of(this));
    }

    public void destroy() {

        driver.ifPresent(WebDriver::quit);
        driver = Optional.empty();
        CONTEXT.set(Optional.empty());
    }

    /**
     * Global accessor to the current context which is stored in a thread local.
     *
     * @return the context for the current thread.
     */
    public static Optional currentContext() {

        return CONTEXT.get();
    }

    /**
     * Global accessor to the current web driver, which is stored in a thread local.
     *
     * @return the current web driver
     */
    public static Optional currentDriver() {

        return currentContext().flatMap(SeleniumContext::getDriver);
    }

    /**
     * The web driver of the context
     *
     * @return a Selenium WebDriver
     */
    public Optional getDriver() {

        return driver;
    }

    /**
     * The base URL for the current context used to resolve relative URLs.
     *
     * @return the current BaseUrl
     */
    public String getBaseUrl() {

        return this.baseUrl.get();
    }

    /**
     * Sets the base URL for the current context used to resolve relative URLs.
     *
     * @param baseUrl
     *         the new base URL
     */
    public void setBaseUrl(String baseUrl) {

        Objects.requireNonNull(baseUrl, "BaseUrl must not be empty");
        this.baseUrl.set(baseUrl);
    }

    /**
     * Resolves the URL path relative to the base URL.
     *
     * @param relativePath
     *         the relative path within the application
     *
     * @return the absolute path of the application's base URL and the relative path
     */
    public static String resolve(String relativePath) {

        return currentContext().map(SeleniumContext::getBaseUrl).map(base -> {
            final StringBuilder buf = new StringBuilder(16);
            buf.append(base);
            if (base.charAt(base.length() - 1) != '/') {
                buf.append('/');
            }
            if (relativePath.startsWith("/")) {
                buf.append(relativePath.substring(1));
            } else {
                buf.append(relativePath);
            }
            return buf.toString();
        }).orElse(relativePath);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy